"use client";

import { useEffect, useRef, type RefObject } from "react";
import { MessagesSquare, X } from "lucide-react";

import { ThinkingIndicator } from "@/components/ThinkingIndicator";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { EmptyState } from "@/components/ui/EmptyState";
import { StatusNote } from "@/components/ui/StatusNote";
import { handleTabTrap } from "@/lib/a11y/focusTrap";
import { useRestoreFocus } from "@/lib/a11y/useRestoreFocus";
import { useBodyScrollLock } from "@/lib/ui/useBodyScrollLock";
import { cn, overlayScrim } from "@/lib/ui/variants";
import type { AiMessageView } from "@/lib/api/types";
import {
  acceptActionFor,
  bubbleSide,
  groupByThread,
  kindBadge,
  kindLabel,
  partitionByScope,
  type AcceptAction,
  type AiThread,
} from "@/lib/workbench/aiConversation";
import type { UseAiConversation } from "@/lib/workbench/useAiConversation";

// AI 对话总开关：一键关闭即回滚（触发按钮据此隐藏），仿 CONTEXT_DRAWER_ENABLED。
export const AI_CONVERSATION_ENABLED = true;

interface AiConversationDrawerProps {
  open: boolean;
  onClose: () => void;
  conversation: UseAiConversation;
  currentChapterNo: number;
  // 从历史再接受（复用编辑器既有 DRAFT-only HITL 回调）；返回 true=成功则关抽屉。
  onReplaceChapter: (content: string) => boolean;
  onAppendDraft: (content: string) => boolean;
  onRefillSegment: (segment: string, refined: string) => boolean;
  triggerRef?: RefObject<HTMLElement | null>;
}

// 每章「AI 对话」抽屉（对话气泡视图）：本章对话 + 项目级生成两段，作者右 / AI 左，kind 徽标 + 线程归组。
// 结构克隆 ContextDrawer：右侧 slide-over + overlayScrim + role=dialog + Esc + focus trap + scroll lock + 还原焦点。
// append-only 只读展示；对 ai 产出可「从历史再接受」——只回填编辑器草稿，不绕开验收事务（守不变量 #3）。
export function AiConversationDrawer({
  open,
  onClose,
  conversation,
  currentChapterNo,
  onReplaceChapter,
  onAppendDraft,
  onRefillSegment,
  triggerRef,
}: AiConversationDrawerProps) {
  const panelRef = useRef<HTMLDivElement>(null);
  const closeRef = useRef<HTMLButtonElement>(null);

  useBodyScrollLock(open);
  useRestoreFocus(open, triggerRef);

  // 打开即懒加载（Workbench 级 hook，append 早已可能触发过——已加载则空操作）。
  useEffect(() => {
    if (open) conversation.ensureLoaded();
  }, [open, conversation]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent): void => {
      if (e.key === "Escape") {
        e.preventDefault();
        onClose();
      }
    };
    window.addEventListener("keydown", onKey);
    closeRef.current?.focus();
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);

  if (!open) return null;

  const threads = groupByThread(conversation.messages);
  const { chapterThreads, projectThreads } = partitionByScope(
    threads,
    currentChapterNo,
  );
  const summary = `本章 ${chapterThreads.length} 段对话，项目级 ${projectThreads.length} 项生成`;

  // 再接受成功则关抽屉揭示改动后的编辑器；失败（如原选段漂移）保留抽屉与漂移提示。
  const accept = (action: AcceptAction, m: AiMessageView): void => {
    let ok = false;
    if (action === "replace_chapter") ok = onReplaceChapter(m.content);
    else if (action === "append_draft") ok = onAppendDraft(m.content);
    else if (action === "refill_segment")
      ok = onRefillSegment(readSegment(m.meta), m.content);
    if (ok) onClose();
  };

  return (
    <div className={overlayScrim} onClick={onClose}>
      <div
        ref={panelRef}
        role="dialog"
        aria-modal="true"
        aria-label="AI 对话"
        onClick={(e) => e.stopPropagation()}
        onKeyDown={(e) => {
          if (panelRef.current) handleTabTrap(panelRef.current, e);
        }}
        className="absolute right-0 top-0 flex h-full w-96 max-w-[90vw] flex-col border-l border-line bg-panel shadow-paper outline-none motion-safe:transition-transform"
      >
        <header className="flex items-center justify-between border-b border-line px-4 py-3">
          <span className="flex items-center gap-2 font-serif text-base text-ink">
            <MessagesSquare
              className="h-4 w-4 text-cinnabar"
              aria-hidden="true"
            />
            AI 对话
          </span>
          <Button
            ref={closeRef}
            onClick={onClose}
            aria-label="关闭 AI 对话"
            variant="ghost"
            size="icon"
          >
            <X className="h-5 w-5" aria-hidden="true" />
          </Button>
        </header>

        <div
          role="log"
          aria-live="polite"
          aria-relevant="additions"
          aria-label={summary}
          className="flex-1 overflow-auto overscroll-contain px-4 py-4"
        >
          <DrawerBody
            status={conversation.status}
            chapterThreads={chapterThreads}
            projectThreads={projectThreads}
            onRetry={conversation.reload}
            onAccept={accept}
          />
        </div>
      </div>
    </div>
  );
}

interface DrawerBodyProps {
  status: UseAiConversation["status"];
  chapterThreads: AiThread[];
  projectThreads: AiThread[];
  onRetry: () => void;
  onAccept: (action: AcceptAction, m: AiMessageView) => void;
}

function DrawerBody({
  status,
  chapterThreads,
  projectThreads,
  onRetry,
  onAccept,
}: DrawerBodyProps) {
  if (status === "loading" || status === "idle") {
    return (
      <div className="py-6 text-center">
        <ThinkingIndicator label="加载中" className="text-sm text-ink-soft" />
      </div>
    );
  }
  if (status === "error") {
    return (
      <StatusNote variant="danger" title="加载失败">
        <p>AI 对话暂不可用。</p>
        <Button
          onClick={onRetry}
          variant="secondary"
          size="sm"
          className="mt-2"
        >
          重试
        </Button>
      </StatusNote>
    );
  }
  if (chapterThreads.length === 0 && projectThreads.length === 0) {
    return (
      <EmptyState
        icon={MessagesSquare}
        title="还没有 AI 对话"
        description="润色、整章重写、续写、工具箱生成的往复会自动留痕，重开也不丢。"
      />
    );
  }
  return (
    <div className="space-y-5">
      <ThreadSection
        heading="本章对话"
        threads={chapterThreads}
        onAccept={onAccept}
        emptyText="本章还没有对话。"
      />
      <ThreadSection
        heading="项目级生成"
        threads={projectThreads}
        onAccept={onAccept}
        emptyText="还没有项目级（工具箱）生成。"
      />
    </div>
  );
}

interface ThreadSectionProps {
  heading: string;
  threads: AiThread[];
  onAccept: (action: AcceptAction, m: AiMessageView) => void;
  emptyText: string;
}

function ThreadSection({
  heading,
  threads,
  onAccept,
  emptyText,
}: ThreadSectionProps) {
  return (
    <section>
      <h3 className="mb-2 text-2xs font-medium uppercase tracking-wide text-ink-soft">
        {heading}
      </h3>
      {threads.length === 0 ? (
        <p className="text-xs text-ink-soft">{emptyText}</p>
      ) : (
        <ul className="space-y-3">
          {threads.map((thread) => (
            <li key={thread.threadId}>
              <ThreadBlock thread={thread} onAccept={onAccept} />
            </li>
          ))}
        </ul>
      )}
    </section>
  );
}

interface ThreadBlockProps {
  thread: AiThread;
  onAccept: (action: AcceptAction, m: AiMessageView) => void;
}

function ThreadBlock({ thread, onAccept }: ThreadBlockProps) {
  const action = acceptActionFor(thread);
  const headAt = thread.messages[0]?.created_at ?? "";
  return (
    <div className="rounded border border-line bg-bg/50 p-3">
      <div className="mb-2 flex items-center gap-2">
        <Badge variant={kindBadge(thread.kind)}>{kindLabel(thread.kind)}</Badge>
        {thread.toolKey ? (
          <span className="font-mono text-2xs text-ink-soft">
            {thread.toolKey}
          </span>
        ) : null}
        <span className="ml-auto font-mono text-2xs text-ink-soft">
          {formatBubbleTime(headAt)}
        </span>
      </div>
      <div className="space-y-2">
        {thread.messages.map((m) => (
          <Bubble key={m.id} message={m} action={action} onAccept={onAccept} />
        ))}
      </div>
    </div>
  );
}

interface BubbleProps {
  message: AiMessageView;
  action: AcceptAction | null;
  onAccept: (action: AcceptAction, m: AiMessageView) => void;
}

// 单条气泡：作者右（cinnabar-wash）/ AI 左（border+bg）；clarify ai 气泡额外渲染只读方向选项。
function Bubble({ message, action, onAccept }: BubbleProps) {
  const side = bubbleSide(message.role);
  const options = message.kind === "clarify" ? readOptions(message.meta) : [];
  // 只有 ai 产出气泡（左侧）挂再接受按钮；作者气泡与 clarify 不挂。
  const showAccept = action !== null && side === "left" && message.kind !== "clarify";
  return (
    <div className={cn("flex", side === "right" ? "justify-end" : "justify-start")}>
      <div
        className={cn(
          "max-w-[85%] rounded border px-3 py-2",
          side === "right"
            ? "border-cinnabar/30 bg-[var(--color-cinnabar-wash)]"
            : "border-line bg-bg",
        )}
      >
        <p className="max-h-56 overflow-y-auto whitespace-pre-wrap text-sm text-ink">
          {message.content}
        </p>
        {options.length > 0 ? (
          <ul className="mt-2 flex flex-wrap gap-1.5">
            {options.map((opt, i) => (
              <li
                key={i}
                className="rounded border border-line bg-panel px-2 py-0.5 text-2xs text-ink-soft"
              >
                {opt}
              </li>
            ))}
          </ul>
        ) : null}
        {showAccept && action ? (
          <Button
            onClick={() => onAccept(action, message)}
            variant="secondary"
            size="sm"
            className="mt-2"
          >
            {ACCEPT_LABEL[action]}
          </Button>
        ) : null}
      </div>
    </div>
  );
}

const ACCEPT_LABEL: Record<AcceptAction, string> = {
  replace_chapter: "接受这版（替换整章）",
  append_draft: "插入正文（章末）",
  refill_segment: "回填选段",
};

// meta.segment（refine ai 行每版都存原选段）——供「回填选段」按内容重锚。
function readSegment(meta: AiMessageView["meta"]): string {
  const v = (meta ?? {})["segment"];
  return typeof v === "string" ? v : "";
}

// clarify ai 行 meta.options → 只读选项 label 列表（外部数据，逐项显式取值，不信任形状）。
function readOptions(meta: AiMessageView["meta"]): string[] {
  const raw = (meta ?? {})["options"];
  if (!Array.isArray(raw)) return [];
  return raw
    .map((o) =>
      o && typeof o === "object" && "label" in o
        ? String((o as { label: unknown }).label ?? "")
        : "",
    )
    .filter((label) => label.length > 0);
}

// 气泡时间戳：本地 HH:MM（显示用，与自动保存「保存于 HH:MM」风格一致）。
function formatBubbleTime(iso: string): string {
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return "";
  return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
