"use client";

import { useEffect, useMemo, useState, type ReactNode } from "react";
import { ArrowUp, History, ImageIcon, LoaderCircle, MessageSquare, PanelRightClose, Plus, RotateCcw, Settings2, Sparkles, Trash2, X } from "lucide-react";
import { Button, ConfigProvider, Input, InputNumber, Modal, Popover, Segmented, Tooltip } from "antd";
import { motion } from "motion/react";

import { ImageGenerationPending } from "@/components/image-generation-pending";
import { ModelPicker } from "@/components/model-picker";
import type { AiConfig } from "@/lib/ai-config";
import { canvasThemes } from "@/lib/canvas-theme";
import { createId } from "@/lib/id";
import { cn } from "@/lib/utils";
import { requestEdit, requestGeneration, requestImageQuestion, type ChatCompletionMessage } from "@/services/api/image";
import { imageToDataUrl, uploadImage } from "@/services/image-storage";
import { useAiConfigStore } from "@/stores/use-ai-config-store";
import { useAssetStore } from "@/stores/use-asset-store";
import { useConfigDialogStore } from "@/stores/use-config-dialog-store";
import { useThemeStore } from "@/stores/use-theme-store";
import type { ReferenceImage } from "@/types/image";
import { DiaTextReveal } from "@/components/ui/dia-text-reveal";
import { CanvasPromptLibrary } from "./canvas-prompt-library";
import { CanvasNodeType, type CanvasAssistantImage, type CanvasAssistantMessage, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "../types";

type AssistantMode = "ask" | "image";
const PANEL_MOTION_MS = 500;
const PANEL_MOTION_SECONDS = PANEL_MOTION_MS / 1000;

type CanvasAssistantPanelProps = {
  nodes: CanvasNodeData[];
  selectedNodeIds: Set<string>;
  sessions: CanvasAssistantSession[];
  activeSessionId: string | null;
  onSelectNodeIds: (ids: Set<string>) => void;
  onSessionsChange: (sessions: CanvasAssistantSession[], activeSessionId: string | null) => void;
  onInsertImage: (image: CanvasAssistantImage) => void;
  onInsertText: (text: string) => void;
  onPasteImage: (file: File) => void;
  onCollapseStart: () => void;
  onCollapse: () => void;
};

export function CanvasAssistantPanel({ nodes, selectedNodeIds, sessions, activeSessionId, onSelectNodeIds, onSessionsChange, onInsertImage, onInsertText, onPasteImage, onCollapseStart, onCollapse }: CanvasAssistantPanelProps) {
  const theme = canvasThemes[useThemeStore((state) => state.theme)];
  const config = useAiConfigStore((state) => state.config);
  const cleanupImages = useAssetStore((state) => state.cleanupImages);
  const updateConfig = useAiConfigStore((state) => state.updateConfig);
  const openConfigDialog = useConfigDialogStore((state) => state.openConfigDialog);
  const [width, setWidth] = useState(390);
  const [view, setView] = useState<"chat" | "history">("chat");
  const [mode, setMode] = useState<AssistantMode>("image");
  const [prompt, setPrompt] = useState("");
  const [isRunning, setIsRunning] = useState(false);
  const [checkedChatIds, setCheckedChatIds] = useState<string[]>([]);
  const [deleteChatIds, setDeleteChatIds] = useState<string[]>([]);
  const [closing, setClosing] = useState(false);
  const [resizing, setResizing] = useState(false);
  const [removedReferenceIds, setRemovedReferenceIds] = useState<Set<string>>(new Set());
  const [localSessions, setLocalSessions] = useState<CanvasAssistantSession[]>(() => sessions.length ? sessions : [createSession()]);
  const [localActiveSessionId, setLocalActiveSessionId] = useState<string | null>(activeSessionId);

  useEffect(() => {
    if (!sessions.length) return;
    setLocalSessions(sessions);
    setLocalActiveSessionId(activeSessionId);
  }, [activeSessionId, sessions]);

  useEffect(() => {
    onSessionsChange(localSessions, localActiveSessionId);
  }, [localActiveSessionId, localSessions, onSessionsChange]);

  const safeSessions = localSessions.length ? localSessions : [createSession()];
  const activeSession = useMemo(() => safeSessions.find((session) => session.id === localActiveSessionId) || safeSessions[0] || null, [localActiveSessionId, safeSessions]);
  const historySessions = safeSessions.filter((session) => session.messages.length > 0);
  const messages = activeSession?.messages || [];
  const hasMessages = messages.length > 0;
  const selectedNodeKey = useMemo(() => Array.from(selectedNodeIds).sort().join(","), [selectedNodeIds]);
  const allSelectedReferences = useMemo(() => buildAssistantReferences(nodes, selectedNodeIds), [nodes, selectedNodeIds]);
  const selectedReferences = useMemo(() => allSelectedReferences.filter((item) => !removedReferenceIds.has(item.id)), [allSelectedReferences, removedReferenceIds]);
  const iconButtonStyle = { color: theme.node.muted };

  useEffect(() => {
    setRemovedReferenceIds(new Set());
  }, [selectedNodeKey]);

  const updateSession = (sessionId: string, updater: (session: CanvasAssistantSession) => CanvasAssistantSession) => {
    setLocalSessions((prev) => prev.map((session) => session.id === sessionId ? updater(session) : session));
  };

  const appendMessage = (sessionId: string, message: CanvasAssistantMessage) => {
    updateSession(sessionId, (session) => ({
      ...session,
      title: session.messages.length ? session.title : message.text.slice(0, 18) || "新对话",
      messages: [...session.messages, message],
      updatedAt: new Date().toISOString(),
    }));
  };

  const updateMessage = (sessionId: string, messageId: string, patch: Partial<CanvasAssistantMessage>) => {
    updateSession(sessionId, (session) => ({
      ...session,
      messages: session.messages.map((message) => message.id === messageId ? { ...message, ...patch } : message),
      updatedAt: new Date().toISOString(),
    }));
  };

  const startChatSession = () => {
    if (activeSession && activeSession.messages.length === 0) {
      setLocalActiveSessionId(activeSession.id);
      return;
    }
    const session = createSession();
    setLocalSessions((prev) => [session, ...prev]);
    setLocalActiveSessionId(session.id);
  };

  const removeSessions = (ids: string[]) => {
    const next = safeSessions.filter((session) => !ids.includes(session.id));
    if (!next.length) {
      const session = createSession();
      setLocalSessions([session]);
      setLocalActiveSessionId(session.id);
    } else {
      setLocalSessions(next);
      setLocalActiveSessionId(localActiveSessionId && ids.includes(localActiveSessionId) ? next[0].id : localActiveSessionId);
    }
    cleanupImages({ sessions: next });
    setCheckedChatIds((prev) => prev.filter((id) => !ids.includes(id)));
  };

  const clearSessions = () => {
    const session = createSession();
    setLocalSessions([session]);
    setLocalActiveSessionId(session.id);
    setCheckedChatIds([]);
    cleanupImages({ sessions: [session] });
  };

  const sendMessage = async (text: string, nextMode: AssistantMode, history: CanvasAssistantMessage[], savedReferences?: CanvasAssistantReference[]) => {
    const requestConfig = { ...config, model: nextMode === "image" ? config.imageModel || config.model : config.textModel || config.model };
    if (!requestConfig.baseUrl.trim() || !requestConfig.model.trim() || !requestConfig.apiKey.trim()) {
      openConfigDialog(true);
      return;
    }

    const session = activeSession || createSession();
    if (!activeSession) {
      setLocalSessions([session]);
      setLocalActiveSessionId(session.id);
    }

    const refs = savedReferences || selectedReferences;
    const userMessage: CanvasAssistantMessage = { id: createId(), role: "user", mode: nextMode, text, references: refs };
    const assistantId = createId();
    appendMessage(session.id, userMessage);
    appendMessage(session.id, { id: assistantId, role: "assistant", mode: nextMode, text: nextMode === "image" ? "正在生成图片" : "正在回答", isLoading: true });
    setPrompt("");
    setIsRunning(true);

    try {
      if (nextMode === "image") {
        const referenceImages: ReferenceImage[] = await Promise.all(refs.filter((item) => item.dataUrl).map(async (item) => ({ id: item.id, name: `${item.title}.png`, type: "image/png", dataUrl: await imageToDataUrl(item), storageKey: item.storageKey })));
        const images = referenceImages.length ? await requestEdit(requestConfig, text, referenceImages) : await requestGeneration(requestConfig, text);
        const storedImages = await Promise.all(images.map((image) => uploadImage(image.dataUrl)));
        updateMessage(session.id, assistantId, {
          text: `生成了 ${storedImages.length} 张图片`,
          images: storedImages.map((image, index) => ({ id: images[index].id, dataUrl: image.url, storageKey: image.storageKey, prompt: text })),
          isLoading: false,
        });
        return;
      }

      const answer = await requestImageQuestion(requestConfig, await buildChatMessages([...history, userMessage]), (streamed) => {
        updateMessage(session.id, assistantId, { text: streamed, isLoading: false });
      });
      updateMessage(session.id, assistantId, { text: answer, isLoading: false });
    } catch (error) {
      updateMessage(session.id, assistantId, { text: error instanceof Error ? error.message : "操作失败", isLoading: false });
    } finally {
      setIsRunning(false);
    }
  };

  const submit = async () => {
    const text = prompt.trim();
    if (!text || isRunning) return;
    await sendMessage(text, mode, messages);
  };

  const retryMessage = (message: CanvasAssistantMessage) => {
    const index = messages.findIndex((item) => item.id === message.id);
    const userIndex = messages.slice(0, index).findLastIndex((item) => item.role === "user");
    const user = messages[userIndex];
    if (user) void sendMessage(user.text, user.mode, messages.slice(0, userIndex), user.references);
  };

  const startResize = () => {
    const move = (event: MouseEvent) => setWidth(Math.min(760, Math.max(320, window.innerWidth - event.clientX)));
    const stop = () => {
      setResizing(false);
      document.body.style.cursor = "";
      document.body.style.userSelect = "";
      document.removeEventListener("mousemove", move);
      document.removeEventListener("mouseup", stop);
    };
    setResizing(true);
    document.body.style.cursor = "col-resize";
    document.body.style.userSelect = "none";
    document.addEventListener("mousemove", move);
    document.addEventListener("mouseup", stop);
  };

  const collapse = () => {
    setClosing(true);
    onCollapseStart();
    window.setTimeout(onCollapse, PANEL_MOTION_MS);
  };

  return (
    <motion.div
      className="flex shrink-0"
      initial={{ width: 0, opacity: 0 }}
      animate={{ width: closing ? 0 : width + 1, opacity: closing ? 0 : 1 }}
      transition={{ duration: resizing ? 0 : PANEL_MOTION_SECONDS, ease: [0.22, 1, 0.36, 1] }}
      style={{ overflow: "clip", pointerEvents: closing ? "none" : undefined }}
    >
      <motion.aside className="relative flex shrink-0 flex-col border-l" initial={{ x: 48 }} animate={{ x: closing ? 28 : 0 }} transition={{ duration: resizing ? 0 : PANEL_MOTION_SECONDS, ease: [0.22, 1, 0.36, 1] }} style={{ width, background: theme.node.panel, borderColor: theme.node.stroke, color: theme.node.text }}>
        <button type="button" className="absolute inset-y-0 left-0 z-40 w-4 -translate-x-1/2 cursor-col-resize" onMouseDown={startResize} aria-label="调整右侧面板宽度" />
        <div className="flex items-center justify-between border-b px-4 py-3" style={{ borderColor: theme.node.stroke }}>
          <div className="flex items-center gap-2 text-sm font-medium"><Sparkles className="size-4" />{view === "history" ? "历史记录" : "画布助手(未开发)"}</div>
          <div className="flex items-center gap-1">
            {view === "history" ? (
              <>
                <Tooltip title="删除选中">
                  <Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={iconButtonStyle} icon={<Trash2 className="size-4" />} disabled={!checkedChatIds.length} onClick={() => setDeleteChatIds(checkedChatIds)} />
                </Tooltip>
                <Tooltip title="删除全部">
                  <Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={iconButtonStyle} icon={<X className="size-4" />} disabled={!historySessions.length} onClick={() => setDeleteChatIds(historySessions.map((session) => session.id))} />
                </Tooltip>
              </>
            ) : null}
            <Tooltip title={view === "history" ? "返回对话" : "历史记录"}>
              <Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={iconButtonStyle} icon={<History className="size-4" />} onClick={() => setView(view === "history" ? "chat" : "history")} />
            </Tooltip>
            <Tooltip title="新对话">
              <Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={iconButtonStyle} icon={<Plus className="size-4" />} disabled={!hasMessages} onClick={() => { startChatSession(); setView("chat"); }} />
            </Tooltip>
            <Tooltip title="配置">
              <Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={iconButtonStyle} icon={<Settings2 className="size-4" />} onClick={() => openConfigDialog(false)} />
            </Tooltip>
            <Tooltip title="收起对话">
              <Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={iconButtonStyle} icon={<PanelRightClose className="size-4" />} onClick={collapse} />
            </Tooltip>
          </div>
        </div>

        <div className="thin-scrollbar min-h-0 flex-1 space-y-4 overflow-y-auto px-4 py-4">
          {view === "history" ? (
            <AssistantHistory
              sessions={historySessions}
              activeSession={activeSession}
              checkedIds={checkedChatIds.filter((id) => historySessions.some((session) => session.id === id))}
              onToggleChecked={(id, checked) => setCheckedChatIds((prev) => checked ? [...new Set([...prev, id])] : prev.filter((item) => item !== id))}
              onOpen={(id) => { setLocalActiveSessionId(id); setView("chat"); }}
              onDelete={(id) => setDeleteChatIds([id])}
            />
          ) : messages.length ? (
            <AssistantMessages messages={messages} onRetry={retryMessage} onInsertImage={onInsertImage} onInsertText={onInsertText} />
          ) : (
            <div className="flex h-full flex-col items-center justify-center px-1 text-center">
              <div className="relative font-serif text-4xl font-bold italic tracking-normal" style={{ color: theme.node.text }}>
                <span>Infinite Canvas</span>
                <DiaTextReveal
                  className="absolute inset-0"
                  colors={["#A97CF8", "#F38CB8", "#FDCC92"]}
                  textColor="transparent"
                  duration={1.8}
                  startOnView={false}
                  text="Infinite Canvas"
                />
              </div>
              <div className="mt-3 font-serif text-base italic tracking-wide opacity-60">One canvas, infinite ideas</div>
            </div>
          )}
        </div>

        {view === "chat" ? (
          <AssistantComposer
            mode={mode}
            prompt={prompt}
            isRunning={isRunning}
            references={selectedReferences}
            config={config}
            onModeChange={setMode}
            onPromptChange={setPrompt}
            onSubmit={submit}
            onConfigChange={updateConfig}
            onMissingConfig={() => openConfigDialog(true)}
            onRemoveReference={(id) => {
              setRemovedReferenceIds((prev) => new Set(prev).add(id));
              if (selectedNodeIds.has(id)) onSelectNodeIds(new Set(Array.from(selectedNodeIds).filter((nodeId) => nodeId !== id)));
            }}
            onPasteImage={onPasteImage}
          />
        ) : null}

        <Modal
          title="删除对话记录？"
          open={deleteChatIds.length > 0}
          centered
          onCancel={() => setDeleteChatIds([])}
          footer={<>
            <Button onClick={() => setDeleteChatIds([])}>取消</Button>
            <Button
              danger
              type="primary"
              onClick={() => {
                deleteChatIds.length === historySessions.length ? clearSessions() : removeSessions(deleteChatIds);
                setDeleteChatIds([]);
              }}
            >
              删除
            </Button>
          </>}
        >
          <p className="text-sm opacity-60">将删除 {deleteChatIds.length} 条对话记录，此操作不可撤销。</p>
        </Modal>
      </motion.aside>
    </motion.div>
  );
}

function AssistantComposer({
  mode,
  prompt,
  isRunning,
  references,
  config,
  onModeChange,
  onPromptChange,
  onSubmit,
  onConfigChange,
  onMissingConfig,
  onRemoveReference,
  onPasteImage,
}: {
  mode: AssistantMode;
  prompt: string;
  isRunning: boolean;
  references: CanvasAssistantReference[];
  config: AiConfig;
  onModeChange: (mode: AssistantMode) => void;
  onPromptChange: (prompt: string) => void;
  onSubmit: () => void;
  onConfigChange: (key: keyof AiConfig, value: string) => void;
  onMissingConfig: () => void;
  onRemoveReference: (id: string) => void;
  onPasteImage: (file: File) => void;
}) {
  const theme = canvasThemes[useThemeStore((state) => state.theme)];

  return (
    <div className="px-2 pb-2" onWheelCapture={(event) => event.stopPropagation()}>
      {references.length ? (
        <div className="thin-scrollbar mb-1.5 flex max-w-full gap-1.5 overflow-x-auto px-1 pb-1">
          {references.map((item) => <AssistantReferenceChip key={item.id} item={item} onRemove={() => onRemoveReference(item.id)} />)}
        </div>
      ) : null}
      <div className="rounded-[28px] border px-3 pb-3 pt-3 shadow-lg" style={{ background: theme.toolbar.panel, borderColor: theme.node.stroke }}>
        <textarea
          value={prompt}
          onChange={(event) => onPromptChange(event.target.value)}
          onPaste={(event) => {
            const file = Array.from(event.clipboardData.files).find((item) => item.type.startsWith("image/"));
            if (!file) return;
            event.preventDefault();
            onPasteImage(file);
          }}
          onKeyDown={(event) => {
            if (event.key !== "Enter" || event.ctrlKey || event.metaKey || event.shiftKey) return;
            event.preventDefault();
            void onSubmit();
          }}
          className="thin-scrollbar h-20 w-full resize-none border-0 bg-transparent px-1 py-1 text-sm leading-5 outline-none placeholder:text-stone-400"
          style={{ color: theme.node.text }}
          placeholder={mode === "image" ? "描述你想生成或修改的图片" : "输入你想问的问题"}
        />
        <div className="mt-2 flex items-center justify-between gap-2">
          <div className="flex min-w-0 flex-1 items-center gap-1.5">
            <CanvasPromptLibrary onSelect={onPromptChange} />
            <CanvasThemeProvider theme={theme}>
              <Segmented
                size="small"
                className="canvas-composer-mode !rounded-full !p-0.5"
                value={mode}
                onChange={(value) => onModeChange(value as AssistantMode)}
                options={[
                  { value: "ask", label: <Tooltip title="文本"><MessageSquare className="size-4" /></Tooltip> },
                  { value: "image", label: <Tooltip title="生图"><ImageIcon className="size-4" /></Tooltip> },
                ]}
              />
            </CanvasThemeProvider>
            {mode === "image" ? (
              <ImageSettingsPopover config={config} onConfigChange={onConfigChange} onMissingConfig={onMissingConfig} />
            ) : (
              <ModelPicker config={config} value={config.textModel || config.model} onChange={(model) => onConfigChange("textModel", model)} onMissingConfig={onMissingConfig} />
            )}
          </div>
          <Button type="primary" shape="circle" className="!h-10 !w-10 !min-w-10 shrink-0" icon={isRunning ? <LoaderCircle className="size-4 animate-spin" /> : <ArrowUp className="size-4" />} disabled={isRunning || !prompt.trim()} onClick={() => void onSubmit()} aria-label="发送" />
        </div>
      </div>
    </div>
  );
}

function ImageSettingsPopover({ config, onConfigChange, onMissingConfig }: { config: AiConfig; onConfigChange: (key: keyof AiConfig, value: string) => void; onMissingConfig: () => void }) {
  const theme = canvasThemes[useThemeStore((state) => state.theme)];
  const quality = config.quality || "auto";
  const count = String(Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1))));
  const sizes = ["1:1", "3:2", "2:3", "16:9", "9:16", "auto"];
  const customSize = sizes.includes(config.size || "auto") ? "" : config.size;
  return (
    <Popover
      trigger="click"
      placement="topLeft"
      arrow={false}
      overlayClassName="canvas-image-settings-popover"
      color={theme.toolbar.panel}
      content={(
        <CanvasThemeProvider theme={theme}>
          <div className="w-[330px] space-y-4" style={{ color: theme.node.text }} onMouseDown={(event) => event.stopPropagation()}>
            <div className="text-base font-semibold">图像设置</div>
            <div className="space-y-1.5">
              <SettingTitle color={theme.node.muted}>模型</SettingTitle>
              <ModelPicker className="!h-9 !w-full !max-w-none" config={config} value={config.imageModel || config.model} onChange={(model) => onConfigChange("imageModel", model)} onMissingConfig={onMissingConfig} fullWidth />
            </div>
            <div className="space-y-1.5">
              <SettingTitle color={theme.node.muted}>质量</SettingTitle>
              <Segmented block value={quality} onChange={(value) => onConfigChange("quality", String(value))} options={[{ value: "auto", label: "自动" }, { value: "high", label: "高" }, { value: "medium", label: "中" }, { value: "low", label: "低" }]} />
            </div>
            <div className="space-y-1.5">
              <SettingTitle color={theme.node.muted}>宽高比</SettingTitle>
              <div className="grid grid-cols-4 gap-1.5">
                {sizes.map((size) => (
                  <Button key={size} size="small" type={(config.size || "auto") === size ? "primary" : "default"} className="!h-7 !rounded-md !px-1.5" onClick={() => onConfigChange("size", size)}>
                    {size}
                  </Button>
                ))}
                <Input size="small" className="col-span-2 !h-7" placeholder="自定义比例" value={customSize} onChange={(event) => onConfigChange("size", event.target.value.trim() || "auto")} />
              </div>
            </div>
            <div className="space-y-1.5">
              <SettingTitle color={theme.node.muted}>生成数量</SettingTitle>
              <div className="grid grid-cols-[minmax(0,1fr)_92px] gap-2">
                <Segmented block value={["1", "2", "3", "4"].includes(count) ? count : undefined} onChange={(value) => onConfigChange("count", String(value))} options={["1", "2", "3", "4"].map((value) => ({ value, label: `${value} 张` }))} />
                <InputNumber min={1} max={15} size="small" className="canvas-control-number !h-8 !w-full" value={Number(count)} onChange={(value) => onConfigChange("count", String(value || 1))} />
              </div>
            </div>
          </div>
        </CanvasThemeProvider>
      )}
    >
      <Button size="small" type="text" className="!h-8 !max-w-[180px] !justify-start !rounded-full !px-2.5" style={{ background: theme.node.fill, color: theme.node.text }} icon={<Settings2 className="size-3.5" />}>
        <span className="truncate">{qualityLabel(quality)} · {config.size || "auto"} · {count} 张</span>
      </Button>
    </Popover>
  );
}

function CanvasThemeProvider({ theme, children }: { theme: (typeof canvasThemes)[keyof typeof canvasThemes]; children: ReactNode }) {
  return (
    <ConfigProvider theme={{ token: { colorBgContainer: theme.toolbar.panel, colorBgElevated: theme.toolbar.panel, colorBorder: theme.node.stroke, colorPrimary: theme.node.activeStroke, colorText: theme.node.text, colorTextLightSolid: theme.node.panel }, components: { Button: { defaultBg: theme.toolbar.panel, defaultBorderColor: theme.node.stroke, defaultColor: theme.node.text }, Input: { activeBorderColor: theme.node.activeStroke, hoverBorderColor: theme.node.activeStroke }, InputNumber: { activeBorderColor: theme.node.activeStroke, hoverBorderColor: theme.node.activeStroke }, Segmented: { itemColor: theme.node.text, itemHoverBg: theme.node.fill, itemSelectedBg: theme.node.activeStroke, itemSelectedColor: theme.node.panel, trackBg: theme.toolbar.panel, trackPadding: 2 } } }}>
      {children}
    </ConfigProvider>
  );
}

function SettingTitle({ children, color }: { children: string; color: string }) {
  return <div className="text-xs font-medium" style={{ color }}>{children}</div>;
}

function qualityLabel(value: string) {
  return ({ auto: "自动", high: "高", medium: "中", low: "低" } as Record<string, string>)[value] || value;
}

function AssistantMessages({
  messages,
  onRetry,
  onInsertImage,
  onInsertText,
}: {
  messages: CanvasAssistantMessage[];
  onRetry: (message: CanvasAssistantMessage) => void;
  onInsertImage: (image: CanvasAssistantImage) => void;
  onInsertText: (text: string) => void;
}) {
  const theme = canvasThemes[useThemeStore((state) => state.theme)];

  return (
    <>
      {messages.map((message) => (
        <div key={message.id} className={cn("flex flex-col gap-2", message.role === "user" ? "items-end" : "items-start")}>
          <div className="max-w-[88%] whitespace-pre-wrap rounded-2xl px-3 py-2 text-sm leading-6" style={message.role === "user" ? { background: theme.toolbar.activeBg, color: theme.toolbar.activeText } : { background: theme.node.fill, color: theme.node.text }}>
            {message.role === "assistant" ? <div className="mb-1 flex items-center gap-1.5 text-xs opacity-60"><MessageSquare className="size-3.5" />回答</div> : null}
            {message.text}
          </div>
          {message.references?.length ? <MessageReferences message={message} /> : null}
          {message.isLoading ? <ImageGenerationPending compact label={message.mode === "image" ? "正在生成图片" : "正在回答"} className="w-[250px] rounded-2xl border" /> : null}
          {message.role === "assistant" && !message.isLoading ? (
            <div className="flex gap-1">
              <Button shape="circle" size="small" style={{ borderColor: theme.node.stroke }} icon={<RotateCcw className="size-3.5" />} onClick={() => onRetry(message)} title="重试" />
              {!message.images?.length ? <Button shape="circle" size="small" style={{ borderColor: theme.node.stroke }} icon={<Plus className="size-3.5" />} onClick={() => onInsertText(message.text)} title="插入画布" /> : null}
            </div>
          ) : null}
          {message.images?.map((image) => (
            <div key={image.id} className="w-[250px] overflow-hidden rounded-2xl border" style={{ background: theme.node.panel, borderColor: theme.node.stroke }}>
              <img src={image.dataUrl} alt="" className="aspect-square w-full object-cover" />
              <Button type="text" className="!h-8 !w-full !rounded-none" style={{ borderTop: `1px solid ${theme.node.stroke}`, color: theme.node.text }} icon={<Plus className="size-3.5" />} onClick={() => onInsertImage(image)} title="插入画布" />
            </div>
          ))}
        </div>
      ))}
    </>
  );
}

function AssistantHistory({
  sessions,
  activeSession,
  checkedIds,
  onToggleChecked,
  onOpen,
  onDelete,
}: {
  sessions: CanvasAssistantSession[];
  activeSession: CanvasAssistantSession | null;
  checkedIds: string[];
  onToggleChecked: (id: string, checked: boolean) => void;
  onOpen: (id: string) => void;
  onDelete: (id: string) => void;
}) {
  const theme = canvasThemes[useThemeStore((state) => state.theme)];

  return (
    <div className="space-y-1">
      {sessions.map((session) => (
        <div key={session.id} className="group flex items-center gap-2 rounded-lg px-2 py-1.5 transition hover:bg-black/5 dark:hover:bg-white/10" style={session.id === activeSession?.id ? { background: theme.node.fill } : undefined}>
          <input type="checkbox" className="size-4 accent-stone-950" checked={checkedIds.includes(session.id)} onChange={(event) => onToggleChecked(session.id, event.target.checked)} />
          <button type="button" className="min-w-0 flex-1 text-left text-sm" onClick={() => onOpen(session.id)}>
            <span className="block truncate">{session.title}</span>
            <span className="text-xs opacity-50">{session.messages.length} 条消息</span>
          </button>
          <Button type="text" shape="circle" size="small" className="opacity-0 transition group-hover:opacity-100" icon={<Trash2 className="size-3.5" />} onClick={() => onDelete(session.id)} title="删除" />
        </div>
      ))}
    </div>
  );
}

function MessageReferences({ message }: { message: CanvasAssistantMessage }) {
  return (
    <div className={cn("flex max-w-[88%] flex-wrap gap-2", message.role === "user" ? "justify-end" : "justify-start")}>
      {message.references?.map((item) => <AssistantReferenceChip key={item.id} item={item} />)}
    </div>
  );
}

function AssistantReferenceChip({ item, onRemove }: { item: CanvasAssistantReference; onRemove?: () => void }) {
  const theme = canvasThemes[useThemeStore((state) => state.theme)];
  const text = (item.text || item.title).replace(/\s+/g, " ").trim().slice(0, 1) || "文";
  return (
    <div className="group/chip relative inline-flex h-8 max-w-[150px] shrink-0 items-center gap-1.5 rounded-lg text-sm" style={{ color: theme.node.text }}>
      {item.dataUrl ? <img src={item.dataUrl} alt="" className="size-8 rounded-lg object-cover" /> : <span className="grid size-8 place-items-center rounded-lg border text-sm font-medium" style={{ background: theme.node.panel, borderColor: theme.node.activeStroke }}>{text}</span>}
      {onRemove ? (
        <button type="button" className="absolute -right-1 -top-1 grid size-4 place-items-center rounded-full border opacity-0 shadow-sm transition group-hover/chip:opacity-100" style={{ background: theme.toolbar.panel, borderColor: theme.node.stroke }} onClick={onRemove} aria-label="移除引用">
          <X className="size-3" />
        </button>
      ) : null}
    </div>
  );
}

function nodeToReference(node: CanvasNodeData): CanvasAssistantReference | null {
  if (node.type === CanvasNodeType.Image && node.metadata?.content) {
    return { id: node.id, type: node.type, title: node.title, dataUrl: node.metadata.content, storageKey: node.metadata.storageKey };
  }
  if (node.type === CanvasNodeType.Text && node.metadata?.content) {
    return { id: node.id, type: node.type, title: node.title, text: node.metadata.content };
  }
  return null;
}

function buildAssistantReferences(nodes: CanvasNodeData[], selectedNodeIds: Set<string>) {
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
  return Array.from(selectedNodeIds)
    .map((id) => nodeById.get(id))
    .filter((node): node is CanvasNodeData => Boolean(node))
    .map(nodeToReference)
    .filter((item): item is CanvasAssistantReference => Boolean(item));
}

async function buildChatMessages(messages: CanvasAssistantMessage[]): Promise<ChatCompletionMessage[]> {
  return Promise.all(messages.map(async (message, index) => {
    if (message.role === "assistant") return { role: "assistant", content: message.text };
    if (index !== messages.length - 1) return { role: "user", content: message.text };
    const refs = message.references || [];
    return {
      role: "user",
      content: [
        ...refs.flatMap((item) => item.text ? [{ type: "text" as const, text: item.text }] : []),
        { type: "text", text: message.text },
        ...(await Promise.all(refs.filter((item) => item.dataUrl).map(async (item) => ({ type: "image_url" as const, image_url: { url: await imageToDataUrl(item) } })))),
      ],
    };
  }));
}

function createSession(): CanvasAssistantSession {
  const now = new Date().toISOString();
  return { id: createId(), title: "新对话", messages: [], createdAt: now, updatedAt: now };
}
