"use client";

import React, { useEffect, useMemo, useRef, useState } from "react";
import { Bot, Settings, Wrench } from "lucide-react";
import { Button } from "@/components/ui/button";
import { AiBridgePanel } from "@/components/ai-agent/AiBridgePanel";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Textarea } from "@/components/ui/textarea";
import { clamp } from "@/lib/constants";
import { useAppPreferencesStore } from "@/store/app-preferences";
import { useOnlyOfficeAiBridgeStore } from "@/store/onlyoffice-ai-bridge";
import { parseSseChunks, readAiPanelPrefs, type AiProvider, writeAiPanelPrefs } from "@/components/ai-agent/panelShared";

type AgentMessage = { role: "user" | "assistant"; content: string };

type ToolLog =
  | { type: "tool_call"; id: string; tool: string; args: Record<string, unknown> }
  | { type: "tool_result"; id: string; tool: string; ok: boolean; ms: number; result: unknown }
  | { type: "info"; message: string }
  | { type: "error"; message: string };

type PanelPage = "chat" | "tools" | "settings";

type AgentAttachment = { id: string; title: string; fileUrl: string; mimeType?: string | null };

const CHANNEL = "mnote_onlyoffice_agent_tools_v1";

const DEFAULT_MESSAGES: AgentMessage[] = [
  {
    role: "assistant",
    content:
      "你好，我是 OnlyOffice AI Agent。\n- 先选中一段文字，再说“改写/补全/翻译/润色/删除/插入”\n- 我会通过 oo_* 工具读取/替换选区\n- 需要引用资料时可联网检索或用 LightRAG/文档检索",
  },
];

const ONLINE_MODELS = [
  "",
  "gemini-2.5-flash",
  "gemini-2.5-pro",
  "gemini-3-pro-preview",
  "gemini-3-flash-preview",
] as const;

const OLLAMA_QWEN3_30B = "qwen3:30b-a3b-instruct-2507-q4_K_M";

const isRecord = (v: unknown): v is Record<string, unknown> =>
  typeof v === "object" && v !== null && !Array.isArray(v);

const blobToDataUrl = (blob: Blob) =>
  new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result ?? ""));
    reader.onerror = () => reject(new Error("读取图片失败（FileReader）"));
    reader.readAsDataURL(blob);
  });

export function OnlyOfficeAiAgentPanelRuntime({
  openFile,
  initialOpen = false,
}: {
  openFile: { id: string; title: string; fileUrl: string; mimeType?: string | null };
  initialOpen?: boolean;
}) {
  const flightMode = useAppPreferencesStore((s) => s.flightMode);
  const bridgePluginReady = useOnlyOfficeAiBridgeStore((s) => s.pluginReady);
  const bridgeTargetOrigin = useOnlyOfficeAiBridgeStore((s) => s.targetOrigin);
  const bridgeTargetWindow = useOnlyOfficeAiBridgeStore((s) => s.targetWindow);
  const [open, setOpen] = useState(initialOpen);
  const [page, setPage] = useState<PanelPage>("chat");

  const [messages, setMessages] = useState<AgentMessage[]>(() => DEFAULT_MESSAGES);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [toolLogs, setToolLogs] = useState<ToolLog[]>([]);

  const [networkOn, setNetworkOn] = useState(() => !flightMode);
  const [toolAuto, setToolAuto] = useState(true);
  const [aiProvider, setAiProvider] = useState<AiProvider>("online");
  const [aiModel, setAiModel] = useState<string>("");
  const [maxSteps, setMaxSteps] = useState<number>(10);

  // Codex：每个面板对话维护一个 session，支持连续对话与“暂停（类似 ESC）”
  const [codexSessionId, setCodexSessionId] = useState<string | null>(null);

  const abortRef = useRef<AbortController | null>(null);
  const pluginTargetRef = useRef<{ win: Window | null; origin: string }>({ win: null, origin: "*" });
  const pendingPluginCallsRef = useRef<
    Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timeoutId: number }>
  >(new Map());

  const attachments = useMemo<AgentAttachment[]>(
    () => [
      {
        id: openFile.id,
        title: openFile.title,
        fileUrl: openFile.fileUrl,
        mimeType: openFile.mimeType ?? null,
      },
    ],
    [openFile.fileUrl, openFile.id, openFile.mimeType, openFile.title],
  );

  useEffect(() => {
    if (flightMode) {
      setNetworkOn(false);
    }
  }, [flightMode]);

  useEffect(() => {
    const prefs = readAiPanelPrefs("onlyoffice_ai", { provider: "online", model: "", maxSteps: 10 });
    setAiProvider(prefs.provider);
    setAiModel(prefs.model);
    setMaxSteps(clamp(Math.floor(prefs.maxSteps), 1, 24));
  }, []);

  useEffect(() => {
    writeAiPanelPrefs("onlyoffice_ai", {
      provider: aiProvider,
      model: aiModel,
      maxSteps: clamp(Math.floor(maxSteps), 1, 24),
    });
  }, [aiProvider, aiModel, maxSteps]);

  useEffect(() => {
    pluginTargetRef.current = {
      win: bridgeTargetWindow,
      origin: bridgeTargetOrigin || "*",
    };
  }, [bridgeTargetOrigin, bridgeTargetWindow]);

  useEffect(() => {
    const onMessage = (ev: MessageEvent) => {
      const data = ev.data as unknown;
      if (!isRecord(data)) return;
      if (data.channel !== CHANNEL) return;

      const type = String(data.type ?? "").trim();
      if (type === "ready") {
        return;
      }

      if (type === "result") {
        const callId = String(data.callId ?? "").trim();
        if (!callId) return;
        const pending = pendingPluginCallsRef.current.get(callId);
        if (!pending) return;
        pendingPluginCallsRef.current.delete(callId);
        window.clearTimeout(pending.timeoutId);

        const ok = Boolean(data.ok);
        if (ok) {
          pending.resolve("result" in data ? (data as Record<string, unknown>).result : null);
        } else {
          pending.reject(new Error(String((data as Record<string, unknown>).error ?? "插件执行失败")));
        }
      }
    };

    window.addEventListener("message", onMessage);
    return () => window.removeEventListener("message", onMessage);
  }, []);

  useEffect(() => {
    const pendingPluginCalls = pendingPluginCallsRef.current;
    return () => {
      abortRef.current?.abort();
      abortRef.current = null;
      pendingPluginCalls.forEach(({ reject, timeoutId }) => {
        window.clearTimeout(timeoutId);
        reject(new Error("OnlyOffice AI 面板已卸载"));
      });
      pendingPluginCalls.clear();
    };
  }, []);

  const callPlugin = async (callId: string, tool: string, args: Record<string, unknown>) => {
    const target = pluginTargetRef.current;
    if (!target.win) throw new Error("插件未就绪（未收到 ready），请稍等或刷新文档");

    const payload = { channel: CHANNEL, type: "call", callId, tool, args };
    const result = await new Promise<unknown>((resolve, reject) => {
      const timeoutId = window.setTimeout(() => {
        pendingPluginCallsRef.current.delete(callId);
        reject(new Error("插件调用超时"));
      }, 60_000);

      pendingPluginCallsRef.current.set(callId, { resolve, reject, timeoutId });
      try {
        target.win!.postMessage(payload, target.origin || "*");
      } catch (e) {
        window.clearTimeout(timeoutId);
        pendingPluginCallsRef.current.delete(callId);
        reject(e instanceof Error ? e : new Error(String(e)));
      }
    });
    return result;
  };

  const postClientToolResult = async ({
    requestId,
    callId,
    ok,
    result,
    error,
  }: {
    requestId: string;
    callId: string;
    ok: boolean;
    result?: unknown;
    error?: string;
  }) => {
    await fetch("/api/ai-agent/client-tool-result", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ requestId, callId, ok, result, error }),
    });
  };

  const resolveImageRefToDataUrl = async (imageRef: string) => {
    const s = String(imageRef ?? "").trim();
    if (!s) throw new Error("缺少 imageRef");

    // 1) 优先当作附件 id
    const match = attachments.find((a) => a.id === s) ?? null;
    const url = match ? match.fileUrl : s;

    const res = await fetch(url);
    if (!res.ok) throw new Error(`图片下载失败：HTTP ${res.status}`);
    const blob = await res.blob();
    return await blobToDataUrl(blob);
  };

  const handleClientToolCall = async (payloadText: string) => {
    let data: unknown = null;
    try {
      data = JSON.parse(payloadText || "null");
    } catch {
      return;
    }
    const obj = isRecord(data) ? data : ({} as Record<string, unknown>);
    const requestId = String(obj.requestId ?? "").trim();
    const callId = String(obj.callId ?? "").trim();
    const tool = String(obj.tool ?? "").trim();
    const args = isRecord(obj.args) ? (obj.args as Record<string, unknown>) : {};
    if (!requestId || !callId || !tool) return;

    try {
      let result: unknown = null;
      if (tool === "oo_insert_image") {
        const imageRef = String(args.imageRef ?? "").trim();
        const src = await resolveImageRefToDataUrl(imageRef);
        const width = Number(args.width ?? 0);
        const height = Number(args.height ?? 0);
        result = await callPlugin(callId, tool, { ...args, src, width, height });
      } else {
        result = await callPlugin(callId, tool, args);
      }
      await postClientToolResult({ requestId, callId, ok: true, result });
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      await postClientToolResult({ requestId, callId, ok: false, error: msg });
    }
  };

  const stop = () => {
    abortRef.current?.abort();
    abortRef.current = null;
    setLoading(false);
    if (aiProvider === "codex") {
      setToolLogs((prev) => [...prev, { type: "info", message: "已暂停（可继续对话）" }]);
    }
  };

  const send = async () => {
    const text = input.trim();
    if (!text) return;
    if (loading) return;

    const codexSessionIdForRequest =
      aiProvider === "codex" ? String(codexSessionId ?? "").trim() || null : null;

    const nextMessages: AgentMessage[] = [...messages, { role: "user", content: text }];
    setMessages(nextMessages);
    setInput("");
    setLoading(true);
    setToolLogs([]);

    const controller = new AbortController();
    abortRef.current = controller;

    try {
      const res = await fetch("/api/ai-agent/run", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        signal: controller.signal,
        body: JSON.stringify({
          stream: true,
          maxSteps,
          scope: "onlyoffice",
          messages: nextMessages.slice(-20),
          attachments,
          toolChoice: {
            mode: toolAuto ? "auto" : "manual",
            toolSets: [
              "toolset.readonly",
              "toolset.rag_read",
              "toolset.media_read",
              "toolset.docs_read",
              "toolset.onlyoffice_editor",
            ],
          },
          options: {
            searxng: networkOn,
            ai: {
              provider: aiProvider,
              ...(aiProvider === "codex" && codexSessionIdForRequest ? { sessionId: codexSessionIdForRequest } : {}),
              ...(aiProvider !== "codex" && String(aiModel || "").trim() ? { model: String(aiModel || "").trim() } : {}),
            },
          },
        }),
      });
      if (!res.ok) {
        const j = (await res.json().catch(() => null)) as unknown;
        const err =
          typeof j === "object" && j && "error" in j ? String((j as Record<string, unknown>).error ?? "") : "";
        throw new Error(err || `HTTP ${res.status}`);
      }

      await parseSseChunks(res, (event, dataText) => {
        if (event === "codex_session") {
          try {
            const d = JSON.parse(dataText || "null") as unknown;
            const obj = isRecord(d) ? d : ({} as Record<string, unknown>);
            const sid = String(obj.sessionId ?? "").trim();
            if (sid) {
              setCodexSessionId(sid);
            }
          } catch {
            // ignore
          }
          return;
        }
        if (event === "assistant_message") {
          try {
            const d = JSON.parse(dataText || "null") as unknown;
            const t = isRecord(d) && "text" in d ? String(d.text ?? "") : "";
            if (t) setMessages((prev) => [...prev, { role: "assistant", content: t }]);
          } catch {
            // ignore
          }
          return;
        }

        if (event === "client_tool_call") {
          void handleClientToolCall(dataText);
          return;
        }

        if (event === "tool_call") {
          try {
            const d = JSON.parse(dataText || "null") as unknown;
            const obj = isRecord(d) ? d : ({} as Record<string, unknown>);
            setToolLogs((prev) => [
              ...prev,
              {
                type: "tool_call",
                id: String(obj.id ?? ""),
                tool: String(obj.tool ?? ""),
                args: (isRecord(obj.args) ? obj.args : {}) as Record<string, unknown>,
              },
            ]);
          } catch {
            // ignore
          }
          return;
        }

        if (event === "tool_result") {
          try {
            const d = JSON.parse(dataText || "null") as unknown;
            const obj = isRecord(d) ? d : ({} as Record<string, unknown>);
            setToolLogs((prev) => [
              ...prev,
              {
                type: "tool_result",
                id: String(obj.id ?? ""),
                tool: String(obj.tool ?? ""),
                ok: Boolean(obj.ok),
                ms: Number(obj.ms ?? 0),
                result: "result" in obj ? obj.result : null,
              },
            ]);
          } catch {
            // ignore
          }
          return;
        }

        if (event === "error") {
          if (controller.signal.aborted && aiProvider === "codex") return;
          try {
            const d = JSON.parse(dataText || "null") as unknown;
            const msg = isRecord(d) && "message" in d ? String(d.message ?? "") : "";
            setToolLogs((prev) => [...prev, { type: "error", message: msg || "未知错误" }]);
          } catch {
            setToolLogs((prev) => [...prev, { type: "error", message: "未知错误" }]);
          }
          return;
        }
      });
    } catch (e) {
      if (controller.signal.aborted && aiProvider === "codex") return;
      const msg = e instanceof Error ? e.message : String(e);
      setToolLogs((prev) => [...prev, { type: "error", message: msg }]);
    } finally {
      abortRef.current = null;
      setLoading(false);
    }
  };

  const canSend = useMemo(() => input.trim().length > 0 && !loading, [input, loading]);

  return (
    <>
      <div className="fixed bottom-4 right-4 z-[60]">
        <Button
          className="shadow"
          onClick={() => {
            setOpen((v) => !v);
            if (!open) setPage("chat");
          }}
        >
          <Bot className="mr-2 h-4 w-4" />
          AI
        </Button>
      </div>

      {open ? (
        <div className="fixed inset-y-0 right-0 z-[70] w-[min(960px,calc(100vw-24px))] p-3">
          <AiBridgePanel
            title={page === "chat" ? "OnlyOffice AI" : page === "tools" ? "OnlyOffice 工具" : "OnlyOffice 设置"}
            subtitle="OnlyOffice AI"
            status={loading ? "运行中" : "待命"}
            onClose={() => setOpen(false)}
            scrollBody={false}
            className="h-[calc(100vh-24px)] min-h-0 rounded-[24px] border-white/10 shadow-none"
            secondaryActions={
              <>
                <Button
                  variant="ghost"
                  size="icon-sm"
                  onClick={() => setPage("chat")}
                  className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
                >
                  <Bot className="h-4 w-4" />
                </Button>
                <Button
                  variant="ghost"
                  size="icon-sm"
                  onClick={() => setPage("tools")}
                  className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
                >
                  <Wrench className="h-4 w-4" />
                </Button>
                <Button
                  variant="ghost"
                  size="icon-sm"
                  onClick={() => setPage("settings")}
                  className="rounded-xl border border-white/10 bg-white/[0.03] text-white/80 hover:bg-white/[0.08] hover:text-white"
                >
                  <Settings className="h-4 w-4" />
                </Button>
              </>
            }
          >
          {page === "tools" ? (
            <div className="space-y-3 p-3 text-sm">
              <div className="flex items-center justify-between">
                <div className="font-medium">联网检索</div>
                <input
                  type="checkbox"
                  checked={networkOn}
                  onChange={(e) => setNetworkOn(e.target.checked)}
                  disabled={loading}
                />
              </div>
              <div className="flex items-center justify-between">
                <div className="font-medium">自动工具</div>
                <input
                  type="checkbox"
                  checked={toolAuto}
                  onChange={(e) => setToolAuto(e.target.checked)}
                  disabled={loading}
                />
              </div>
              <div className="rounded border p-2 text-xs text-muted-foreground">
                <div>插件状态：{bridgePluginReady ? "已连接" : "未连接（等待 ready）"}</div>
                <div>说明：oo_* 工具依赖该插件执行“选区读写”。</div>
              </div>

              <details open className="rounded border p-2">
                <summary className="cursor-pointer select-none text-sm font-medium">工具日志（可折叠）</summary>
                <div className="mt-2 space-y-2">
                  {toolLogs.length === 0 ? <div className="text-muted-foreground">暂无工具日志</div> : null}
                  {toolLogs.map((l, idx) => {
                    if (l.type === "error") {
                      return (
                        <div key={idx} className="rounded border border-red-200 bg-red-50 p-2 text-red-700">
                          错误：{l.message}
                        </div>
                      );
                    }
                    if (l.type === "info") {
                      return (
                        <div key={idx} className="rounded border bg-muted p-2 text-muted-foreground">
                          {l.message}
                        </div>
                      );
                    }
                    if (l.type === "tool_call") {
                      return (
                        <div key={idx} className="rounded border p-2">
                          <div className="text-xs text-muted-foreground">tool_call · {l.id}</div>
                          <div className="font-medium">{l.tool}</div>
                          <pre className="mt-1 whitespace-pre-wrap text-xs">{JSON.stringify(l.args, null, 2)}</pre>
                        </div>
                      );
                    }
                    return (
                      <div key={idx} className="rounded border p-2">
                        <div className="text-xs text-muted-foreground">
                          tool_result · {l.id} · {l.ok ? "ok" : "fail"} · {l.ms}ms
                        </div>
                        <div className="font-medium">{l.tool}</div>
                        <pre className="mt-1 whitespace-pre-wrap text-xs">{JSON.stringify(l.result, null, 2)}</pre>
                      </div>
                    );
                  })}
                </div>
              </details>
            </div>
          ) : null}

          {page === "settings" ? (
            <div className="space-y-3 p-3 text-sm">
              <label className="flex items-center justify-between gap-2">
                <span className="font-medium">最大步数</span>
                <input
                  className="w-[96px] rounded border px-2 py-1 text-xs"
                  type="number"
                  min={1}
                  max={24}
                  step={1}
                  value={maxSteps}
                  onChange={(e) => {
                    const v = Number(e.target.value);
                    if (!Number.isFinite(v)) return;
                    setMaxSteps(clamp(Math.floor(v), 1, 24));
                  }}
                  disabled={loading}
                />
              </label>

              <label className="flex items-center justify-between gap-2">
                <span className="font-medium">AI 提供方</span>
                <select
                  className="rounded border px-2 py-1 text-xs"
                  value={aiProvider}
                  onChange={(e) => {
                    const v = String(e.target.value || "").trim();
                    if (v === "online" || v === "local" || v === "ollama" || v === "codex") setAiProvider(v);
                    else setAiProvider("online");
                  }}
                  disabled={loading}
                >
                  <option value="online">在线</option>
                  <option value="local">本地</option>
                  <option value="ollama">Ollama</option>
                  <option value="codex">Codex</option>
                </select>
              </label>

              <label className="flex items-center justify-between gap-2">
                <span className="font-medium">模型</span>
                {aiProvider === "codex" ? (
                  <div className="text-xs text-muted-foreground">
                    消息开头加 <code className="rounded bg-muted px-1 py-0.5">#chat</code> /{" "}
                    <code className="rounded bg-muted px-1 py-0.5">#test</code> /{" "}
                    <code className="rounded bg-muted px-1 py-0.5">#dev</code>（默认 <code className="rounded bg-muted px-1 py-0.5">#chat</code>）。
                  </div>
                ) : aiProvider === "online" ? (
                  <select
                    className="w-[220px] rounded border px-2 py-1 text-xs"
                    value={ONLINE_MODELS.includes(aiModel as any) ? aiModel : ""}
                    onChange={(e) => setAiModel(e.target.value)}
                    disabled={loading}
                  >
                    {ONLINE_MODELS.map((m) => (
                      <option key={m} value={m}>
                        {m || "默认"}
                      </option>
                    ))}
                  </select>
                ) : aiProvider === "ollama" ? (
                  <select
                    className="w-[320px] rounded border px-2 py-1 text-xs"
                    value={aiModel === "" || aiModel === OLLAMA_QWEN3_30B ? aiModel : ""}
                    onChange={(e) => setAiModel(e.target.value)}
                    disabled={loading}
                  >
                    <option value="">默认：{OLLAMA_QWEN3_30B}</option>
                    <option value={OLLAMA_QWEN3_30B}>{OLLAMA_QWEN3_30B}</option>
                  </select>
                ) : (
                  <input
                    className="w-[320px] rounded border px-2 py-1 text-xs"
                    value={aiModel}
                    onChange={(e) => setAiModel(e.target.value)}
                    placeholder="默认（ai.local.md/环境变量）"
                    disabled={loading}
                    list="oo-local-model-suggestions"
                  />
                )}
              </label>
              <datalist id="oo-local-model-suggestions">
                <option value={OLLAMA_QWEN3_30B} />
              </datalist>
            </div>
          ) : null}

          {page === "chat" ? (
            <div className="flex h-[calc(100vh-96px)] flex-col">
              <ScrollArea className="flex-1">
                <div className="space-y-3 p-3 text-sm">
                  {messages.map((m, idx) => (
                    <div key={idx} className="space-y-1">
                      <div className="text-xs text-muted-foreground">{m.role === "user" ? "用户" : "AI"}</div>
                      <div className="whitespace-pre-wrap">{m.content}</div>
                    </div>
                  ))}
                </div>
              </ScrollArea>

              <div className="border-t p-3">
                <div className="flex gap-2">
                  <Textarea
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    placeholder="输入你的需求（Enter 发送，Shift+Enter 换行）"
                    className="min-h-[72px] flex-1"
                    onKeyDown={(e) => {
                      if (e.key === "Enter" && !e.shiftKey) {
                        e.preventDefault();
                        if (canSend) void send();
                      }
                    }}
                  />
                  <div className="flex flex-col gap-2">
                    <Button disabled={!canSend} onClick={() => void send()}>
                      发送
                    </Button>
                    <Button variant="secondary" disabled={!loading} onClick={stop}>
                      {aiProvider === "codex" ? "暂停" : "停止"}
                    </Button>
                  </div>
                </div>
              </div>
            </div>
          ) : null}
          </AiBridgePanel>
        </div>
      ) : null}
    </>
  );
}

export { OnlyOfficeAiAgentPanelRuntime as OnlyOfficeAiAgentPanel };
