'use client';

import { useRef, useState } from 'react';
import { env } from '@/lib/env';
import { useAuthStore } from '@/stores/auth-store';
import { useAssistantStore } from '@/stores/assistant-store';

/**
 * 🔬 裸 agent 试验台 `/assistant-lab`（2026-08-14）。
 *
 * 看模型的**原生能力**：边写正文边调工具，把按钮和卡片摆在它写到的位置。
 * ⛔ 这里没有任何补偿 —— 不重排、不补标题、不兜底、不改历史。
 *   模型漏调 `place_*`，页面上就真的没有那个标记；那正是要观察的东西。
 *
 * ⚠️ 与产品助手**没有代码关联**：不 import 它的块模型、不复用它的排版。
 * ⚠️ 历史是服务端每轮原样吐回来的 `messages`（含真的 tool-call / tool-result），
 *   这里**原样存、原样回传** —— 前端一行重建逻辑都没有。
 * ⚠️ 「执行过程」是**纯 debug 通道**（`step` 事件），⛔ 不进 messages、⛔ 不回传给模型。
 */

type Part =
  | { k: 'text'; text: string }
  /** 🔬 思考 —— 跟正文走同一条流，按到达顺序穿插；显示上区分，⛔ 不改变它的位置 */
  | { k: 'reasoning'; text: string }
  /**
   * 🔬 工具调用 —— 也按到达顺序**穿插在正文里**，这样一眼能看出时序：
   *   想 → 写 → 调 → 写 → 调…
   * ⚠️ `show_sheet` / `show_guidance` **不出 chip**：它们已经有绿框了，
   *   而且绿框（execute 里发的）比 step 事件（fullStream 里转的）先到，
   *   两个都画会出现"框在自己的调用记录前面"的错觉。
   */
  | { k: 'step'; id: string; tool: string; args?: unknown; result?: unknown; error?: string }
  | { k: 'place'; what: 'sheet' | 'guidance'; index?: number; kind?: string; title?: string }
  | { k: 'sheet'; summary: Record<string, number> };

interface Step {
  id: string;
  tool: string;
  args?: unknown;
  result?: unknown;
  error?: string;
}

interface Turn {
  ask: string;
  parts: Part[];
  steps: Step[];
  limit?: number;
  err?: string;
}

/** 服务端原样吐回来的 AI SDK 消息 —— ⛔ 前端不解读、不改写，只存和回传。 */
type ApiMessage = Record<string, unknown>;

/** 这两个已经有绿框代表位置了，⛔ 不再出 chip（见 Part 里 `step` 那段） */
const PLACE_TOOLS = new Set(['show_sheet', 'show_guidance']);

const PRESETS = [
  '帮我给「拔牙 · 3 年以上」这批患者出一份分配方案',
  '帮我给「种植 · 2−3 年」这批患者出一份分配方案',
  '这批只圈消费高的那些，重新排一版',
];

export default function AssistantLabPage() {
  const [turns, setTurns] = useState<Turn[]>([]);
  const [input, setInput] = useState(PRESETS[0]!);
  const [busy, setBusy] = useState(false);
  const [model, setModel] = useState('deepseek');
  /**
   * 🔬 qwen 的思考开关（deepseek / gemini 本来就开着，勾不勾都一样）。
   *
   * 共用的 provider 里有个 fetch 中间件**无条件**给 qwen 塞 `enable_thinking=false`，
   * 连 agent 这条带工具的链路也塞 —— 这几天拿 qwen 跟 DeepSeek 比工具调用，
   * 其实是「便宜档 + 会思考」对「最高档 + 不许思考」。
   */
  const [thinking, setThinking] = useState(true);
  /** 全部历史（服务端原样给的）+ 我们插进去的 user 消息 */
  const history = useRef<ApiMessage[]>([]);

  const send = async (): Promise<void> => {
    const ask = input.trim();
    if (!ask || busy) return;
    setBusy(true);
    setInput('');
    history.current = [...history.current, { role: 'user', content: ask }];
    const at = turns.length;
    setTurns((t) => [...t, { ask, parts: [], steps: [] }]);

    const patch = (fn: (t: Turn) => Turn): void =>
      setTurns((prev) => prev.map((t, i) => (i === at ? fn(t) : t)));

    try {
      const res = await fetch(new URL('/pac/v1/assistant/lab/chat', env.apiBaseUrl), {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'text/event-stream',
          ...(useAuthStore.getState().accessToken
            ? { Authorization: `Bearer ${useAuthStore.getState().accessToken}` }
            : {}),
        },
        body: JSON.stringify({
          messages: history.current,
          model,
          thinking,
          ...(useAssistantStore.getState().activeClinicId
            ? { activeClinicId: useAssistantStore.getState().activeClinicId }
            : {}),
        }),
      });
      if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);

      let buf = '';
      const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
      for (;;) {
        const { value, done } = await reader.read();
        if (done) break;
        buf += value;
        let sep: number;
        while ((sep = buf.indexOf('\n\n')) !== -1) {
          const frame = buf.slice(0, sep);
          buf = buf.slice(sep + 2);
          const line = frame.split('\n').find((l) => l.startsWith('data:'));
          if (!line) continue;
          const e = JSON.parse(line.slice(5).trim()) as Record<string, unknown>;
          switch (e.type) {
            case 'text':
            case 'reasoning': {
              // ⚠️ 追加到**最后一个同类** part；被别的东西打断就新起一段。
              //   这就是"位置"的全部实现 —— ⛔ 不做任何重排。
              const k = e.type === 'reasoning' ? ('reasoning' as const) : ('text' as const);
              patch((t) => {
                const last = t.parts[t.parts.length - 1];
                if (last?.k === k) {
                  return {
                    ...t,
                    parts: [...t.parts.slice(0, -1), { k, text: last.text + String(e.text) }],
                  };
                }
                return { ...t, parts: [...t.parts, { k, text: String(e.text) }] };
              });
              break;
            }
            case 'place':
              patch((t) => ({
                ...t,
                parts: [
                  ...t.parts,
                  {
                    k: 'place',
                    what: e.what as 'sheet' | 'guidance',
                    ...(typeof e.index === 'number' ? { index: e.index } : {}),
                    ...(e.kind ? { kind: String(e.kind) } : {}),
                    ...(e.title ? { title: String(e.title) } : {}),
                  },
                ],
              }));
              break;
            case 'sheet_ready':
              patch((t) => ({
                ...t,
                parts: [...t.parts, { k: 'sheet', summary: e.summary as Record<string, number> }],
              }));
              break;
            case 'step': {
              const id = String(e.id);
              const tool = String(e.tool);
              patch((t) => {
                // ① 计数用的扁平表（底部那行「N 步」）—— 所有工具都算，含 show_*
                const i = t.steps.findIndex((s) => s.id === id);
                const merged: Step = i === -1 ? { id, tool } : { ...t.steps[i]! };
                if (e.phase === 'call') merged.args = e.args;
                if (e.phase === 'result') merged.result = e.result;
                if (e.phase === 'error') merged.error = String(e.error);
                const steps =
                  i === -1 ? [...t.steps, merged] : t.steps.map((s, j) => (j === i ? merged : s));

                // ② 穿插进正文的 chip —— ⚠️ show_* 跳过（它们已经有绿框，见 Part 的注释）
                if (PLACE_TOOLS.has(tool)) return { ...t, steps };
                const at = t.parts.findIndex((p) => p.k === 'step' && p.id === id);
                if (at === -1) {
                  return { ...t, steps, parts: [...t.parts, { k: 'step', id, tool, args: e.args }] };
                }
                const parts = t.parts.map((p, j) =>
                  j === at && p.k === 'step'
                    ? {
                        ...p,
                        ...(e.phase === 'result' ? { result: e.result } : {}),
                        ...(e.phase === 'error' ? { error: String(e.error) } : {}),
                      }
                    : p,
                );
                return { ...t, steps, parts };
              });
              break;
            }
            case 'step_limit':
              patch((t) => ({ ...t, limit: Number(e.limit) }));
              break;
            case 'messages':
              // ⭐ 原样存下来，下一轮原样回传。⛔ 不解读、不裁剪、不排序。
              history.current = [...history.current, ...(e.messages as ApiMessage[])];
              break;
            case 'error':
              patch((t) => ({ ...t, err: String(e.error) }));
              break;
            default:
              break;
          }
        }
      }
    } catch (err) {
      patch((t) => ({ ...t, err: err instanceof Error ? err.message : String(err) }));
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="mx-auto min-h-screen max-w-3xl bg-white p-6 text-[14px] text-slate-800">
      <header className="mb-4 flex items-center gap-3 border-b pb-3">
        <h1 className="text-base font-semibold">🔬 裸 agent 试验台</h1>
        <span className="text-xs text-slate-400">/assistant-lab · 无任何补偿</span>
        <select
          className="ml-auto rounded border px-2 py-1 text-xs"
          value={model}
          onChange={(ev) => setModel(ev.target.value)}
        >
          <option value="deepseek">deepseek（AI_DEFAULT_MODEL）</option>
          <option value="deepseek-v4-pro">deepseek-v4-pro</option>
          <option value="deepseek-v4-flash">deepseek-v4-flash</option>
          <option value="qwen">qwen</option>
        </select>
        <label className="flex items-center gap-1 text-xs text-slate-500" title="只对 qwen 有效">
          <input
            type="checkbox"
            checked={thinking}
            onChange={(ev) => setThinking(ev.target.checked)}
          />
          思考
        </label>
        <button
          className="rounded border px-2 py-1 text-xs text-slate-500"
          onClick={() => {
            history.current = [];
            setTurns([]);
          }}
        >
          清空会话
        </button>
      </header>

      {turns.map((t, i) => (
        <section key={i} className="mb-8">
          <div className="mb-3 text-right">
            <span className="inline-block rounded-lg bg-slate-800 px-3 py-1.5 text-[13px] text-white">
              {t.ask}
            </span>
          </div>

          {/* 正文与标记 —— **按到达顺序**，⛔ 不重排 */}
          {t.parts.map((p, j) =>
            p.k === 'text' ? (
              <p key={j} className="whitespace-pre-wrap leading-relaxed">
                {p.text}
              </p>
            ) : p.k === 'reasoning' ? (
              // 🔬 思考：默认折叠成一行，展开看全文。位置**就是它到达的位置**，⛔ 不挪。
              <details key={j} className="my-2 border-l-2 border-violet-300 bg-violet-50/40 pl-3">
                <summary className="cursor-pointer py-1 text-xs text-violet-500">
                  思考 · {p.text.length} 字
                </summary>
                <p className="whitespace-pre-wrap py-1 text-[12px] leading-relaxed text-violet-900/70">
                  {p.text}
                </p>
              </details>
            ) : p.k === 'step' ? (
              // 🔬 工具调用 chip —— 收起时一行，展开看入参和返回值
              <details key={j} className="my-1.5 rounded bg-slate-50 px-2 py-1">
                <summary className="cursor-pointer font-mono text-[11px] text-slate-500">
                  {p.tool}
                  {p.error && <span className="ml-2 text-red-600">✗ {p.error}</span>}
                  {!p.error && p.result === undefined && <span className="ml-2 text-slate-400">…</span>}
                </summary>
                <pre className="mt-1 overflow-x-auto whitespace-pre-wrap break-all text-[11px] text-slate-500">
                  {JSON.stringify(p.args ?? {})}
                </pre>
                {p.result != null && (
                  <pre className="mt-1 max-h-64 overflow-auto whitespace-pre-wrap break-all text-[11px] text-slate-400">
                    {typeof p.result === 'string' ? p.result : JSON.stringify(p.result, null, 2)}
                  </pre>
                )}
              </details>
            ) : p.k === 'sheet' ? (
              <div key={j} className="my-2 rounded border border-dashed border-slate-300 px-3 py-2 text-xs text-slate-500">
                确认单已生成（{Object.entries(p.summary).map(([k, v]) => `${k} ${v}`).join(' · ')}）
                <span className="ml-2 text-slate-400">— 还没摆位置</span>
              </div>
            ) : (
              <div
                key={j}
                className="my-2 rounded border-2 border-emerald-400 bg-emerald-50 px-3 py-1.5 text-xs text-emerald-800"
              >
                {p.what === 'sheet'
                  ? '【确认单卡片摆在这里】'
                  : `【「${p.kind ?? p.index}」的按钮摆在这里】${p.title ? ` ${p.title}` : ''}`}
              </div>
            ),
          )}

          {t.limit != null && (
            <p className="my-2 text-xs text-amber-700">⚠️ 工具步数到顶（{t.limit}），本轮被截断</p>
          )}
          {t.err && <p className="my-2 text-xs text-red-600">✗ {t.err}</p>}

          {/**
           * 执行过程 —— 现在只剩**一行计数**：明细已经按时序穿插在正文里了（chip / 绿框）。
           * ⛔ 别把明细搬回来：两份一样的东西，改一处漏一处。
           * ⚠️ 计数含 `show_*`（它们在正文里以绿框出现，不出 chip），所以这个数会比 chip 多。
           */}
          {t.steps.length > 0 && (
            <p className="mt-3 text-xs text-slate-400">
              执行过程 · {t.steps.length} 步（{t.steps.map((s) => s.tool).join(' → ')}）
            </p>
          )}
        </section>
      ))}

      <div className="sticky bottom-0 bg-white pt-2">
        <div className="mb-2 flex flex-wrap gap-1.5">
          {PRESETS.map((p) => (
            <button
              key={p}
              className="rounded border px-2 py-1 text-[11px] text-slate-500"
              onClick={() => setInput(p)}
            >
              {p}
            </button>
          ))}
        </div>
        <div className="flex gap-2">
          <input
            className="flex-1 rounded border px-3 py-2 text-[13px]"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Enter' && !e.shiftKey) void send();
            }}
            placeholder="帮我给「拔牙 · 3 年以上」这批患者出一份分配方案"
          />
          <button
            className="rounded bg-slate-800 px-4 py-2 text-[13px] text-white disabled:opacity-40"
            disabled={busy}
            onClick={() => void send()}
          >
            {busy ? '…' : '发送'}
          </button>
        </div>
      </div>
    </div>
  );
}
