"use client"

import { type MouseEvent as ReactMouseEvent, type ReactNode, useEffect, useMemo, useRef, useState } from "react"
import { createPortal } from "react-dom"
import {
  BookOpen, Check, Copy, Database, Download, Edit3, FileText, Image as ImageIcon, Loader2,
  Package, Plus, Search, Sparkles, Trash2, Upload, Video, X,
} from "lucide-react"
import { toast } from "sonner"
import { MediaAssetTile } from "@/components/media-asset-tile"
import {
  type AssetLibraryItem,
  type AssetLibraryKind,
  type ImageRef,
  type PromptLibraryCategory,
  type PromptLibraryItem,
  type ResourceLibraryRecentItem,
  apiAssetUrl,
  copyAssetLibraryToJob,
  createAssetLibraryItem,
  createPromptLibraryItem,
  deleteAssetLibraryItem,
  deletePromptLibraryItem,
  getAssetLibraryRefs,
  getResourceLibraryRecent,
  listAssetLibrary,
  listPromptLibrary,
  usePromptLibraryItem,
} from "@/lib/api"

type LibraryTab = "prompts" | "assets"
type LibraryApplyTarget = "copy_only" | "product_pool"

type LibraryDrawerProps = {
  open: boolean
  currentJobId?: string
  onClose: () => void
  onApplyAsset?: (kind: AssetLibraryKind, ref: ImageRef, target: LibraryApplyTarget, item: AssetLibraryItem) => Promise<void> | void
}

const DRAWER_STORAGE_KEY = "skg-resource-library-drawer"
const PROMPT_COLUMNS: Array<{ category: PromptLibraryCategory; label: string; desc: string }> = [
  { category: "scene_desc", label: "场景描述", desc: "首尾帧、场景图、环境描述" },
  { category: "video_desc", label: "视频描述", desc: "视频生成动作、镜头语言" },
  { category: "subject_desc", label: "主体描述", desc: "人物、透明骨架、角色 brief" },
  { category: "skg_script", label: "SKG 文案", desc: "口播、卖点、作者意图" },
  { category: "product_angle", label: "产品角度", desc: "视角、佩戴、结构约束" },
]
const ASSET_COLUMNS: Array<{ kind: AssetLibraryKind; label: string; icon: ReactNode }> = [
  { kind: "subjects", label: "主体", icon: <Sparkles className="h-3.5 w-3.5" /> },
  { kind: "products", label: "产品", icon: <Package className="h-3.5 w-3.5" /> },
  { kind: "scenes", label: "场景", icon: <ImageIcon className="h-3.5 w-3.5" /> },
  { kind: "videos", label: "视频", icon: <Video className="h-3.5 w-3.5" /> },
]

function cn(...items: Array<string | false | null | undefined>) {
  return items.filter(Boolean).join(" ")
}

function formatAgo(ts?: number) {
  if (!ts) return "-"
  const diff = Math.max(0, Date.now() / 1000 - ts)
  if (diff < 3600) return `${Math.max(1, Math.floor(diff / 60))} 分钟前`
  if (diff < 86400) return `${Math.floor(diff / 3600)} 小时前`
  return `${Math.floor(diff / 86400)} 天前`
}

function monthLabel(ts?: number) {
  const date = ts ? new Date(ts * 1000) : new Date()
  return `${date.getFullYear()} 年 ${date.getMonth() + 1} 月`
}

function matchesPrompt(item: PromptLibraryItem, q: string) {
  const needle = q.trim().toLowerCase()
  if (!needle) return true
  return [item.name, item.prompt_en, item.prompt_zh, item.tags.join(" ")].join(" ").toLowerCase().includes(needle)
}

function matchesAsset(item: AssetLibraryItem, q: string) {
  const needle = q.trim().toLowerCase()
  if (!needle) return true
  return [item.name, item.name_zh, item.note, item.prompt_brief, item.prompt_brief_zh, item.tags.join(" ")].join(" ").toLowerCase().includes(needle)
}

function groupByMonth<T extends { created_at?: number }>(items: T[]) {
  const groups = new Map<string, T[]>()
  for (const item of [...items].sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0))) {
    const label = monthLabel(item.created_at)
    groups.set(label, [...(groups.get(label) ?? []), item])
  }
  return [...groups.entries()]
}

function assetThumb(item: AssetLibraryItem) {
  const image = item.image || item.poster || item.views?.[0] || item.images?.[0]
  if (image?.url) return apiAssetUrl(image.url)
  if (item.video_url) return apiAssetUrl(item.video_url)
  return ""
}

function assetMeta(item: AssetLibraryItem) {
  if (item.kind === "subjects") return `${item.images?.length || item.views?.length || 0} 图 · ${item.subject_style === "source_actor" ? "真人" : "骨架"}`
  if (item.kind === "products") return `${item.views?.length || 0} 视角 · ${item.product_type || "肩颈产品"}`
  if (item.kind === "scenes") return item.asset_role || item.aspect_ratio || "场景图"
  return item.duration ? `${item.duration.toFixed(1)}s` : "视频素材"
}

export function LibraryDrawer({ open, currentJobId, onClose, onApplyAsset }: LibraryDrawerProps) {
  const [tab, setTab] = useState<LibraryTab>("prompts")
  const [search, setSearch] = useState("")
  const [prompts, setPrompts] = useState<PromptLibraryItem[]>([])
  const [assets, setAssets] = useState<Record<AssetLibraryKind, AssetLibraryItem[]>>({ subjects: [], products: [], scenes: [], videos: [] })
  const [recent, setRecent] = useState<ResourceLibraryRecentItem[]>([])
  const [loading, setLoading] = useState(false)
  const [newPromptOpen, setNewPromptOpen] = useState(false)
  const [uploadOpen, setUploadOpen] = useState(false)
  const [detail, setDetail] = useState<PromptLibraryItem | AssetLibraryItem | null>(null)
  const [pulseId, setPulseId] = useState<string>("")
  const [rect, setRect] = useState({ width: 1100, height: 700, left: 0, top: 0 })
  const dragRef = useRef<{ mode: "move" | "resize"; x: number; y: number; rect: typeof rect } | null>(null)

  const refresh = async () => {
    setLoading(true)
    try {
      const [promptItems, subjects, products, scenes, videos, recentItems] = await Promise.all([
        listPromptLibrary(),
        listAssetLibrary("subjects"),
        listAssetLibrary("products"),
        listAssetLibrary("scenes"),
        listAssetLibrary("videos"),
        getResourceLibraryRecent(24),
      ])
      setPrompts(promptItems)
      setAssets({ subjects, products, scenes, videos })
      setRecent(recentItems.items)
    } catch (error) {
      toast.error("资源库读取失败：" + (error instanceof Error ? error.message : String(error)))
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    if (!open) return
    try {
      const saved = window.localStorage.getItem(DRAWER_STORAGE_KEY)
      if (saved) {
        const parsed = JSON.parse(saved)
        setTab(parsed.tab === "assets" ? "assets" : "prompts")
        setRect({
          width: Math.max(800, Math.min(Number(parsed.width) || 1100, window.innerWidth - 32)),
          height: Math.max(540, Math.min(Number(parsed.height) || 700, window.innerHeight - 32)),
          left: Math.max(16, Math.min(Number(parsed.left) || (window.innerWidth - 1100) / 2, window.innerWidth - 240)),
          top: Math.max(16, Math.min(Number(parsed.top) || (window.innerHeight - 700) / 2, window.innerHeight - 120)),
        })
      } else {
        setRect({
          width: Math.min(1100, window.innerWidth - 32),
          height: Math.min(700, window.innerHeight - 32),
          left: Math.max(16, (window.innerWidth - Math.min(1100, window.innerWidth - 32)) / 2),
          top: Math.max(16, (window.innerHeight - Math.min(700, window.innerHeight - 32)) / 2),
        })
      }
    } catch {
      // storage is optional
    }
    void refresh()
  }, [open])

  useEffect(() => {
    if (!open) return
    const onKey = (event: KeyboardEvent) => {
      if (event.key === "Escape") onClose()
    }
    window.addEventListener("keydown", onKey)
    return () => window.removeEventListener("keydown", onKey)
  }, [open, onClose])

  useEffect(() => {
    if (!open) return
    try {
      window.localStorage.setItem(DRAWER_STORAGE_KEY, JSON.stringify({ ...rect, tab }))
    } catch {
      // ignore
    }
  }, [open, rect, tab])

  useEffect(() => {
    const onMove = (event: MouseEvent) => {
      const state = dragRef.current
      if (!state) return
      if (state.mode === "move") {
        setRect((current) => ({
          ...current,
          left: Math.max(8, Math.min(state.rect.left + event.clientX - state.x, window.innerWidth - 240)),
          top: Math.max(8, Math.min(state.rect.top + event.clientY - state.y, window.innerHeight - 120)),
        }))
      } else {
        setRect((current) => ({
          ...current,
          width: Math.max(800, Math.min(state.rect.width + event.clientX - state.x, window.innerWidth - current.left - 8)),
          height: Math.max(540, Math.min(state.rect.height + event.clientY - state.y, window.innerHeight - current.top - 8)),
        }))
      }
    }
    const onUp = () => { dragRef.current = null }
    window.addEventListener("mousemove", onMove)
    window.addEventListener("mouseup", onUp)
    return () => {
      window.removeEventListener("mousemove", onMove)
      window.removeEventListener("mouseup", onUp)
    }
  }, [])

  const copyPrompt = async (item: PromptLibraryItem, mode: "en" | "zh" | "both" = "en") => {
    const text = mode === "zh" ? item.prompt_zh : mode === "both" ? `${item.prompt_en}\n\n中文：${item.prompt_zh}` : item.prompt_en
    await navigator.clipboard.writeText(text || item.prompt_en || item.prompt_zh)
    const updated = await usePromptLibraryItem(item.id)
    setPrompts((current) => current.map((candidate) => candidate.id === item.id ? updated : candidate))
    toast.success("已复制 · 可粘贴到任意输入框")
  }

  const deletePrompt = async (item: PromptLibraryItem) => {
    if (!window.confirm(`删除提示词「${item.name}」？`)) return
    await deletePromptLibraryItem(item.id)
    setPrompts((current) => current.filter((candidate) => candidate.id !== item.id))
    toast.success("提示词已移入回收区")
  }

  const applyAsset = async (item: AssetLibraryItem) => {
    if (!currentJobId) {
      toast.warning("先选择一个 job，再应用素材。")
      return
    }
    const target = item.kind === "products" && window.confirm("应用到产品素材池？取消则仅复制到当前 job 素材目录。") ? "product_pool" : "copy_only"
    const result = await copyAssetLibraryToJob(item.kind, item.id, currentJobId)
    if ("kind" in result && result.kind === "video") {
      await navigator.clipboard.writeText(result.url)
      toast.success("视频已复制到当前 job，链接已复制")
      return
    }
    await onApplyAsset?.(item.kind, result as ImageRef, target, item)
    await navigator.clipboard.writeText((result as ImageRef).element_id || "")
    toast.success(target === "product_pool" ? "已应用到产品素材池" : "已复制到当前 job，素材 ID 已复制")
    void refresh()
  }

  const deleteAsset = async (item: AssetLibraryItem) => {
    const refs = await getAssetLibraryRefs(item.kind, item.id)
    if (refs.count && !window.confirm(`${refs.count} 个 job 仍在引用这个库素材，仍要删除？`)) return
    if (!refs.count && !window.confirm(`删除素材「${item.name}」？`)) return
    await deleteAssetLibraryItem(item.kind, item.id, refs.count > 0)
    setAssets((current) => ({ ...current, [item.kind]: current[item.kind].filter((candidate) => candidate.id !== item.id) }))
    toast.success("素材已移入回收区")
  }

  const recentNodes = recent.slice(0, 12)

  if (!open || typeof document === "undefined") return null

  return createPortal(
    <div className="fixed inset-0 z-[9000] pointer-events-none">
      <div
        className="pointer-events-auto fixed flex min-w-[800px] flex-col overflow-hidden rounded-xl border border-white/12 bg-[#070707]/96 text-white shadow-[0_28px_90px_rgba(0,0,0,0.72)] backdrop-blur-xl"
        style={{ width: rect.width, height: rect.height, left: rect.left, top: rect.top }}
      >
        <header
          className="flex cursor-move items-center justify-between gap-3 border-b border-white/10 bg-white/[0.035] px-3 py-2"
          onMouseDown={(event) => {
            if ((event.target as HTMLElement).closest("button,input")) return
            dragRef.current = { mode: "move", x: event.clientX, y: event.clientY, rect }
          }}
        >
          <div className="flex items-center gap-2">
            <span className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-[#f5efe3] text-black shadow-xl shadow-black/25"><BookOpen className="h-4 w-4" /></span>
            <div>
              <div className="text-[13px] font-semibold">全局资源中心</div>
              <div className="text-[10px] text-white/42">提示词与素材只沉淀到库；应用到 job 时永远复制文件。</div>
            </div>
          </div>
          <div className="flex items-center gap-2">
            <div className="flex rounded-md border border-white/10 bg-black/35 p-0.5">
              {[
                ["prompts", "提示词库"],
                ["assets", "素材库"],
              ].map(([value, label]) => (
                <button
                  key={value}
                  type="button"
                  onClick={() => setTab(value as LibraryTab)}
                  className={cn("h-7 rounded px-2.5 text-[11px] font-semibold transition", tab === value ? "bg-[#f5efe3] text-black" : "text-white/52 hover:text-white")}
                >
                  {label}
                </button>
              ))}
            </div>
            <label className="flex h-8 min-w-[240px] items-center gap-1.5 rounded-md border border-white/10 bg-black/35 px-2 text-white/46">
              <Search className="h-3.5 w-3.5" />
              <input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="搜索当前库，不隐藏上下文" className="w-full bg-transparent text-[11px] text-white/78 outline-none placeholder:text-white/28" />
            </label>
            <button type="button" onClick={() => tab === "prompts" ? setNewPromptOpen(true) : setUploadOpen(true)} className="skg-primary-action inline-flex h-8 items-center gap-1.5 px-2.5 text-[11px] font-semibold">
              <Plus className="h-3.5 w-3.5" />
              新建
            </button>
            <button type="button" onClick={onClose} className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-white/10 bg-white/[0.04] text-white/58 hover:text-white">
              <X className="h-4 w-4" />
            </button>
          </div>
        </header>

        <LibraryRecentStrip items={recentNodes} onPick={(item) => { setPulseId(item.id); setDetail(item.item); setTimeout(() => setPulseId(""), 2000) }} />

        <main className="grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_260px]">
          <div className="min-w-0 overflow-x-auto p-3">
            {loading ? (
              <div className="flex h-full items-center justify-center text-white/45"><Loader2 className="mr-2 h-4 w-4 animate-spin" />资源库读取中</div>
            ) : tab === "prompts" ? (
              <div className="grid h-full auto-cols-[260px] grid-flow-col gap-4">
                {PROMPT_COLUMNS.map((column) => (
                  <PromptColumn
                    key={column.category}
                    label={column.label}
                    desc={column.desc}
                    query={search}
                    items={prompts.filter((item) => item.category === column.category)}
                    pulseId={pulseId}
                    onCopy={copyPrompt}
                    onDelete={deletePrompt}
                    onDetail={setDetail}
                  />
                ))}
              </div>
            ) : (
              <div className="grid h-full auto-cols-[260px] grid-flow-col gap-4">
                {ASSET_COLUMNS.map((column) => (
                  <AssetColumn
                    key={column.kind}
                    label={column.label}
                    icon={column.icon}
                    query={search}
                    items={assets[column.kind]}
                    pulseId={pulseId}
                    onApply={applyAsset}
                    onDelete={deleteAsset}
                    onDetail={setDetail}
                  />
                ))}
              </div>
            )}
          </div>
          <LibraryDetailPanel item={detail} />
        </main>

        <button
          type="button"
          aria-label="调整资源库浮窗大小"
          onMouseDown={(event) => {
            event.preventDefault()
            dragRef.current = { mode: "resize", x: event.clientX, y: event.clientY, rect }
          }}
          className="absolute bottom-1 right-1 h-5 w-5 cursor-nwse-resize rounded-sm border-b-2 border-r-2 border-[#d6b36a]/60"
        />
      </div>

      {newPromptOpen ? <LibraryNewPromptDialog currentJobId={currentJobId} onClose={() => setNewPromptOpen(false)} onSaved={(item) => { setPrompts((current) => [item, ...current]); setNewPromptOpen(false); toast.success("提示词已入库") }} /> : null}
      {uploadOpen ? <LibraryUploadDialog currentJobId={currentJobId} onClose={() => setUploadOpen(false)} onSaved={(item) => { setAssets((current) => ({ ...current, [item.kind]: [item, ...current[item.kind]] })); setUploadOpen(false); void refresh(); toast.success("素材已入库") }} /> : null}
    </div>,
    document.body,
  )
}

function LibraryRecentStrip({ items, onPick }: { items: ResourceLibraryRecentItem[]; onPick: (item: ResourceLibraryRecentItem) => void }) {
  return (
    <div className="h-[100px] shrink-0 border-b border-white/10 bg-black/24 px-3 py-2">
      <div className="mb-1 flex items-center justify-between">
        <div className="text-[10px] font-semibold uppercase tracking-[0.18em] text-[#d6b36a]">最近 24 小时</div>
        <div className="text-[10px] text-white/34">{items.length ? `${items.length} 个新增` : "暂无新增"}</div>
      </div>
      <div className="flex gap-2 overflow-x-auto">
        {items.length ? items.map((item) => (
          <button key={`${item.type}-${item.id}`} type="button" onClick={() => onPick(item)} className="relative h-[72px] w-[80px] shrink-0 overflow-hidden rounded-md border border-white/10 bg-white/[0.035] text-left hover:border-[#d6b36a]/55">
            <span className="absolute left-1 top-1 z-10 rounded bg-black/72 px-1 text-[9px] text-white/75">{item.type === "asset" ? "素" : "词"}</span>
            <div className="flex h-full items-end p-1 text-[10px] leading-tight text-white/72">
              <span className="line-clamp-2">{item.name}</span>
            </div>
          </button>
        )) : (
          <div className="flex h-[72px] items-center text-[11px] text-white/34">新建或上传后会出现在这里。</div>
        )}
      </div>
    </div>
  )
}

function PromptColumn({ label, desc, items, query, pulseId, onCopy, onDelete, onDetail }: {
  label: string
  desc: string
  items: PromptLibraryItem[]
  query: string
  pulseId: string
  onCopy: (item: PromptLibraryItem, mode?: "en" | "zh" | "both") => void
  onDelete: (item: PromptLibraryItem) => void
  onDetail: (item: PromptLibraryItem) => void
}) {
  const common = [...items].sort((a, b) => b.use_count - a.use_count).slice(0, 5).filter((item) => item.use_count > 0)
  return (
    <section className="flex min-h-0 flex-col rounded-lg border border-white/10 bg-white/[0.028]">
      <header className="shrink-0 border-b border-white/10 p-2">
        <div className="text-[12px] font-semibold">{label}</div>
        <div className="mt-0.5 text-[10px] text-white/35">{desc}</div>
      </header>
      <div className="min-h-0 flex-1 space-y-2 overflow-y-auto p-2">
        {common.length ? (
          <>
            <Divider label="常用" />
            {common.map((item) => <PromptCard key={`common-${item.id}`} item={item} dim={!matchesPrompt(item, query)} pulse={pulseId === item.id} onCopy={onCopy} onDelete={onDelete} onDetail={onDetail} />)}
          </>
        ) : null}
        {groupByMonth(items).map(([month, monthItems]) => (
          <div key={month}>
            <Divider label={month} />
            <div className="space-y-2">
              {monthItems.map((item) => <PromptCard key={item.id} item={item} dim={!matchesPrompt(item, query)} pulse={pulseId === item.id} onCopy={onCopy} onDelete={onDelete} onDetail={onDetail} />)}
            </div>
          </div>
        ))}
      </div>
    </section>
  )
}

function PromptCard({ item, dim, pulse, onCopy, onDelete, onDetail }: {
  item: PromptLibraryItem
  dim: boolean
  pulse: boolean
  onCopy: (item: PromptLibraryItem, mode?: "en" | "zh" | "both") => void
  onDelete: (item: PromptLibraryItem) => void
  onDetail: (item: PromptLibraryItem) => void
}) {
  const isNew = Date.now() / 1000 - item.created_at < 86400
  return (
    <article className={cn("group h-[132px] rounded-md border border-white/10 bg-black/34 p-2 transition", dim && "opacity-25", pulse && "ring-2 ring-[#d6b36a]/80")}>
      <div className="flex items-start justify-between gap-2">
        <button type="button" onClick={() => onDetail(item)} className="min-w-0 text-left">
          <div className="truncate text-[11px] font-semibold text-white/86">{item.name}</div>
          <div className="mt-1 flex flex-wrap gap-1">{item.tags.slice(0, 2).map((tag) => <span key={tag} className="rounded border border-[#d6b36a]/20 bg-[#d6b36a]/8 px-1 text-[9px] text-[#f1d78e]">{tag}</span>)}{isNew ? <span className="rounded bg-[#d6b36a]/18 px-1 text-[9px] text-[#f1d78e]">✨ 新</span> : null}</div>
        </button>
        <div className="flex opacity-0 transition group-hover:opacity-100">
          <button type="button" title="编辑" onClick={() => onDetail(item)} className="h-5 w-5 text-white/42 hover:text-white"><Edit3 className="h-3.5 w-3.5" /></button>
          <button type="button" title="删除" onClick={() => onDelete(item)} className="h-5 w-5 text-rose-200/58 hover:text-rose-100"><Trash2 className="h-3.5 w-3.5" /></button>
        </div>
      </div>
      <button type="button" onClick={() => onDetail(item)} className="mt-2 line-clamp-2 min-h-[34px] w-full text-left text-[10.5px] leading-snug text-white/48">{item.prompt_en || item.prompt_zh}</button>
      <div className="mt-2 flex items-center justify-between gap-2 text-[10px] text-white/34">
        <CopyButton onCopy={(mode) => onCopy(item, mode)} />
        <span>{item.use_count} 次使用 · {formatAgo(item.created_at)}</span>
      </div>
    </article>
  )
}

function CopyButton({ onCopy }: { onCopy: (mode: "en" | "zh" | "both") => void }) {
  const [open, setOpen] = useState(false)
  return (
    <span className="relative" onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
      <button type="button" onClick={() => onCopy("en")} className="inline-flex h-6 items-center gap-1 rounded bg-[#f5efe3] px-2 text-[10px] font-semibold text-black">
        <Copy className="h-3 w-3" />
        复制
      </button>
      {open ? (
        <span className="absolute bottom-7 left-0 z-20 flex rounded-md border border-white/12 bg-black/94 p-1 shadow-xl">
          {[
            ["en", "英文"],
            ["zh", "中文"],
            ["both", "双语"],
          ].map(([mode, label]) => (
            <button key={mode} type="button" onClick={() => onCopy(mode as "en" | "zh" | "both")} className="whitespace-nowrap rounded px-2 py-1 text-[10px] text-white/68 hover:bg-white/10 hover:text-white">{label}</button>
          ))}
        </span>
      ) : null}
    </span>
  )
}

function AssetColumn({ label, icon, items, query, pulseId, onApply, onDelete, onDetail }: {
  label: string
  icon: ReactNode
  items: AssetLibraryItem[]
  query: string
  pulseId: string
  onApply: (item: AssetLibraryItem) => void
  onDelete: (item: AssetLibraryItem) => void
  onDetail: (item: AssetLibraryItem) => void
}) {
  return (
    <section className="flex min-h-0 flex-col rounded-lg border border-white/10 bg-white/[0.028]">
      <header className="flex shrink-0 items-center gap-1.5 border-b border-white/10 p-2 text-[12px] font-semibold">{icon}{label}</header>
      <div className="min-h-0 flex-1 space-y-3 overflow-y-auto p-2">
        {groupByMonth(items).map(([month, monthItems]) => (
          <div key={month}>
            <Divider label={month} />
            <div className="grid grid-cols-2 gap-2">
              {monthItems.map((item) => <AssetCard key={item.id} item={item} dim={!matchesAsset(item, query)} pulse={pulseId === item.id} onApply={onApply} onDelete={onDelete} onDetail={onDetail} />)}
            </div>
          </div>
        ))}
      </div>
    </section>
  )
}

function AssetCard({ item, dim, pulse, onApply, onDelete, onDetail }: {
  item: AssetLibraryItem
  dim: boolean
  pulse: boolean
  onApply: (item: AssetLibraryItem) => void
  onDelete: (item: AssetLibraryItem) => void
  onDetail: (item: AssetLibraryItem) => void
}) {
  const horizontal = item.kind === "videos"
  return (
    <div className={cn("group rounded-md border border-white/10 bg-black/34 p-1.5 transition", dim && "opacity-25", pulse && "ring-2 ring-[#d6b36a]/80", horizontal && "col-span-2")}>
      <MediaAssetTile
        kind={item.kind === "videos" ? "video" : "image"}
        src={item.kind === "videos" ? apiAssetUrl(item.video_url) : assetThumb(item)}
        poster={item.kind === "videos" ? assetThumb(item) : undefined}
        label={item.name}
        meta={assetMeta(item)}
        className={horizontal ? "aspect-video w-full bg-white" : "aspect-[120/156] w-full bg-white"}
        objectFit="cover"
        onClick={() => onDetail(item)}
        actions={[
          { key: "copy", label: "复制 ID", icon: <Copy className="h-3 w-3" />, onClick: () => { void navigator.clipboard.writeText(item.id); toast.success("素材 ID 已复制") } },
          { key: "apply", label: "应用到当前 job", icon: <Download className="h-3 w-3" />, onClick: () => void onApply(item), tone: "cyan" },
          { key: "edit", label: "编辑", icon: <Edit3 className="h-3 w-3" />, onClick: () => onDetail(item) },
        ]}
        onDelete={() => void onDelete(item)}
      />
      <div className="mt-1 min-w-0">
        <div className="truncate text-[10.5px] font-semibold text-white/78">{item.name}</div>
        <div className="truncate text-[9.5px] text-white/34">{assetMeta(item)} · {item.use_count} 次</div>
      </div>
    </div>
  )
}

function Divider({ label }: { label: string }) {
  return <div className="my-2 flex items-center gap-2 text-[9px] uppercase tracking-[0.16em] text-white/28"><span className="h-px flex-1 bg-white/10" />{label}<span className="h-px flex-1 bg-white/10" /></div>
}

function LibraryDetailPanel({ item }: { item: PromptLibraryItem | AssetLibraryItem | null }) {
  if (!item) {
    return <aside className="border-l border-white/10 p-3 text-[11px] text-white/36">选择一个节点查看详情。</aside>
  }
  const isPrompt = "prompt_en" in item
  return (
    <aside className="min-h-0 overflow-y-auto border-l border-white/10 p-3">
      <div className="mb-2 flex items-center gap-2 text-[12px] font-semibold">
        {isPrompt ? <FileText className="h-4 w-4 text-[#d6b36a]" /> : <Database className="h-4 w-4 text-[#d6b36a]" />}
        {item.name}
      </div>
      <div className="space-y-2 text-[11px] leading-relaxed text-white/58">
        <p>ID：<span className="font-mono text-white/42">{item.id}</span></p>
        <p>使用：{item.use_count} 次</p>
        <p>创建：{formatAgo(item.created_at)}</p>
        <p>标签：{item.tags?.join(" / ") || "-"}</p>
        {isPrompt ? (
          <>
            <div><div className="mb-1 text-white/34">英文</div><pre className="whitespace-pre-wrap rounded border border-white/10 bg-black/36 p-2 text-[10.5px]">{(item as PromptLibraryItem).prompt_en}</pre></div>
            <div><div className="mb-1 text-white/34">中文</div><pre className="whitespace-pre-wrap rounded border border-white/10 bg-black/36 p-2 text-[10.5px]">{(item as PromptLibraryItem).prompt_zh || "-"}</pre></div>
          </>
        ) : (
          <>
            <p>备注：{(item as AssetLibraryItem).note || "-"}</p>
            <p>来源 job：{(item as AssetLibraryItem).source_job_id || "-"}</p>
            <p>brief：{(item as AssetLibraryItem).prompt_brief || "-"}</p>
          </>
        )}
      </div>
    </aside>
  )
}

function LibraryNewPromptDialog({ currentJobId, onClose, onSaved }: { currentJobId?: string; onClose: () => void; onSaved: (item: PromptLibraryItem) => void }) {
  const [category, setCategory] = useState<PromptLibraryCategory>("scene_desc")
  const [name, setName] = useState("")
  const [tags, setTags] = useState("")
  const [promptEn, setPromptEn] = useState("")
  const [promptZh, setPromptZh] = useState("")
  const [busy, setBusy] = useState(false)
  const save = async () => {
    setBusy(true)
    try {
      const item = await createPromptLibraryItem({
        category,
        name,
        tags: tags.split(/[,，\s]+/).map((tag) => tag.trim()).filter(Boolean),
        prompt_en: promptEn,
        prompt_zh: promptZh,
        source_job_id: currentJobId || "",
      })
      onSaved(item)
    } catch (error) {
      toast.error("提示词入库失败：" + (error instanceof Error ? error.message : String(error)))
    } finally {
      setBusy(false)
    }
  }
  return <DialogFrame title="新建提示词" onClose={onClose}>
    <div className="grid gap-2">
      <select value={category} onChange={(event) => setCategory(event.target.value as PromptLibraryCategory)} className="h-9 rounded border border-white/10 bg-black px-2 text-[12px]">
        {PROMPT_COLUMNS.map((column) => <option key={column.category} value={column.category}>{column.label}</option>)}
      </select>
      <input value={name} onChange={(event) => setName(event.target.value)} placeholder="标题" className="h-9 rounded border border-white/10 bg-black px-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <input value={tags} onChange={(event) => setTags(event.target.value)} placeholder="标签，用空格或逗号分隔" className="h-9 rounded border border-white/10 bg-black px-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <textarea value={promptEn} onChange={(event) => setPromptEn(event.target.value)} placeholder="英文内容，实际发给模型" className="min-h-[112px] resize-y rounded border border-white/10 bg-black px-2 py-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <textarea value={promptZh} onChange={(event) => setPromptZh(event.target.value)} placeholder="中文翻译，给团队看" className="min-h-[72px] resize-y rounded border border-white/10 bg-black px-2 py-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <div className="flex justify-end gap-2">
        <button type="button" onClick={onClose} className="skg-secondary-action h-9 px-3 text-[12px]">取消</button>
        <button type="button" onClick={() => void save()} disabled={busy || !name.trim() || (!promptEn.trim() && !promptZh.trim())} className="skg-primary-action h-9 px-3 text-[12px] disabled:opacity-40">{busy ? "保存中" : "保存"}</button>
      </div>
    </div>
  </DialogFrame>
}

function LibraryUploadDialog({ currentJobId, onClose, onSaved }: { currentJobId?: string; onClose: () => void; onSaved: (item: AssetLibraryItem) => void }) {
  const [kind, setKind] = useState<AssetLibraryKind>("subjects")
  const [name, setName] = useState("")
  const [note, setNote] = useState("")
  const [tags, setTags] = useState("")
  const [files, setFiles] = useState<File[]>([])
  const [busy, setBusy] = useState(false)
  const save = async () => {
    setBusy(true)
    try {
      const item = await createAssetLibraryItem(kind, {
        name,
        note,
        tags: tags.split(/[,，\s]+/).map((tag) => tag.trim()).filter(Boolean),
        source_job_id: currentJobId || "",
      }, files)
      onSaved(item)
    } catch (error) {
      toast.error("素材入库失败：" + (error instanceof Error ? error.message : String(error)))
    } finally {
      setBusy(false)
    }
  }
  return <DialogFrame title="上传素材" onClose={onClose}>
    <div className="grid gap-2">
      <select value={kind} onChange={(event) => setKind(event.target.value as AssetLibraryKind)} className="h-9 rounded border border-white/10 bg-black px-2 text-[12px]">
        {ASSET_COLUMNS.map((column) => <option key={column.kind} value={column.kind}>{column.label}</option>)}
      </select>
      <input type="file" multiple onChange={(event) => setFiles(Array.from(event.currentTarget.files ?? []))} className="rounded border border-dashed border-white/12 bg-black/35 p-3 text-[12px] text-white/56 file:mr-3 file:rounded file:border-0 file:bg-[#f5efe3] file:px-2 file:py-1 file:text-black" />
      <input value={name} onChange={(event) => setName(event.target.value)} placeholder="名称" className="h-9 rounded border border-white/10 bg-black px-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <input value={tags} onChange={(event) => setTags(event.target.value)} placeholder="标签，用空格或逗号分隔" className="h-9 rounded border border-white/10 bg-black px-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <textarea value={note} onChange={(event) => setNote(event.target.value)} placeholder="备注 / 视角 / brief" className="min-h-[80px] resize-y rounded border border-white/10 bg-black px-2 py-2 text-[12px] outline-none focus:border-[#d6b36a]/55" />
      <div className="flex justify-end gap-2">
        <button type="button" onClick={onClose} className="skg-secondary-action h-9 px-3 text-[12px]">取消</button>
        <button type="button" onClick={() => void save()} disabled={busy || !name.trim() || !files.length} className="skg-primary-action h-9 px-3 text-[12px] disabled:opacity-40">{busy ? "保存中" : "保存"}</button>
      </div>
    </div>
  </DialogFrame>
}

function DialogFrame({ title, children, onClose }: { title: string; children: ReactNode; onClose: () => void }) {
  return (
    <div className="pointer-events-auto fixed inset-0 z-[9100] flex items-center justify-center bg-black/45">
      <div className="w-[520px] rounded-xl border border-white/12 bg-[#080808] p-4 text-white shadow-2xl">
        <div className="mb-3 flex items-center justify-between">
          <div className="text-[14px] font-semibold">{title}</div>
          <button type="button" onClick={onClose} className="h-7 w-7 rounded border border-white/10 text-white/55 hover:text-white"><X className="mx-auto h-4 w-4" /></button>
        </div>
        {children}
      </div>
    </div>
  )
}
