// 遗留兼容：runAiAgent 已退出 /api/ai-agent/run 主链，保留此文件仅用于历史测试与渐进清理。
import type { OpenAiCompatibleChatMessage, OpenAiCompatibleChatOptions } from "@/lib/ai/openaiCompatibleChat";
import { openAiCompatibleChat } from "@/lib/ai/openaiCompatibleChat";
import { parseToolTagCalls, formatToolResultTag } from "../protocol/toolTagProtocol";

export type AgentChatFn = (
  messages: OpenAiCompatibleChatMessage[],
  cfg: OpenAiCompatibleChatOptions,
) => Promise<{ text: string; raw: unknown }>;

export type RunAiAgentArgs = {
  userMessages: Array<{ role: "user" | "assistant"; content: string }>;
  cfg: OpenAiCompatibleChatOptions;
  chat?: AgentChatFn;
  allowedToolIds: Set<string>;
  runTool: (toolId: string, toolArgs: Record<string, unknown>) => Promise<unknown>;
  maxSteps?: number;
  onEvent?: (event: { type: string; data: unknown }) => void;
  systemContextText?: string;
  defaultMindmapTargetUid?: string;
};

const nowMs = () => Date.now();
const DEFAULT_MAX_STEPS = 10;

const safeParseJsonObject = (raw: string): Record<string, unknown> | null => {
  const s = String(raw ?? "").trim();
  if (!s) return null;
  try {
    const parsed = JSON.parse(s);
    if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed as Record<string, unknown>;
    return null;
  } catch {
    return null;
  }
};

const buildSystemPrompt = (allowedTools: Set<string>, systemContextText?: string) => {
  const toolLines: string[] = [];
  const guideLines: string[] = [];
  if (allowedTools.has("search_web")) {
    toolLines.push("- search_web：<search_web>{\"query\":\"...\",\"count\":6}</search_web>");
  }
  if (allowedTools.has("rag_lightrag_query")) {
    toolLines.push(
      "- rag_lightrag_query：<rag_lightrag_query>{\"query\":\"...\",\"mode\":\"mix\",\"topK\":12,\"chunkTopK\":12}</rag_lightrag_query>",
    );
  }
  if (allowedTools.has("docs_search")) {
    toolLines.push("- docs_search：<docs_search>{\"query\":\"...\",\"limit\":12}</docs_search>");
  }
  if (allowedTools.has("docs_read")) {
    toolLines.push("- docs_read：<docs_read>{\"documentId\":\"...\",\"maxChars\":2500}</docs_read>");
  }
  if (allowedTools.has("image_read")) {
    toolLines.push("- image_read：<image_read>{\"attachmentRef\":\"...\"}</image_read>");
  }
  if (allowedTools.has("asset_extract_outline")) {
    toolLines.push(
      "- asset_extract_outline：<asset_extract_outline>{\"attachmentRef\":\"...\"}</asset_extract_outline>",
    );
  }
  if (allowedTools.has("asset_to_mindmap")) {
    toolLines.push(
      "- asset_to_mindmap：<asset_to_mindmap>{\"mindmapId\":\"...\",\"assetId\":\"...\",\"maxItems\":120,\"reason\":\"...\"}</asset_to_mindmap>",
    );
  }
  if (allowedTools.has("oo_get_selection")) {
    toolLines.push("- oo_get_selection：<oo_get_selection>{\"format\":\"text\"}</oo_get_selection>");
  }
  if (allowedTools.has("oo_replace_selection")) {
    toolLines.push(
      "- oo_replace_selection：<oo_replace_selection>{\"text\":\"...\",\"format\":\"text\",\"reason\":\"...\"}</oo_replace_selection>",
    );
  }
  if (allowedTools.has("oo_insert_text")) {
    toolLines.push("- oo_insert_text：<oo_insert_text>{\"text\":\"...\",\"reason\":\"...\"}</oo_insert_text>");
  }
  if (allowedTools.has("oo_insert_html")) {
    toolLines.push("- oo_insert_html：<oo_insert_html>{\"html\":\"<p>...</p>\",\"reason\":\"...\"}</oo_insert_html>");
  }
  if (allowedTools.has("oo_insert_image")) {
    toolLines.push(
      "- oo_insert_image：<oo_insert_image>{\"imageRef\":\"...\",\"width\":320,\"height\":180,\"reason\":\"...\"}</oo_insert_image>",
    );
  }
  if (
    allowedTools.has("oo_get_selection") ||
    allowedTools.has("oo_replace_selection") ||
    allowedTools.has("oo_insert_text") ||
    allowedTools.has("oo_insert_html") ||
    allowedTools.has("oo_insert_image")
  ) {
    guideLines.push("- OnlyOffice 增/删/改：先 oo_get_selection 读选区，再用 oo_replace_selection / oo_insert_* 写回。");
    guideLines.push("- oo_* 属于客户端插件工具：会直接修改当前 OnlyOffice 文档的选区/光标位置。");
  }
  if (allowedTools.has("slash_run")) {
    toolLines.push("- slash_run：<slash_run>{\"text\":\"/new 新页面标题\"}</slash_run>");
  }
  if (allowedTools.has("doc_get")) {
    toolLines.push("- doc_get：<doc_get>{\"maxBlocks\":80}</doc_get>");
  }
  if (allowedTools.has("doc_find")) {
    toolLines.push("- doc_find：<doc_find>{\"query\":\"...\",\"maxResults\":8}</doc_find>");
  }
  if (allowedTools.has("doc_insert_blocks")) {
    toolLines.push(
      "- doc_insert_blocks：<doc_insert_blocks>{\"afterBlockId\":\"...\",\"blocks\":[{\"type\":\"heading\",\"level\":2,\"text\":\"...\"},{\"type\":\"paragraph\",\"text\":\"...\"}]}</doc_insert_blocks>",
    );
  }
  if (allowedTools.has("doc_replace_range")) {
    toolLines.push(
      "- doc_replace_range：<doc_replace_range>{\"blockId\":\"...\",\"text\":\"...\",\"mode\":\"replace\"}</doc_replace_range>",
    );
  }
  if (allowedTools.has("mindmap_get")) {
    toolLines.push("- mindmap_get：<mindmap_get>{\"maxNodes\":120}</mindmap_get>");
  }
  if (allowedTools.has("mindmap_get_subtree")) {
    toolLines.push("- mindmap_get_subtree：<mindmap_get_subtree>{\"uid\":\"...\",\"depth\":2,\"maxNodes\":60}</mindmap_get_subtree>");
  }
  if (allowedTools.has("mindmap_apply_ops")) {
    toolLines.push("- mindmap_apply_ops：<mindmap_apply_ops>{\"ops\":[{\"op\":\"updateText\",\"uid\":\"...\",\"text\":\"...\"}],\"reason\":\"...\"}</mindmap_apply_ops>");
  }
  if (allowedTools.has("mindmap_add_child")) {
    toolLines.push(
      "- mindmap_add_child：<mindmap_add_child>{\"parentUid\":\"...\",\"text\":\"...\",\"hyperlink\":\"https://...\",\"note\":\"...\",\"refs\":[{\"kind\":\"url\",\"fileUrl\":\"https://...\"}]}</mindmap_add_child>",
    );
  }
  if (allowedTools.has("mindmap_add_sibling_after")) {
    toolLines.push("- mindmap_add_sibling_after：<mindmap_add_sibling_after>{\"targetUid\":\"...\",\"text\":\"...\",\"hyperlink\":\"https://...\"}</mindmap_add_sibling_after>");
  }
  if (allowedTools.has("mindmap_update_node_text")) {
    toolLines.push("- mindmap_update_node_text：<mindmap_update_node_text>{\"uid\":\"...\",\"text\":\"...\"}</mindmap_update_node_text>");
  }
  if (allowedTools.has("mindmap_set_hyperlink")) {
    toolLines.push("- mindmap_set_hyperlink：<mindmap_set_hyperlink>{\"uid\":\"...\",\"hyperlink\":\"https://...\"}</mindmap_set_hyperlink>（清除传 null）");
  }
  if (allowedTools.has("mindmap_append_note")) {
    toolLines.push("- mindmap_append_note：<mindmap_append_note>{\"uid\":\"...\",\"markdown\":\"...\"}</mindmap_append_note>");
  }
  if (allowedTools.has("mindmap_set_refs")) {
    toolLines.push("- mindmap_set_refs：<mindmap_set_refs>{\"uid\":\"...\",\"refs\":[{\"kind\":\"url\",\"fileUrl\":\"https://...\"}]}</mindmap_set_refs>");
  }
  if (allowedTools.has("mindmap_delete_node")) {
    toolLines.push("- mindmap_delete_node：<mindmap_delete_node>{\"uid\":\"...\"}</mindmap_delete_node>");
  }
  if (allowedTools.has("mindmap_add_attachment_ref")) {
    toolLines.push("- mindmap_add_attachment_ref：<mindmap_add_attachment_ref>{\"uid\":\"...\",\"attachmentId\":\"...\",\"page\":12,\"mode\":\"append\"}</mindmap_add_attachment_ref>");
  }
  if (allowedTools.has("mindmap_add_attachment_child")) {
    toolLines.push("- mindmap_add_attachment_child：<mindmap_add_attachment_child>{\"parentUid\":\"...\",\"attachmentId\":\"...\",\"page\":12}</mindmap_add_attachment_child>");
  }
  if (allowedTools.has("mindmap_add_image_child")) {
    toolLines.push("- mindmap_add_image_child：<mindmap_add_image_child>{\"parentUid\":\"...\",\"imageRef\":\"...\",\"caption\":\"...\"}</mindmap_add_image_child>");
  }
  if (allowedTools.has("mindmap_append_image_note")) {
    toolLines.push("- mindmap_append_image_note：<mindmap_append_image_note>{\"uid\":\"...\",\"imageRef\":\"...\"}</mindmap_append_image_note>");
  }
  if (allowedTools.has("mindmap_expand_node")) {
    toolLines.push("- mindmap_expand_node：<mindmap_expand_node>{\"targetUid\":\"...\",\"instruction\":\"...\"}</mindmap_expand_node>");
  }
  guideLines.push("- 先用只读工具定位（需要时再检索），再做最小范围写入。");
  if (allowedTools.has("search_web")) {
    guideLines.push("- 需要来源时先 search_web，再把 URL 放进最终回答或写入引用字段。");
  }
  if (allowedTools.has("rag_lightrag_query")) {
    guideLines.push("- 需要从本地知识库语义检索/汇总：优先 rag_lightrag_query；若还需要网页来源，再额外 search_web。");
  }
  if (allowedTools.has("docs_search") || allowedTools.has("docs_read")) {
    guideLines.push("- 跨页面找内容：先 docs_search 找到 documentId，再 docs_read 读取原文片段（需要时再到对应页面使用 doc_* 写入）。");
  }
  if (allowedTools.has("image_read")) {
    guideLines.push("- 需要读图：用 image_read 从 media_assets.ocr_text 获取文字（attachmentRef 可用附件 id/title/url 片段）。");
  }
  if (allowedTools.has("asset_extract_outline") || allowedTools.has("asset_to_mindmap")) {
    guideLines.push("- PDF→导图（M3）：先 asset_extract_outline 提取标题层级/页码，再根据需要调用 asset_to_mindmap 落盘到指定 mindmap。");
    guideLines.push("- 注意：asset_to_mindmap 是写工具，只有在用户明确要求“生成/写入思维导图”时才调用。");
  }
  if (allowedTools.has("slash_run")) {
    guideLines.push("- 需要创建/改名：用 slash_run 执行 /new 或 /rename（这是写工具，只有在用户明确要求时才调用）。");
  }
  if (
    allowedTools.has("doc_get") ||
    allowedTools.has("doc_find") ||
    allowedTools.has("doc_insert_blocks") ||
    allowedTools.has("doc_replace_range")
  ) {
    guideLines.push("- 写页面前：先 doc_get 或 doc_find 确认 blockId；再用 doc_* 写工具插入/替换。");
    guideLines.push("- doc_insert_blocks 适合新增标题/段落；doc_replace_range 适合改写某个段落/标题的文本。");
  }
  if (
    allowedTools.has("mindmap_get") ||
    allowedTools.has("mindmap_get_subtree") ||
    allowedTools.has("mindmap_apply_ops") ||
    allowedTools.has("mindmap_expand_node")
  ) {
    guideLines.push("- 思维导图小改动优先用细粒度 mindmap_* 写工具（add_child/update/set_hyperlink/append_note/set_refs 等）。");
    guideLines.push("- 只有当需要一次性执行多条混合操作时才用 mindmap_apply_ops。");
    guideLines.push("- 附件的 id 与 url 在“当前上下文”里（attachments 列表）。");
    guideLines.push("- 挂到已有节点：mindmap_add_attachment_ref；把附件变成节点：mindmap_add_attachment_child。");
    guideLines.push("- 插入图片：mindmap_add_image_child 或 mindmap_append_image_note（图片 URL 可来自 attachments 或 https 链接）。");
  }

  const hardRules: string[] = [];
  hardRules.push("- 当用户询问价格/版本/配置信息并要求“最新/准确/带来源”时：优先调用 search_web 获取来源，再回答。");
  hardRules.push("- 如果你调用了 search_web：最终回答需给出“来源”列表（至少 2 条 URL）；未调用 search_web 时不要编造来源。");
  hardRules.push("- 最终回答不要再输出任何工具标签。");
  if (allowedTools.has("slash_run")) {
    hardRules.push("- slash_run 属于写工具：只有在用户明确要求“创建/改名/执行斜杠命令”时才调用；否则不要擅自创建新文档。");
  }
  if (allowedTools.has("asset_to_mindmap")) {
    hardRules.push("- asset_to_mindmap 属于写工具：只有在用户明确要求“从附件生成/写入思维导图”时才调用；否则不要擅自写入导图。");
  }
  if (
    allowedTools.has("oo_replace_selection") ||
    allowedTools.has("oo_insert_text") ||
    allowedTools.has("oo_insert_html") ||
    allowedTools.has("oo_insert_image")
  ) {
    hardRules.push("- oo_* 属于写工具：只有在用户明确要求“修改/补全/插入 OnlyOffice 文档内容”时才调用；否则不要擅自改文档。");
    hardRules.push("- 删除选区：用 oo_replace_selection 且 text 传空字符串。");
  }
  if (allowedTools.has("mindmap_apply_ops") || allowedTools.has("mindmap_expand_node")) {
    hardRules.push("- 涉及思维导图写入时：只能通过 mindmap_* 写工具落盘；不要输出整棵树覆盖。");
  }
  if (allowedTools.has("doc_insert_blocks") || allowedTools.has("doc_replace_range")) {
    hardRules.push("- 涉及页面写入时：只能通过 doc_* 写工具落入页面；不要让用户复制粘贴，也不要输出整篇 blocks JSON 让用户手动替换。");
  }
  hardRules.push("- 若上下文包含 selectedUids：默认使用第一个 uid 作为目标节点（除非用户明确给出 targetUid）。");

  return [
    "你是一个“AI Agent”（类似 Cline/VSCode Chat）。你可以通过工具完成任务，并把步骤展示给用户。",
    systemContextText ? `\n当前上下文：\n${systemContextText.trim()}\n` : "",
    "",
    "工具调用协议（硬性）：",
    "1) 当你需要调用工具时，必须只输出一个工具标签（不要输出其它文字）。",
    "2) 标签格式：<tool_name>{...JSON...}</tool_name>，JSON 必须是严格 JSON（双引号）。",
    "3) 你将收到工具结果：<tool_result tool=\"tool_name\">{...JSON...}</tool_result>，再继续。",
    "",
    "工具列表（允许集）：",
    ...toolLines,
    "",
    "如何选择工具（建议）：",
    ...guideLines,
    "",
    "行为要求（硬性）：",
    ...hardRules,
  ].join("\n");
};

export const runAiAgent = async (args: RunAiAgentArgs): Promise<{ ok: true; text: string; steps: number } | { ok: false; error: string }> => {
  const maxSteps = Math.max(1, Math.min(24, Math.floor(args.maxSteps ?? DEFAULT_MAX_STEPS)));
  const allowedToolIds = args.allowedToolIds;
  const allowedToolNames = new Set<string>(Array.from(allowedToolIds));
  const chat = args.chat ?? openAiCompatibleChat;

  const messages: OpenAiCompatibleChatMessage[] = [
    { role: "system", content: buildSystemPrompt(allowedToolIds, args.systemContextText) },
    ...args.userMessages.map((m) => ({ role: m.role, content: m.content })),
  ];

  const emit = (type: string, data: unknown) => args.onEvent?.({ type, data });

  const stepId = (step: number) => `step_${step}_${Math.random().toString(16).slice(2, 10)}`;

  let steps = 0;
  let usedAnyTool = false;

  const latestUserQuestion =
    [...args.userMessages]
      .reverse()
      .find((m) => m.role === "user")
      ?.content?.trim() ?? "";

  const shouldForceSearchWeb = (question: string) => {
    const q = String(question || "").trim();
    if (!q) return false;
    // v1：非常粗粒度的启发式，避免“问价格/最新信息”却不检索
    return /价格|多少钱|价位|收费|pricing|price|token|tokens|最新版|最新|更新|版本|费率/i.test(q);
  };

  const looksLikeHasSources = (text: string) => {
    const s = String(text || "");
    const urls = s.match(/https?:\/\/\S+/g) ?? [];
    return urls.length >= 2;
  };

  const shouldForceMindmapExpand = (question: string) => {
    const q = String(question || "").trim();
    if (!q) return false;
    // v1：仅在明确“要改导图”的意图时才兜底触发写工具，避免误操作
    const wantsMindmap = /导图|思维导图|节点|子节点/i.test(q);
    const wantsWrite = /写入|保存|落盘|应用|修改|更新|补完|扩展|完善|新增|添加/i.test(q);
    return wantsMindmap && wantsWrite;
  };

  for (; steps < maxSteps; steps += 1) {
    const { text } = await chat(messages, args.cfg);
    const parsed = parseToolTagCalls(text, allowedToolNames);

    if (parsed.calls.length === 0) {
      const finalText = String(text ?? "").trim();

      // 若模型没调用工具且看起来也没给足来源，而问题又强依赖“最新/可追溯”，则自动补一次检索再让模型回答
      if (!usedAnyTool && allowedToolIds.has("search_web") && shouldForceSearchWeb(latestUserQuestion) && !looksLikeHasSources(finalText)) {
        const id = stepId(steps + 1);
        const tool = "search_web";
        const toolArgs = { query: latestUserQuestion, count: 6 };
        emit("tool_call", { id, tool, args: toolArgs });
        const t0 = nowMs();
        try {
          const result = await args.runTool(tool, toolArgs);
          const ms = nowMs() - t0;
          usedAnyTool = true;
          emit("tool_result", { id, tool, ok: true, ms, result });
          messages.push({ role: "assistant", content: `<${tool}>${JSON.stringify(toolArgs)}</${tool}>` });
          messages.push({ role: "user", content: formatToolResultTag(tool, JSON.stringify(result)) });
          continue;
        } catch (e) {
          const ms = nowMs() - t0;
          const msg = e instanceof Error ? e.message : String(e);
          usedAnyTool = true;
          emit("tool_result", { id, tool, ok: false, ms, result: { error: msg } });
          messages.push({ role: "user", content: formatToolResultTag(tool, JSON.stringify({ error: msg })) });
          continue;
        }
      }

      // 若用户明确要“补完/写入思维导图”，但模型没触发工具，则兜底执行一次 mindmap_expand_node
      if (
        !usedAnyTool &&
        allowedToolIds.has("mindmap_expand_node") &&
        args.defaultMindmapTargetUid &&
        shouldForceMindmapExpand(latestUserQuestion)
      ) {
        const id = stepId(steps + 1);
        const tool = "mindmap_expand_node";
        const toolArgs = { targetUid: args.defaultMindmapTargetUid, instruction: latestUserQuestion };
        emit("tool_call", { id, tool, args: toolArgs });
        const t0 = nowMs();
        try {
          const result = await args.runTool(tool, toolArgs);
          const ms = nowMs() - t0;
          usedAnyTool = true;
          emit("tool_result", { id, tool, ok: true, ms, result });
          messages.push({ role: "assistant", content: `<${tool}>${JSON.stringify(toolArgs)}</${tool}>` });
          messages.push({ role: "user", content: formatToolResultTag(tool, JSON.stringify(result)) });
          continue;
        } catch (e) {
          const ms = nowMs() - t0;
          const msg = e instanceof Error ? e.message : String(e);
          usedAnyTool = true;
          emit("tool_result", { id, tool, ok: false, ms, result: { error: msg } });
          messages.push({ role: "user", content: formatToolResultTag(tool, JSON.stringify({ error: msg })) });
          continue;
        }
      }

      emit("assistant_message", { text: finalText });
      return { ok: true, text: finalText, steps: steps + 1 };
    }

    // v1：一次只允许执行第一个工具调用，避免模型一口气输出多次工具导致不可控
    const call = parsed.calls[0];
    const tool = call.name;
    if (!allowedToolIds.has(tool)) {
      return { ok: false, error: `工具未被允许：${tool}` };
    }

    const toolArgs = safeParseJsonObject(call.rawInput) ?? {};
    const id = stepId(steps + 1);
    emit("tool_call", { id, tool, args: toolArgs });

    const t0 = nowMs();
    try {
      const result = await args.runTool(tool, toolArgs);
      const ms = nowMs() - t0;
      usedAnyTool = true;
      emit("tool_result", { id, tool, ok: true, ms, result });

      messages.push({ role: "assistant", content: `<${tool}>${JSON.stringify(toolArgs)}</${tool}>` });
      messages.push({ role: "user", content: formatToolResultTag(tool, JSON.stringify(result)) });
    } catch (e) {
      const ms = nowMs() - t0;
      const msg = e instanceof Error ? e.message : String(e);
      usedAnyTool = true;
      emit("tool_result", { id, tool, ok: false, ms, result: { error: msg } });
      messages.push({ role: "user", content: formatToolResultTag(tool, JSON.stringify({ error: msg })) });
    }
  }

  return { ok: false, error: `已达到最大步数（${maxSteps}）` };
};
