"use client";

import { BookOpen, CheckSquare, ClipboardPaste, Download, FolderPlus, History, ImagePlus, LoaderCircle, PenLine, Plus, SlidersHorizontal, Sparkles, Trash2, Upload } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { App, AutoComplete, Button, Checkbox, Drawer, Empty, Image, Input, InputNumber, Modal, Select, Tag, Typography } from "antd";
import localforage from "localforage";

import { ModelPicker } from "@/components/model-picker";
import { PromptSelectDialog } from "@/components/prompts/prompt-select-dialog";
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
import { isAiConfigReady, useConfigStore, useEffectiveAiConfig, type AiConfig } from "@/stores/use-config-store";
import { createId } from "@/lib/id";
import { formatBytes, formatDuration, getDataUrlByteSize, readImageMeta } from "@/lib/image-utils";
import { requestEdit, requestGeneration } from "@/services/api/image";
import { uploadImage } from "@/services/image-storage";
import { useAssetStore } from "@/stores/use-asset-store";
import type { ReferenceImage } from "@/types/image";

type GeneratedImage = {
  id: string;
  dataUrl: string;
  durationMs: number;
  width: number;
  height: number;
  bytes: number;
};

type GenerationResult = {
  id: string;
  status: "pending" | "success" | "failed";
  image?: GeneratedImage;
  error?: string;
};

type GenerationLog = {
  id: string;
  createdAt: number;
  title: string;
  time: string;
  model: string;
  durationMs: number;
  successCount: number;
  failCount: number;
  imageCount: number;
  size: string;
  quality: string;
  status: "成功" | "失败";
  thumbnails: string[];
};

type UpdateAiConfig = <K extends keyof AiConfig>(key: K, value: AiConfig[K]) => void;

const sizeOptions = ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16"].map((value) => ({ label: value, value }));
const qualityOptions = ["auto", "low", "medium", "high"].map((value) => ({ label: value, value }));
const LOG_STORE_KEY = "infinite-canvas:image_generation_logs";
const LOG_STORE_PREFIX = `${LOG_STORE_KEY}:`;
const logStore = localforage.createInstance({ name: "infinite-canvas", storeName: "image_generation_logs" });

export default function ImagePage() {
  const { message } = App.useApp();
  const fileInputRef = useRef<HTMLInputElement>(null);
  const config = useConfigStore((state) => state.config);
  const effectiveConfig = useEffectiveAiConfig(config);
  const updateConfig = useConfigStore((state) => state.updateConfig);
  const openConfigDialog = useConfigStore((state) => state.openConfigDialog);
  const addAsset = useAssetStore((state) => state.addAsset);
  const [prompt, setPrompt] = useState("");
  const [references, setReferences] = useState<ReferenceImage[]>([]);
  const [results, setResults] = useState<GenerationResult[]>([]);
  const [logs, setLogs] = useState<GenerationLog[]>([]);
  const [running, setRunning] = useState(false);
  const [logsOpen, setLogsOpen] = useState(false);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [promptDialogOpen, setPromptDialogOpen] = useState(false);
  const [assetPickerOpen, setAssetPickerOpen] = useState(false);
  const [startedAt, setStartedAt] = useState(0);
  const [elapsedMs, setElapsedMs] = useState(0);
  const [selectedLogIds, setSelectedLogIds] = useState<string[]>([]);
  const [previewLog, setPreviewLog] = useState<GenerationLog | null>(null);
  const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);

  const model = effectiveConfig.imageModel || effectiveConfig.model;
  const canGenerate = Boolean(prompt.trim());
  const generationCount = Math.max(1, Math.min(10, Number(config.count) || 1));

  useEffect(() => {
    if (!running || !startedAt) return;
    const timer = window.setInterval(() => setElapsedMs(performance.now() - startedAt), 1000);
    return () => window.clearInterval(timer);
  }, [running, startedAt]);

  useEffect(() => {
    void refreshLogs();
  }, []);

  const addReferences = async (files?: FileList | null) => {
    const imageFiles = Array.from(files || []).filter((file) => file.type.startsWith("image/"));
    const nextReferences = await Promise.all(imageFiles.map(async (file) => {
      const image = await uploadImage(file);
      return { id: createId(), name: file.name, type: image.mimeType, dataUrl: image.url, storageKey: image.storageKey };
    }));
    setReferences((value) => [...value, ...nextReferences]);
  };

  const addReferencesFromClipboard = async () => {
    try {
      const items = await navigator.clipboard.read();
      const blobs = await Promise.all(items.flatMap((item) => item.types.filter((type) => type.startsWith("image/")).map((type) => item.getType(type))));
      if (!blobs.length) {
        message.error("剪切板里没有可读取的图片");
        return;
      }
      const nextReferences = await Promise.all(blobs.map(async (blob, index) => {
        const image = await uploadImage(blob);
        return { id: createId(), name: `clipboard-${index + 1}.png`, type: image.mimeType, dataUrl: image.url, storageKey: image.storageKey };
      }));
      setReferences((value) => [...value, ...nextReferences]);
      message.success(`已读取 ${nextReferences.length} 张参考图`);
    } catch {
      message.error("剪切板里没有可读取的图片");
    }
  };

  const generate = async () => {
    const text = prompt.trim();
    if (!text) {
      message.error("请输入生图提示词");
      return;
    }
    if (!isAiConfigReady(effectiveConfig, model)) {
      message.warning("请先完成配置");
      openConfigDialog(true);
      return;
    }

    const snapshot = buildRequestSnapshot();
    if (!snapshot) return;

    setElapsedMs(0);
    setRunning(true);
    setPreviewLog(null);
    setResults(Array.from({ length: generationCount }, () => ({ id: createId(), status: "pending" })));
    const batchStartedAt = performance.now();
    setStartedAt(batchStartedAt);

    const tasks = Array.from({ length: generationCount }, (_, index) => runGenerationSlot(index, snapshot));

    const result = await Promise.allSettled(tasks);
    const successImages = result
      .filter((item): item is PromiseFulfilledResult<GeneratedImage> => item.status === "fulfilled")
      .map((item) => item.value);
    const successCount = successImages.length;
    const failCount = generationCount - successCount;
    const failed = result.find((item): item is PromiseRejectedResult => item.status === "rejected");

    try {
      saveLog(buildLog({ prompt: text, model, config: { ...snapshot.config, count: String(generationCount) }, durationMs: performance.now() - batchStartedAt, successCount, failCount, status: successCount ? "成功" : "失败", thumbnails: successImages.map((image) => image.dataUrl) }));
      successCount ? message.success("图片已生成") : message.error(failed?.reason instanceof Error ? failed.reason.message : "生成失败");
    } finally {
      setRunning(false);
    }
  };

  const downloadImage = (image: GeneratedImage, index: number) => {
    const link = document.createElement("a");
    link.href = image.dataUrl;
    link.download = `image-${index + 1}.png`;
    link.click();
  };

  const addResultToReferences = async (image: GeneratedImage, index: number) => {
    const stored = await uploadImage(image.dataUrl);
    setReferences((value) => [...value, { id: createId(), name: `result-${index + 1}.png`, type: stored.mimeType, dataUrl: stored.url, storageKey: stored.storageKey }]);
    message.success("已加入参考图");
  };

  const saveResultToAssets = async (image: GeneratedImage, index: number) => {
    const stored = await uploadImage(image.dataUrl);
    addAsset({
      kind: "image",
      title: `生成结果 ${index + 1}`,
      coverUrl: stored.url,
      tags: [],
      source: "生图工作台",
      data: { dataUrl: stored.url, storageKey: stored.storageKey, width: stored.width, height: stored.height, bytes: stored.bytes, mimeType: stored.mimeType },
      metadata: { source: "image-page", prompt },
    });
    message.success("已加入我的素材");
  };

  const insertPickedAsset = async (payload: InsertAssetPayload) => {
    if (payload.kind === "text") {
      setPrompt(payload.content);
    } else {
      const stored = await uploadImage(payload.dataUrl);
      setReferences((value) => [...value, { id: createId(), name: payload.title, type: stored.mimeType, dataUrl: stored.url, storageKey: stored.storageKey }]);
    }
    setAssetPickerOpen(false);
  };

  const createSession = () => {
    setPrompt("");
    setReferences([]);
    setResults([]);
    setElapsedMs(0);
    setStartedAt(0);
    setSelectedLogIds([]);
    setPreviewLog(null);
  };

  const deleteSelectedLogs = () => {
    void Promise.all(selectedLogIds.map((id) => logStore.removeItem(id))).then(refreshLogs);
    if (previewLog && selectedLogIds.includes(previewLog.id)) {
      setPreviewLog(null);
      setResults([]);
    }
    setSelectedLogIds([]);
    setDeleteConfirmOpen(false);
  };

  const saveLog = (log: GenerationLog) => {
    void logStore.setItem(log.id, log).then(refreshLogs);
  };

  const refreshLogs = async () => setLogs(await readStoredLogs());

  const previewGenerationLog = async (log: GenerationLog) => {
    setPreviewLog(log);
    setLogsOpen(false);
    const images = await Promise.all(log.thumbnails.map(async (dataUrl, index) => {
      const meta = await readImageMeta(dataUrl);
      return { id: `${log.id}-${index}`, dataUrl, durationMs: log.durationMs, width: meta.width, height: meta.height, bytes: getDataUrlByteSize(dataUrl) };
    }));
    setResults(images.map((image) => ({ id: image.id, status: "success", image })));
  };

  const buildRequestSnapshot = () => {
    const text = prompt.trim();
    if (!text) {
      message.error("请输入生图提示词");
      return null;
    }
    if (!isAiConfigReady(effectiveConfig, model)) {
      message.warning("请先完成配置");
      openConfigDialog(true);
      return null;
    }
    return { text, config: { ...effectiveConfig, model, count: "1" }, references: [...references] };
  };

  const runGenerationSlot = async (index: number, snapshot: { text: string; config: AiConfig; references: ReferenceImage[] }) => {
    const itemStartedAt = performance.now();
    try {
      const result = snapshot.references.length ? await requestEdit(snapshot.config, snapshot.text, snapshot.references) : await requestGeneration(snapshot.config, snapshot.text);
      const image = result[0];
      if (!image) throw new Error("接口没有返回图片");
      const meta = await readImageMeta(image.dataUrl);
      const nextImage = { id: image.id, dataUrl: image.dataUrl, durationMs: performance.now() - itemStartedAt, width: meta.width, height: meta.height, bytes: getDataUrlByteSize(image.dataUrl) };
      setResults((value) => updateResultAt(value, index, { status: "success", image: nextImage }));
      return nextImage;
    } catch (error) {
      setResults((value) => updateResultAt(value, index, { status: "failed", error: error instanceof Error ? error.message : "生成失败" }));
      throw error;
    }
  };

  const retryResult = (index: number) => {
    const snapshot = buildRequestSnapshot();
    if (!snapshot) return;
    setPreviewLog(null);
    setResults((value) => updateResultAt(value, index, { status: "pending", error: undefined, image: undefined }));
    void runGenerationSlot(index, snapshot).catch(() => {});
  };

  return (
    <div className="flex h-full flex-col overflow-hidden bg-stone-50 text-stone-900 dark:bg-stone-950 dark:text-stone-100">
      <main className="grid min-h-0 flex-1 grid-cols-1 gap-3 overflow-y-auto p-3 lg:grid-cols-[300px_minmax(0,1fr)] lg:overflow-hidden xl:grid-cols-[320px_minmax(0,1fr)]">
        <aside className="thin-scrollbar hidden min-h-0 overflow-y-auto rounded-lg border border-stone-200 bg-card p-4 shadow-sm dark:border-stone-800 lg:block">
          <LogPanel logs={logs} selectedLogIds={selectedLogIds} activeLogId={previewLog?.id} onSelectedLogIdsChange={setSelectedLogIds} onCreateSession={createSession} onDeleteSelected={() => setDeleteConfirmOpen(true)} onPreviewLog={(log) => void previewGenerationLog(log)} />
        </aside>

        <section className="grid gap-3 lg:min-h-0 lg:overflow-hidden xl:grid-cols-[420px_minmax(0,1fr)]">
          <div className="thin-scrollbar flex flex-col rounded-lg border border-stone-200 bg-card p-4 shadow-sm dark:border-stone-800 lg:min-h-0 lg:overflow-y-auto">
              <div>
                <div className="flex items-start justify-between gap-3">
                  <div className="min-w-0">
                    <h1 className="text-2xl font-semibold text-stone-950 dark:text-stone-100">生图工作台</h1>
                  </div>
                  <div className="flex shrink-0 gap-2 lg:hidden">
                    <Button icon={<History className="size-4" />} onClick={() => setLogsOpen(true)}>记录</Button>
                    <Button icon={<SlidersHorizontal className="size-4" />} onClick={() => setSettingsOpen(true)}>参数</Button>
                  </div>
                </div>
              </div>

              <div className="mt-6 space-y-5">
                <div>
                  <div className="mb-2 flex items-center justify-between gap-3">
                    <span className="text-base font-semibold">提示词</span>
                    <div className="flex gap-2">
                      <Button size="small" icon={<BookOpen className="size-3.5" />} onClick={() => setPromptDialogOpen(true)}>查看提示词库</Button>
                      <Button size="small" icon={<FolderPlus className="size-3.5" />} onClick={() => setAssetPickerOpen(true)}>查看我的素材</Button>
                    </div>
                  </div>
                  <Input.TextArea
                    value={prompt}
                    onChange={(event) => setPrompt(event.target.value)}
                    rows={7}
                    placeholder="描述画面主体、风格、构图、光线和用途"
                  />
                </div>

                <div className="min-w-0">
                  <div className="mb-2 flex items-center justify-between gap-3">
                    <span className="text-base font-semibold">参考图</span>
                    <div className="flex gap-2">
                      <Button size="small" icon={<ClipboardPaste className="size-3.5" />} onClick={() => void addReferencesFromClipboard()}>剪切板</Button>
                      <Button size="small" icon={<Upload className="size-3.5" />} onClick={() => fileInputRef.current?.click()}>上传</Button>
                    </div>
                  </div>
                  <div className="hover-scrollbar hover-scrollbar-hint flex min-h-24 w-full min-w-0 max-w-full gap-2 overflow-x-scroll overflow-y-hidden rounded-lg border border-dashed border-stone-300 p-2 pb-3 overscroll-x-contain dark:border-stone-700" onWheel={(event) => {
                    if (event.currentTarget.scrollWidth <= event.currentTarget.clientWidth) return;
                    event.preventDefault();
                    event.currentTarget.scrollLeft += event.deltaY;
                  }}>
                    {references.map((item) => (
                      <div key={item.id} className="group relative size-20 shrink-0 overflow-hidden rounded-md border border-stone-200 dark:border-stone-800">
                        <img src={item.dataUrl} alt={item.name} className="size-full object-cover" />
                        <button type="button" className="absolute right-1 top-1 hidden size-6 items-center justify-center rounded bg-black/60 text-white group-hover:flex" onClick={() => setReferences((value) => value.filter((ref) => ref.id !== item.id))} aria-label="移除参考图">
                          <Trash2 className="size-3.5" />
                        </button>
                      </div>
                    ))}
                    {!references.length ? <div className="flex min-w-full items-center justify-center text-sm text-stone-500">暂无参考图</div> : null}
                  </div>
                </div>

                <div className="flex items-center justify-between rounded-lg border border-stone-200 bg-stone-50 px-3 py-2 text-sm dark:border-stone-800 dark:bg-stone-900 sm:hidden">
                  <span className="truncate text-stone-500 dark:text-stone-400">{model} · {effectiveConfig.size} · {effectiveConfig.quality}</span>
                  <Button size="small" type="text" icon={<SlidersHorizontal className="size-4" />} onClick={() => setSettingsOpen(true)}>调整</Button>
                </div>

                <div className="hidden gap-4 sm:grid sm:grid-cols-2">
                  <GenerationSettings config={effectiveConfig} model={model} updateConfig={updateConfig} openConfigDialog={openConfigDialog} />
                </div>
              </div>

              <div className="mt-auto pt-6">
                <Button type="primary" size="large" block icon={<Sparkles className="size-4" />} loading={running} disabled={!canGenerate || running} onClick={() => void generate()}>
                  开始生成
                </Button>
              </div>
            </div>

          <div className="thin-scrollbar rounded-lg border border-stone-200 bg-card p-4 shadow-sm dark:border-stone-800 lg:min-h-0 lg:overflow-y-auto lg:p-5">
              <div className="mb-4 flex items-center justify-between gap-3">
                <div>
                  <h2 className="text-xl font-semibold">生成结果</h2>
                </div>
                {running ? <Tag className="m-0 px-2 py-1">等待 {formatDuration(elapsedMs)}</Tag> : null}
              </div>
              {results.length ? (
                <div className="grid gap-4 sm:grid-cols-2 2xl:grid-cols-3">
                  {results.map((result, index) => (
                    result.status === "success" && result.image ? (
                      <ResultImageCard key={result.id} image={result.image} index={index} onEdit={addResultToReferences} onDownload={downloadImage} onSaveAsset={saveResultToAssets} />
                    ) : result.status === "failed" ? (
                      <FailedImageCard key={result.id} error={result.error || "生成失败"} onRetry={() => retryResult(index)} />
                    ) : (
                      <PendingImageCard key={result.id} />
                    )
                  ))}
                </div>
              ) : (
                <div className="flex min-h-[320px] flex-col items-center justify-center rounded-lg border border-dashed border-stone-300 text-center dark:border-stone-700 lg:min-h-[560px]">
                  <ImagePlus className="mb-4 size-11 text-stone-400" />
                  <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="还没有生成图片" />
                </div>
              )}
          </div>
        </section>
      </main>
      <input ref={fileInputRef} type="file" accept="image/*" multiple className="hidden" onChange={(event) => {
        void addReferences(event.target.files);
        event.target.value = "";
      }} />
      <Drawer title="生成记录" placement="bottom" size="large" open={logsOpen} onClose={() => setLogsOpen(false)}>
        <LogPanel logs={logs} selectedLogIds={selectedLogIds} activeLogId={previewLog?.id} onSelectedLogIdsChange={setSelectedLogIds} onCreateSession={createSession} onDeleteSelected={() => setDeleteConfirmOpen(true)} onPreviewLog={(log) => void previewGenerationLog(log)} />
      </Drawer>
      <Drawer title="参数" placement="bottom" size="default" open={settingsOpen} onClose={() => setSettingsOpen(false)}>
        <div className="grid grid-cols-2 gap-3">
          <GenerationSettings config={effectiveConfig} model={model} updateConfig={updateConfig} openConfigDialog={openConfigDialog} />
        </div>
      </Drawer>
      <PromptSelectDialog open={promptDialogOpen} onOpenChange={setPromptDialogOpen} onSelect={setPrompt} />
      <AssetPickerModal open={assetPickerOpen} defaultTab="my-assets" onInsert={(payload) => void insertPickedAsset(payload)} onClose={() => setAssetPickerOpen(false)} />
      <Modal title="删除生成记录" open={deleteConfirmOpen} onCancel={() => setDeleteConfirmOpen(false)} onOk={deleteSelectedLogs} okText="删除" okButtonProps={{ danger: true }} cancelText="取消">
        确定删除选中的 {selectedLogIds.length} 条生成记录吗？
      </Modal>
    </div>
  );
}

function GenerationSettings({ config, model, updateConfig, openConfigDialog }: { config: AiConfig; model: string; updateConfig: UpdateAiConfig; openConfigDialog: (shouldPromptContinue?: boolean) => void }) {
  return (
    <>
      <label className="col-span-2 block min-w-0 sm:col-span-1">
        <span className="mb-1.5 block text-sm font-semibold sm:mb-2 sm:text-base">模型</span>
        <ModelPicker config={config} value={model} onChange={(value) => updateConfig("imageModel", value)} fullWidth onMissingConfig={() => openConfigDialog(false)} />
      </label>
      <label className="block">
        <span className="mb-1.5 block text-sm font-semibold sm:mb-2 sm:text-base">生成次数</span>
        <InputNumber className="canvas-control-number !w-full" min={1} max={10} value={Number(config.count) || 1} onChange={(value) => updateConfig("count", String(value || 1))} />
      </label>
      <label className="block">
        <span className="mb-1.5 block text-sm font-semibold sm:mb-2 sm:text-base">尺寸</span>
        <AutoComplete className="canvas-control-select w-full" value={config.size} options={sizeOptions} placeholder="例如 1:1、3:2" onChange={(value) => updateConfig("size", value)} />
      </label>
      <label className="block">
        <span className="mb-1.5 block text-sm font-semibold sm:mb-2 sm:text-base">质量</span>
        <Select className="canvas-control-select w-full" value={config.quality} options={qualityOptions} onChange={(value) => updateConfig("quality", value)} />
      </label>
    </>
  );
}

function ResultImageCard({ image, index, onEdit, onDownload, onSaveAsset }: { image: GeneratedImage; index: number; onEdit: (image: GeneratedImage, index: number) => void; onDownload: (image: GeneratedImage, index: number) => void; onSaveAsset: (image: GeneratedImage, index: number) => void }) {
  return (
    <div className="overflow-hidden rounded-lg border border-stone-200 bg-background dark:border-stone-800">
      <Image src={image.dataUrl} alt={`生成结果 ${index + 1}`} className="aspect-square object-cover" />
      <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-2 border-t border-stone-200 px-3 py-2.5 dark:border-stone-800">
        <div className="flex min-w-0 flex-wrap gap-x-2 gap-y-1 text-xs text-stone-500 dark:text-stone-400">
          <span>{image.width}x{image.height}</span>
          <span>{formatBytes(image.bytes)}</span>
          <span>{formatDuration(image.durationMs)}</span>
        </div>
        <div className="flex shrink-0 gap-1">
          <Button size="small" icon={<FolderPlus className="size-3.5" />} onClick={() => void onSaveAsset(image, index)}>添加到素材</Button>
          <Button size="small" icon={<PenLine className="size-3.5" />} onClick={() => void onEdit(image, index)}>加入参考图</Button>
          <Button size="small" icon={<Download className="size-3.5" />} onClick={() => onDownload(image, index)}>下载</Button>
        </div>
      </div>
    </div>
  );
}

function PendingImageCard() {
  return (
    <div className="relative aspect-square overflow-hidden rounded-lg border border-dashed border-stone-300 bg-stone-50 dark:border-stone-700 dark:bg-stone-900">
      <div
        className="absolute inset-0 opacity-60"
        style={{
          backgroundImage: "radial-gradient(circle, rgba(120,113,108,0.35) 1.4px, transparent 1.6px)",
          backgroundSize: "16px 16px",
        }}
      />
      <div className="absolute inset-0 flex flex-col items-center justify-center gap-2 text-sm text-stone-500 dark:text-stone-400">
        <LoaderCircle className="size-6 animate-spin" />
        <span>生成中</span>
      </div>
    </div>
  );
}

function FailedImageCard({ error, onRetry }: { error: string; onRetry: () => void }) {
  return (
    <div className="overflow-hidden rounded-lg border border-red-200 bg-red-50 dark:border-red-950 dark:bg-red-950/20">
      <div className="flex aspect-square flex-col items-center justify-center gap-3 p-5 text-center">
        <div className="text-sm font-medium text-red-600 dark:text-red-300">生成失败</div>
        <Typography.Paragraph ellipsis={{ rows: 4 }} className="!mb-0 !text-xs !text-red-500 dark:!text-red-300">
          {error}
        </Typography.Paragraph>
      </div>
      <div className="flex justify-end border-t border-red-200 p-3 dark:border-red-950">
        <Button size="small" danger onClick={onRetry}>重试</Button>
      </div>
    </div>
  );
}

function updateResultAt(results: GenerationResult[], index: number, next: Partial<GenerationResult>) {
  return results.map((item, itemIndex) => itemIndex === index ? { ...item, ...next } : item);
}

function LogPanel({ logs, selectedLogIds, activeLogId, onSelectedLogIdsChange, onCreateSession, onDeleteSelected, onPreviewLog }: { logs: GenerationLog[]; selectedLogIds: string[]; activeLogId?: string; onSelectedLogIdsChange: (ids: string[]) => void; onCreateSession: () => void; onDeleteSelected: () => void; onPreviewLog: (log: GenerationLog) => void }) {
  const allSelected = Boolean(logs.length) && selectedLogIds.length === logs.length;
  const toggleAll = () => onSelectedLogIdsChange(allSelected ? [] : logs.map((log) => log.id));

  return (
    <>
      <div className="mb-3 flex items-center justify-between gap-3">
        <div>
          <h2 className="text-base font-semibold">生成记录</h2>
        </div>
        <Tag className="m-0">{logs.length}</Tag>
      </div>
      <div className="mb-4 flex flex-wrap gap-2">
        <Button size="small" icon={<Plus className="size-3.5" />} onClick={onCreateSession}>新建</Button>
        <Button size="small" icon={<CheckSquare className="size-3.5" />} disabled={!logs.length} onClick={toggleAll}>{allSelected ? "取消" : "全选"}</Button>
        <Button size="small" danger icon={<Trash2 className="size-3.5" />} disabled={!selectedLogIds.length} onClick={onDeleteSelected}>删除</Button>
      </div>
      <div className="space-y-3">
        {logs.map((log) => (
          <LogCard
            key={log.id}
            log={log}
            selected={selectedLogIds.includes(log.id)}
            active={activeLogId === log.id}
            onSelectedChange={(checked) => onSelectedLogIdsChange(checked ? [...selectedLogIds, log.id] : selectedLogIds.filter((id) => id !== log.id))}
            onClick={() => onPreviewLog(log)}
          />
        ))}
        {!logs.length ? (
          <div className="flex min-h-48 items-center justify-center rounded-lg border border-dashed border-stone-300 text-center text-sm text-stone-500 dark:border-stone-700">
            暂无生成记录
          </div>
        ) : null}
      </div>
    </>
  );
}

function LogCard({ log, selected, active, onSelectedChange, onClick }: { log: GenerationLog; selected: boolean; active: boolean; onSelectedChange: (checked: boolean) => void; onClick: () => void }) {
  return (
    <button type="button" className={`block w-full rounded-lg border p-2 text-left transition ${active ? "border-stone-900 bg-blue-50 dark:border-stone-100 dark:bg-blue-950/20" : "border-stone-200 bg-background hover:bg-stone-50 dark:border-stone-800 dark:hover:bg-stone-900"}`} onClick={onClick}>
      <div className="grid grid-cols-[minmax(128px,1fr)_auto] gap-2">
        <div className="grid min-w-0 grid-cols-[auto_minmax(0,1fr)] items-start gap-2">
          <Checkbox className="mt-0.5" checked={selected} onClick={(event) => event.stopPropagation()} onChange={(event) => onSelectedChange(event.target.checked)} />
          <div className="min-w-0">
            <div className="truncate text-sm font-semibold leading-5">{log.title}</div>
            {log.thumbnails?.length ? (
              <div className="mt-2 flex gap-1 overflow-hidden">
                {log.thumbnails.slice(0, 4).map((image, index) => <img key={`${log.id}-${index}`} src={image} alt="" className="size-8 shrink-0 rounded-md object-cover" />)}
              </div>
            ) : null}
          </div>
        </div>
        <div className="grid justify-items-end gap-2">
          <div className="flex gap-1">
            <Tag className="m-0 flex h-6 items-center rounded-md px-1.5 text-xs leading-none" color="blue">成功 {log.successCount ?? log.imageCount}</Tag>
            {log.failCount ? <Tag className="m-0 flex h-6 items-center rounded-md px-1.5 text-xs leading-none" color="red">失败 {log.failCount}</Tag> : null}
          </div>
          <div className="flex flex-wrap justify-end gap-1">
            <Tag className="m-0 flex h-6 items-center rounded-md px-1.5 text-xs leading-none">{log.imageCount} 张</Tag>
            <Tag className="m-0 flex h-6 items-center rounded-md px-1.5 text-xs leading-none" color="green">{formatDuration(log.durationMs)}</Tag>
          </div>
          <div className="flex justify-end">
            <Tag className="m-0 flex h-6 items-center rounded-md px-1.5 text-xs leading-none">{log.time}</Tag>
          </div>
        </div>
      </div>
    </button>
  );
}

async function readStoredLogs() {
  if (typeof window === "undefined") return [];
  try {
    const legacyValue = window.localStorage.getItem(LOG_STORE_KEY);
    if (legacyValue) {
      await Promise.all((JSON.parse(legacyValue) as GenerationLog[]).map((log) => logStore.setItem(log.id, normalizeLog(log))));
      window.localStorage.removeItem(LOG_STORE_KEY);
    }
    const legacyKeys = Array.from({ length: window.localStorage.length }, (_, index) => window.localStorage.key(index)).filter((key): key is string => Boolean(key?.startsWith(LOG_STORE_PREFIX)));
    await Promise.all(legacyKeys.map(async (key) => {
      try {
        const log = normalizeLog(JSON.parse(window.localStorage.getItem(key) || ""));
        await logStore.setItem(log.id, log);
        window.localStorage.removeItem(key);
      } catch {}
    }));

    const logs: GenerationLog[] = [];
    await logStore.iterate<GenerationLog, void>((value) => {
      logs.push(normalizeLog(value));
    });
    return logs
      .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
  } catch {
    return [];
  }
}

function normalizeLog(log: Partial<GenerationLog>): GenerationLog {
  return {
    id: log.id || createId(),
    createdAt: log.createdAt || Date.now(),
    title: log.title || log.model || "未命名",
    time: log.time || new Date().toLocaleString("zh-CN", { hour12: false }),
    model: log.model || "",
    durationMs: log.durationMs || 0,
    successCount: log.successCount ?? log.imageCount ?? 0,
    failCount: log.failCount || 0,
    imageCount: log.imageCount || log.successCount || 0,
    size: log.size || "",
    quality: log.quality || "",
    status: log.status || "成功",
    thumbnails: log.thumbnails || [],
  };
}

function buildLog({ prompt, model, config, durationMs, successCount, failCount, status, thumbnails }: { prompt: string; model: string; config: { size: string; quality: string; count?: string }; durationMs: number; successCount: number; failCount: number; status: GenerationLog["status"]; thumbnails: string[] }): GenerationLog {
  return {
    id: createId(),
    createdAt: Date.now(),
    title: prompt.slice(0, 12) || "未命名",
    time: new Date().toLocaleString("zh-CN", { hour12: false }),
    model,
    durationMs,
    successCount,
    failCount,
    imageCount: Number(config.count) || successCount,
    size: config.size,
    quality: config.quality,
    status,
    thumbnails,
  };
}
