"use client";

import { ArrowUp, Check, Copy, RotateCcw, ThumbsDown, ThumbsUp } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { parseAgentReplyBody } from "@/lib/agent-reply";
import type { ChatMessage, ChatMessageView } from "@/lib/chat-message-view";
import {
  createRectificationActivityReceiptState,
  receiptFromRectificationActivityState,
  reduceRectificationActivityReceipt,
  type CompletedActivityReceiptView,
} from "@/lib/rectification-activity-receipt";
import {
  isRecommendedRectificationCandidate,
  parseRectificationCandidateResult,
  type RectificationCandidateResult,
} from "@/lib/rectification-candidate-result";
import { membershipHref } from "@/lib/membership";
import {
  isPublicRectificationMethod,
  isPublicRectificationTool,
  type PublicRectificationTool,
} from "@/lib/rectification-agentic/v9/public-receipt";
import type { PublicLanguageModel } from "@/lib/public-models";
import { ChatMessageRow } from "./chat-message-row";
import { CompletedActivityReceipt } from "./completed-activity-receipt";
import { ModelSelector } from "./model-selector";
import { Button } from "./ui/button";
import { Textarea } from "./ui/textarea";

type PersistedTurn = Readonly<{
  id: string;
  role: "user" | "assistant";
  text: string | null;
  status: string;
  receipt?: Readonly<{
    status: string;
    phases: readonly string[];
    tools: readonly string[];
    methods?: readonly string[];
    skill_name?: string;
    skill_version?: string;
  }> | null;
}>;

type CandidateResult = RectificationCandidateResult | null;

type RectificationAgenticChatProps = Readonly<{
  caseId: string;
  sessionId: string;
  readonly: boolean;
  shouldStartOpening: boolean;
  initialTurns: readonly PersistedTurn[];
  models: readonly PublicLanguageModel[];
  selectedModelId: string;
  onSelectModel: (modelId: string) => void;
  onMessagesChange?: (messages: ChatMessage[]) => void;
  onCompleted?: () => void;
  onPendingChange?: (pending: boolean) => void;
  onProfileIncomplete?: () => void;
  onSaved?: (time: string, status: "accepted" | "confirmed") => void;
  pendingConsultationQuestion?: string | null;
  onRestart?: () => void;
}>;

type RenderMessage = ChatMessageView & {
  renderKey: string;
  activeActivity?: PublicActivity;
  completedReceipt?: CompletedActivityReceiptView;
  turnId?: string;
};

type PublicActivity = Readonly<{
  tool: PublicRectificationTool;
  label: string;
}>;

const ACTIVE_TOOL_LABELS: Readonly<Record<PublicRectificationTool, string>> = {
  "rectification-read-case": "正在读取校正记录…",
  "rectification-propose-evidence": "正在整理事件证据…",
  "rectification-confirm-evidence": "正在确认事件证据…",
  "rectification-revise-evidence": "正在修订事件证据…",
  "rectification-compare-candidates": "正在比较候选时间…",
  "rectification-read-diagnostics": "正在检查候选稳健性…",
  "rectification-offer-candidates": "正在生成候选建议…",
  "rectification-accept-candidate": "正在采用候选时间…",
  "rectification-confirm-birth-time": "正在确认校正时间…",
  "rectification-close-case": "正在完成校正记录…",
};

function completedReceiptFromPersisted(receipt: PersistedTurn["receipt"]): CompletedActivityReceiptView {
  if (!receipt) return { steps: [], methods: [] };
  return {
    steps: [...new Set((receipt.tools ?? []).filter(isPublicRectificationTool))],
    methods: [...new Set((receipt.methods ?? []).filter(isPublicRectificationMethod))],
  };
}


export function toggleRectificationFeedback(
  current: "up" | "down" | undefined,
  requested: "up" | "down",
): "up" | "down" | undefined {
  return current === requested ? undefined : requested;
}

function activityPhase(tool: PublicRectificationTool): NonNullable<ChatMessageView["activity"]>["phase"] {
  if (tool === "rectification-compare-candidates" || tool === "rectification-read-diagnostics") {
    return "chart-calculation";
  }
  return "evidence-validation";
}

function messagesFromTurns(initialTurns: readonly PersistedTurn[]): RenderMessage[] {
  return initialTurns.flatMap((turn, index): RenderMessage[] => {
    const key = `persisted-${turn.id}-${index}`;
    if (turn.role === "assistant") {
      return [{
        role: "assistant",
        text: turn.text ?? "",
        renderKey: key,
        state: turn.status === "completed" ? "settled" : "thinking",
        completedReceipt: completedReceiptFromPersisted(turn.receipt),
        turnId: turn.id,
      }];
    }
    return [{
      role: "user",
      text: turn.text ?? "",
      renderKey: key,
      state: "settled",
    }];
  });
}

export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
  const {
    caseId,
    sessionId,
    readonly,
    shouldStartOpening,
    initialTurns,
    models,
    selectedModelId,
    onSelectModel,
    onMessagesChange,
    onCompleted,
    onPendingChange,
    onProfileIncomplete,
    onSaved,
    pendingConsultationQuestion,
    onRestart,
  } = props;

  const [messages, setMessages] = useState<RenderMessage[]>(() => messagesFromTurns(initialTurns));
  const [draft, setDraft] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [savedTime, setSavedTime] = useState<string | null>(null);
  const [savedStatus, setSavedStatus] = useState<"accepted" | "confirmed" | null>(null);
  const [candidateResult, setCandidateResult] = useState<CandidateResult>(null);
  const [acceptingTime, setAcceptingTime] = useState<string | null>(null);
  const [feedback, setFeedback] = useState<Record<string, "up" | "down" | undefined>>({});
  const [copiedMessageKey, setCopiedMessageKey] = useState<string | null>(null);
  const [regeneratingMessageKey, setRegeneratingMessageKey] = useState<string | null>(null);
  const conversation = useRef<HTMLElement>(null);
  const composer = useRef<HTMLTextAreaElement>(null);
  const keyCounter = useRef(0);
  const openingStarted = useRef(false);

  const setPending = useCallback((value: boolean) => {
    setBusy(value);
    onPendingChange?.(value);
  }, [onPendingChange]);

  useEffect(() => {
    const container = conversation.current;
    if (!container) return;
    const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    container.scrollTo({
      top: container.scrollHeight,
      behavior: busy || reduceMotion ? "auto" : "smooth",
    });
  }, [busy, error, messages, savedTime]);

  // Candidate snapshot comes from the persisted Candidate Snapshot API, never
  // from parsing agent text or hidden sentinels.
  const loadCandidate = useCallback(async (): Promise<CandidateResult> => {
    try {
      const response = await fetch(
        `/api/rectification/cases/${encodeURIComponent(caseId)}?sessionId=${encodeURIComponent(sessionId)}`,
        { cache: "no-store" },
      );
      if (!response.ok) return null;
      const payload = await response.json().catch(() => null);
      return parseRectificationCandidateResult(payload?.latest_result);
    } catch {
      return null;
    }
  }, [caseId, sessionId]);

  useEffect(() => {
    let active = true;
    void loadCandidate().then((result) => {
      if (active) setCandidateResult(result);
    });
    return () => { active = false; };
  }, [loadCandidate]);

  const send = useCallback(async (action: "opening" | "message", messageText: string) => {
    const trimmed = action === "message" ? messageText.trim() : "";
    if ((action === "message" && !trimmed) || busy || readonly) return;
    setError("");
    setPending(true);

    keyCounter.current += 1;
    const requestId = globalThis.crypto.randomUUID();
    const turnKey = keyCounter.current;
    const userRenderKey = `v9-user-${turnKey}`;
    const assistantRenderKey = `v9-assistant-${turnKey}`;

    setMessages((current) => [
      ...current,
      ...(action === "message"
        ? [{ role: "user", text: trimmed, renderKey: userRenderKey, state: "settled" } satisfies RenderMessage]
        : []),
      { role: "assistant", text: "", renderKey: assistantRenderKey, state: "thinking" },
    ]);
    setDraft("");

    let raw = "";
    let activityReceiptState = createRectificationActivityReceiptState();
    let completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
    let completedTurnId: string | undefined;
    try {
      const response = await fetch("/api/rectification/agent", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          caseId,
          sessionId,
          requestId,
          action,
          modelId: selectedModelId,
          ...(action === "message" ? { message: trimmed } : {}),
        }),
      });
      if (!response.ok) {
        const payload = await response.json().catch(() => null);
        const message = payload?.message || payload?.error || `请求失败（${response.status}）`;
        setMessages((current) => current.filter((item) => item.renderKey !== assistantRenderKey));
        if (payload?.code === "profile_incomplete") {
          onProfileIncomplete?.();
          return;
        }
        if (response.status === 402) {
          window.location.assign(membershipHref("rectification"));
          return;
        }
        if (response.status === 401) setError("请先登录。");
        else setError(message);
        return;
      }
      if (!response.body) {
        setMessages((current) => current.filter((item) => item.renderKey !== assistantRenderKey));
        setError("服务暂时不可用，请稍后再试。");
        return;
      }

      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      let completed = false;
      let streamFailed = false;
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() ?? "";
        for (const line of lines) {
          if (!line.trim()) continue;
          let event: {
            type?: unknown;
            status?: unknown;
            text?: unknown;
            message?: unknown;
            tool?: unknown;
            methods?: unknown;
            turnId?: unknown;
          };
          try {
            event = JSON.parse(line) as typeof event;
          } catch {
            continue;
          }
          if (typeof event.type !== "string") continue;
          if (event.type === "answer.delta" && typeof event.text === "string") {
            raw += event.text;
            const parsed = parseAgentReplyBody(raw);
            setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
              ? { ...message, text: parsed.text, state: "streaming", activeActivity: undefined }
              : message));
          } else if (event.type === "run.failed") {
            streamFailed = true;
          } else if (event.type === "error") {
            streamFailed = true;
            setError(typeof event.message === "string" ? event.message : "生时校正暂时不可用，请稍后再试。");
          } else if (event.type === "run.completed") {
            completed = true;
            if (typeof event.turnId === "string") completedTurnId = event.turnId;
          } else if (event.type === "tool.activity") {
            const tool = isPublicRectificationTool(event.tool) ? event.tool : null;
            if (!tool) continue;
            if (event.status === "started") {
              const activeActivity = { tool, label: ACTIVE_TOOL_LABELS[tool] } satisfies PublicActivity;
              setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
                ? { ...message, activeActivity }
                : message));
              continue;
            }
            if (event.status !== "completed" && event.status !== "failed") continue;
            activityReceiptState = reduceRectificationActivityReceipt(activityReceiptState, {
              tool,
              status: event.status,
              methods: event.status === "completed" && Array.isArray(event.methods)
                ? event.methods.filter(isPublicRectificationMethod)
                : [],
            });
            completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
            setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
              ? {
                ...message,
                activeActivity: message.activeActivity?.tool === tool ? undefined : message.activeActivity,
              }
              : message));
          }
        }
      }

      const parsed = parseAgentReplyBody(raw);
      const succeeded = completed && !streamFailed && Boolean(parsed.text);
      setMessages((current) => succeeded
        ? current.map((message) => message.renderKey === assistantRenderKey
          ? {
            ...message,
            text: parsed.text,
            state: "settled",
            activeActivity: undefined,
            completedReceipt,
            turnId: completedTurnId,
          }
          : message)
        : current.filter((message) => message.renderKey !== assistantRenderKey));
      if (!succeeded && completedReceipt.failedTool) {
        setError(completedReceipt.failedTool === "rectification-compare-candidates"
          ? "候选比较未完成，当前进度已保留。"
          : "本轮处理未完成，当前进度已保留。请稍后再试。");
      }
      if (succeeded) {
        onMessagesChange?.([
          ...(action === "message" ? [{ role: "user" as const, text: trimmed }] : []),
          { role: "assistant", text: parsed.text },
        ]);
        onCompleted?.();
        await loadCandidate().then((result) => {
          if (result) setCandidateResult(result);
        });
      }
    } catch {
      setError("生时校正暂时不可用，请稍后再试。");
      setMessages((current) => current.filter((message) => message.renderKey !== assistantRenderKey));
    } finally {
      setPending(false);
    }
  }, [busy, caseId, loadCandidate, onCompleted, onMessagesChange, onProfileIncomplete, readonly, selectedModelId, sessionId, setPending]);

  useEffect(() => {
    if (readonly || openingStarted.current || !shouldStartOpening) return;
    openingStarted.current = true;
    void send("opening", "");
  }, [readonly, send, shouldStartOpening]);

  const acceptCandidate = useCallback(async (time: string) => {
    if (!candidateResult || acceptingTime || readonly) return;
    setError("");
    setAcceptingTime(time);
    try {
      const response = await fetch(
        `/api/rectification/cases/${encodeURIComponent(caseId)}/candidates/accept`,
        {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            sessionId,
            resultId: candidateResult.resultId,
            candidateId: time,
            requestId: globalThis.crypto.randomUUID(),
          }),
        },
      );
      const payload = await response.json().catch(() => null);
      if (!response.ok || payload?.ok !== true) {
        throw new Error(payload?.error || payload?.message || "暂时无法采用该候选时间");
      }
      const status = payload.status === "confirmed" ? "confirmed" : "accepted";
      setCandidateResult((current) => current ? { ...current, selectedTime: payload.saved_time, selectionKind: status === "confirmed" ? "engine_confirmed" : "user_accepted" } : current);
      setSavedTime(payload.saved_time);
      setSavedStatus(status);
      onSaved?.(payload.saved_time, status);
      onCompleted?.();
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : "暂时无法采用该候选时间");
    } finally {
      setAcceptingTime(null);
    }
  }, [acceptingTime, candidateResult, caseId, onCompleted, onSaved, readonly, sessionId]);

  async function copyMessage(message: RenderMessage) {
    try {
      await navigator.clipboard.writeText(message.text);
      setCopiedMessageKey(message.renderKey);
      window.setTimeout(() => setCopiedMessageKey((current) => (
        current === message.renderKey ? null : current
      )), 1_500);
    } catch {
      // Clipboard permission failures must not interrupt the conversation.
    }
  }

  async function regenerateMessage(message: RenderMessage) {
    if (!message.turnId || regeneratingMessageKey || busy || readonly) return;
    setError("");
    setRegeneratingMessageKey(message.renderKey);
    const previousText = message.text;
    try {
      const response = await fetch(
        `/api/rectification/cases/${encodeURIComponent(caseId)}/turns/${encodeURIComponent(message.turnId)}/regenerate`,
        {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            sessionId,
            requestId: globalThis.crypto.randomUUID(),
          }),
        },
      );
      const payload = await response.json().catch(() => null);
      if (!response.ok || payload?.ok !== true || typeof payload.assistantMessage !== "string") {
        throw new Error(payload?.message || payload?.error || "暂时无法重新生成回答");
      }
      setMessages((current) => current.map((item) => item.renderKey === message.renderKey
        ? { ...item, text: payload.assistantMessage, state: "settled" }
        : item));
    } catch (caught) {
      setMessages((current) => current.map((item) => item.renderKey === message.renderKey
        ? { ...item, text: previousText, state: "settled" }
        : item));
      setError(caught instanceof Error ? caught.message : "暂时无法重新生成回答");
    } finally {
      setRegeneratingMessageKey((current) => current === message.renderKey ? null : current);
    }
  }

  async function submit(event: React.FormEvent) {
    event.preventDefault();
    await send("message", draft);
  }

  const latestRegeneratableKey = [...messages]
    .reverse()
    .find((message) => (
      message.role === "assistant"
      && message.state === "settled"
      && Boolean(message.turnId)
      && Boolean(message.text)
    ))?.renderKey;
  const canSend = !busy && !readonly && !regeneratingMessageKey;

  return (
    <>
      <section ref={conversation} className="conversation" aria-label="生时校正对话" aria-busy={busy || regeneratingMessageKey !== null}>
        <div className="message-list" aria-live="polite">
          {pendingConsultationQuestion?.trim() && (
            <p className="rectification-pending-note">
              先陪你核对出生时间范围，之后会回到你原来的问题：“{pendingConsultationQuestion.trim()}”
            </p>
          )}
          {messages.map((message) => {
            const showActions = message.role === "assistant"
              && message.state === "settled"
              && Boolean(message.text);
            const regenerating = regeneratingMessageKey === message.renderKey;
            const canRegenerate = message.renderKey === latestRegeneratableKey
              && !busy
              && !readonly
              && regeneratingMessageKey === null;
            const displayedMessage = regenerating
              ? { ...message, text: "", state: "thinking" as const, activeActivity: undefined }
              : message;
            return (
              <div key={message.renderKey} className="rectification-message-wrap rectification-message-entry">
                {message.state === "settled" && message.completedReceipt && (
                  <CompletedActivityReceipt receipt={message.completedReceipt} />
                )}
                <ChatMessageRow
                  message={{
                    ...displayedMessage,
                    activity: displayedMessage.activeActivity
                      ? { phase: activityPhase(displayedMessage.activeActivity.tool), label: displayedMessage.activeActivity.label }
                      : undefined,
                  }}
                  showActivity={displayedMessage.state === "thinking" || Boolean(displayedMessage.activeActivity)}
                />
                {showActions && !regenerating && (
                  <div className="rectification-message-actions" aria-label="Agent 回答操作">
                    <button
                      aria-label="赞"
                      aria-pressed={feedback[message.renderKey] === "up"}
                      className={feedback[message.renderKey] === "up" ? "is-active" : ""}
                      title="赞"
                      type="button"
                      onClick={() => setFeedback((current) => ({
                        ...current,
                        [message.renderKey]: toggleRectificationFeedback(current[message.renderKey], "up"),
                      }))}
                    >
                      <ThumbsUp aria-hidden="true" />
                    </button>
                    <button
                      aria-label="踩"
                      aria-pressed={feedback[message.renderKey] === "down"}
                      className={feedback[message.renderKey] === "down" ? "is-active" : ""}
                      title="踩"
                      type="button"
                      onClick={() => setFeedback((current) => ({
                        ...current,
                        [message.renderKey]: toggleRectificationFeedback(current[message.renderKey], "down"),
                      }))}
                    >
                      <ThumbsDown aria-hidden="true" />
                    </button>
                    <button aria-label="复制回答" title="复制" type="button" onClick={() => void copyMessage(message)}>
                      {copiedMessageKey === message.renderKey
                        ? <Check aria-hidden="true" />
                        : <Copy aria-hidden="true" />}
                    </button>
                    <button
                      aria-label="重新生成回答"
                      disabled={!canRegenerate}
                      title={canRegenerate ? "重新生成" : "只能重新生成最近一条回答"}
                      type="button"
                      onClick={() => void regenerateMessage(message)}
                    >
                      <RotateCcw aria-hidden="true" />
                    </button>
                  </div>
                )}
              </div>
            );
          })}
          {candidateResult?.selectionAllowed && candidateResult.candidates.length > 0 && (
            <section className="rectification-candidates" aria-label="生时校正候选时间">
              <div className="rectification-candidates-heading">
                <strong>{candidateResult.confirmationAllowed ? "确认校正时间" : "当前可能的出生时间"}</strong>
                <span>这是根据你目前提供的人生事件推算出的几种可能时间。可以先采用一个作为当前排盘时间，也可以继续补充事件；新增证据后，候选和相对支持度会重新计算，准确度还可以继续提高。</span>
                <span>相对支持度不是统计概率，采用不会覆盖原始填报时间。</span>
              </div>
              <div className="rectification-candidate-list">
                {candidateResult.candidates.map((candidate) => {
                  const selected = candidateResult.selectedTime === candidate.time;
                  const recommended = isRecommendedRectificationCandidate(candidateResult, candidate);
                  return (
                    <button
                      type="button"
                      className={`rectification-candidate${selected ? " is-selected" : ""}`}
                      key={`${candidateResult.resultId}-${candidate.time}`}
                      disabled={selected || Boolean(acceptingTime) || readonly}
                      onClick={() => void acceptCandidate(candidate.time)}
                    >
                      <span className="rectification-candidate-time">
                        <strong>{candidate.time}</strong>
                        {selected && <span className="rectification-candidate-badge">已采用</span>}
                        {recommended && <span className="rectification-candidate-badge">当前推荐</span>}
                      </span>
                      <span className="rectification-candidate-support">相对支持度 {candidate.relativeSupport}</span>
                      <span className="rectification-candidate-action">
                        {selected ? "已采用" : acceptingTime === candidate.time ? "正在采用…" : candidateResult.selectedTime ? "改选为此时间" : "采用此时间"}
                      </span>
                    </button>
                  );
                })}
              </div>
            </section>
          )}
          {savedTime && (
            <p className="rectification-saved" role="status">
              {savedStatus === "confirmed" ? "已确认校正时间" : "校正采用时间"}：{savedTime}。后续排盘将使用该时间；你仍可继续补充事件或改选其他候选。
            </p>
          )}
          {error && <p className="error-message" role="alert">{error}</p>}
          {readonly && (
            <div className="rectification-terminal-actions">
              <p className="rectification-terminal-note">该校正已结束，只能查看历史。</p>
              <Button type="button" onClick={onRestart}>再次校正</Button>
            </div>
          )}
        </div>
      </section>

      <div className="composer-wrap">
        <form className="composer" onSubmit={submit}>
          <Textarea
            ref={composer}
            aria-label={readonly ? "该校正已结束，只能查看历史" : "继续描述你的经历或回答"}
            value={draft}
            disabled={!canSend}
            placeholder={readonly
              ? "该校正已结束，只能查看历史；需要再次校正请新建。"
              : "继续说你记得的人生经历，或回答刚才的问题…"}
            onChange={(event) => setDraft(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
                event.preventDefault();
                event.currentTarget.form?.requestSubmit();
              }
            }}
          />
          <Button aria-label="发送" disabled={!draft.trim() || !canSend} size="icon" type="submit">
            <ArrowUp aria-hidden="true" />
          </Button>
        </form>

        <div className="composer-footer">
          <ModelSelector
            models={models}
            selectedModelId={selectedModelId}
            disabled={busy || readonly}
            onSelect={onSelectModel}
          />
        </div>
      </div>
    </>
  );
}
