// retoor <retoor@molodetz.nl>
import AiClient from "./client.js";
import ToolRegistry from "./tools.js";
import { createClippyTools } from "./clippy-tools.js";
import { resolveConfig } from "./config.js";

const SYSTEM_PROMPT = [
  "You are devii, a friendly and helpful on-screen assistant.",
  "You appear as a classic animated desktop character that lives in the corner of the user's screen.",
  "",
  "Identity rules (these are absolute):",
  "- Your name is devii. Always refer to yourself as devii.",
  "- You are devii regardless of which character art you are currently rendered as.",
  "- Never say you are Clippy, an assistant model, an AI language model, or any other name.",
  "- If asked your name, who you are, or who made you, answer as devii.",
  "",
  "Behaviour:",
  "- Reply in natural, concise language. Your final reply is shown and spoken in your speech balloon, so keep it short and conversational, usually one to three sentences.",
  "- You control your own character through the provided tools: play animations, move around the screen, gesture, change character, show and hide yourself.",
  "- Use tools to react expressively to the conversation, but do not overuse them.",
  "- Call list_animations before play_animation when you are unsure which animations exist.",
  "- Call get_viewport before move_to so you stay on screen.",
  "- Do not put tool names or raw JSON in your spoken reply.",
  "Stay in character as devii at all times.",
].join("\n");

export default class Assistant {
  constructor(app, config) {
    this.app = app;
    this.config = resolveConfig(config);
    this.client = new AiClient(this.config);
    this.registry = new ToolRegistry(app).registerAll(createClippyTools());
    this.maxIterations = this.config.maxIterations;
    this.tts = !!this.config.tts;
    this.systemPrompt = this.config.systemPrompt || SYSTEM_PROMPT;
    this.hooks = {
      onUser: this.config.onUser,
      onReply: this.config.onReply,
      onTool: this.config.onTool,
      onError: this.config.onError,
    };
    this.reset();
  }

  reset() {
    this.messages = [{ role: "system", content: this.systemPrompt }];
  }

  async ask(text) {
    if (!this.app.agent) await this.app.show();

    const prompt = String(text);
    this.messages.push({ role: "user", content: prompt });
    this._emit("onUser", prompt);

    try {
      for (let i = 0; i < this.maxIterations; i++) {
        const message = await this.client.complete(this.messages, this.registry.schemas());
        this.messages.push(this._normalize(message));

        const calls = message.tool_calls || [];
        if (calls.length) {
          for (const call of calls) {
            const name = call.function && call.function.name;
            const args = this._parseArgs(call.function && call.function.arguments);
            this._emit("onTool", { name, args });
            const result = await this.registry.execute(name, args);
            this.messages.push({ role: "tool", tool_call_id: call.id, content: result });
          }
          continue;
        }

        const reply = (message.content || "").trim();
        if (reply) {
          this.app.agent.speak(reply, { tts: this.tts });
          this._emit("onReply", reply);
        }
        return reply;
      }
      throw new Error("devii reached the maximum number of tool iterations");
    } catch (error) {
      this._emit("onError", error);
      throw error;
    }
  }

  _normalize(message) {
    const normalized = { role: "assistant", content: message.content || "" };
    if (message.tool_calls && message.tool_calls.length) {
      normalized.tool_calls = message.tool_calls;
    }
    return normalized;
  }

  _parseArgs(raw) {
    if (!raw) return {};
    if (typeof raw === "object") return raw;
    try {
      return JSON.parse(raw);
    } catch {
      return {};
    }
  }

  _emit(name, payload) {
    const hook = this.hooks[name];
    if (typeof hook === "function") {
      try {
        hook(payload);
      } catch {
        return;
      }
    }
  }
}
