"use client"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useTheme } from "next-themes"
import {
  ReactFlow, Background, BackgroundVariant, Controls, MiniMap,
  useNodesState, useEdgesState,
  type Node, type Edge,
} from "@xyflow/react"
import { Toaster, toast } from "sonner"
import {
  InputNode, KeyframeNode, ASRNode,
  TranslateNode, RewriteNode, ImageGenNode, VideoGenNode, ComposeNode,
  type NodeData,
} from "@/components/nodes"
import { ThemeToggle } from "@/components/theme-toggle"
import { Dashboard, type DashboardHandle } from "@/components/dashboard"
import { StoryboardBar } from "@/components/storyboard-bar"
import { StoryboardWorkbench } from "@/components/storyboard-workbench"
import { addManualFrame, analyzeJob, createJob, getJob, uploadJob, deleteFrame, deleteGeneratedImage, pushStoryboardImage, type Job } from "@/lib/api"
import { VideoLightbox } from "@/components/video-lightbox"

const NODE_TYPES = {
  input: InputNode,
  keyframe: KeyframeNode,
  asr: ASRNode,
  translate: TranslateNode,
  rewrite: RewriteNode,
  imagegen: ImageGenNode,
  videogen: VideoGenNode,
  compose: ComposeNode,
}

// 合并 input + download + split 为一个节点
// 分叉：上路 input → keyframe → imagegen → videogen ↘
//       下路 input → asr → translate → rewrite ────→ compose
const LAYOUT: Array<{ id: string; type: keyof typeof NODE_TYPES; x: number; y: number }> = [
  { id: "input",     type: "input",     x:   40, y: 240 },
  { id: "keyframe",  type: "keyframe",  x:  460, y:  60 },
  { id: "asr",       type: "asr",       x:  460, y: 440 },
  { id: "translate", type: "translate", x:  840, y: 440 },
  { id: "imagegen",  type: "imagegen",  x:  880, y:  60 },
  { id: "rewrite",   type: "rewrite",   x: 1220, y: 440 },
  { id: "videogen",  type: "videogen",  x: 1260, y:  60 },
  { id: "compose",   type: "compose",   x: 1640, y: 240 },
]

const EDGES_RAW: Array<[string, string]> = [
  ["input", "keyframe"],
  ["input", "asr"],
  ["asr", "translate"],
  ["translate", "rewrite"],
  ["keyframe", "imagegen"],
  ["rewrite", "imagegen"],
  ["imagegen", "videogen"],
  ["videogen", "compose"],
  ["rewrite", "compose"],
]

export default function Home() {
  const { resolvedTheme } = useTheme()
  const [jobs, setJobs] = useState<Job[]>([])
  const [activeJobId, setActiveJobId] = useState<string | null>(null)
  const job = useMemo(() => jobs.find((j) => j.id === activeJobId) ?? null, [jobs, activeJobId])
  const [submitting, setSubmitting] = useState(false)
  const [analyzing, setAnalyzing] = useState(false)
  const [selectedFrames, setSelectedFrames] = useState<Set<number>>(new Set())
  const [expandedFrame, setExpandedFrame] = useState<number | null>(null)
  const [videoLightboxOpen, setVideoLightboxOpen] = useState(false)
  const [storyboardFrame, setStoryboardFrame] = useState<number | null>(null)
  const [workbenchOpen, setWorkbenchOpen] = useState(false)
  const dashboardRef = useRef<DashboardHandle>(null)

  // 把 setJob(prev=>...) 翻译成 setJobs 里更新当前 active
  const setJob = useCallback((updater: Job | ((prev: Job | null) => Job | null) | null) => {
    setJobs((prev) => {
      const current = prev.find((j) => j.id === activeJobId) ?? null
      const next = typeof updater === "function" ? (updater as (p: Job | null) => Job | null)(current) : updater
      if (!next) return prev
      const idx = prev.findIndex((j) => j.id === next.id)
      if (idx < 0) {
        setActiveJobId(next.id)
        return [...prev, next]
      }
      const arr = [...prev]
      arr[idx] = next
      return arr
    })
  }, [activeJobId])

  // 新增 job + 设为 active
  const addJob = useCallback((j: Job) => {
    setJobs((prev) => [...prev.filter((x) => x.id !== j.id), j])
    setActiveJobId(j.id)
  }, [])

  const handleSwitchJob = useCallback((id: string) => {
    setActiveJobId(id)
    setSelectedFrames(new Set())
  }, [])
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null)

  const handleSubmit = useCallback(async (url: string) => {
    setSubmitting(true)
    setSelectedFrames(new Set())
    try {
      const created = await createJob(url)
      addJob(created)
      toast.success(`已创建任务 ${created.id.slice(0, 8)}`)
    } catch (e) {
      toast.error("提交失败：" + (e instanceof Error ? e.message : String(e)))
    } finally {
      setSubmitting(false)
    }
  }, [addJob])

  const handleUpload = useCallback(async (file: File) => {
    setSubmitting(true)
    setSelectedFrames(new Set())
    try {
      toast.info(`上传中：${file.name} (${(file.size / 1024 / 1024).toFixed(1)} MB)`)
      const created = await uploadJob(file)
      addJob(created)
      toast.success(`已上传 ${created.id.slice(0, 8)}`)
    } catch (e) {
      toast.error("上传失败：" + (e instanceof Error ? e.message : String(e)))
    } finally {
      setSubmitting(false)
    }
  }, [addJob])

  const handleAnalyze = useCallback(async () => {
    if (!job) return
    setAnalyzing(true)
    setSelectedFrames(new Set())
    try {
      await analyzeJob(job.id, 5)
      toast.info("开始解析：拆轨 → 抽帧 → ASR → 翻译")
      // 乐观更新本地状态，让轮询 useEffect 重新启动
      setJob((prev) => prev ? { ...prev, status: "splitting", message: "拆轨中…", progress: 30 } : prev)
    } catch (e) {
      toast.error("解析触发失败：" + (e instanceof Error ? e.message : String(e)))
    } finally {
      setAnalyzing(false)
    }
  }, [job?.id])

  const handleAddManualFrame = useCallback(async (t: number) => {
    if (!job) return
    try {
      const updated = await addManualFrame(job.id, t)
      setJob(updated)
      toast.success(`已加帧 @ ${t.toFixed(1)}s · 共 ${updated.frames.length} 张`)
    } catch (e) {
      toast.error("加帧失败：" + (e instanceof Error ? e.message : String(e)))
    }
  }, [job?.id])

  const handleToggleFrame = useCallback((idx: number) => {
    setSelectedFrames((prev) => {
      const next = new Set(prev)
      if (next.has(idx)) next.delete(idx)
      else next.add(idx)
      return next
    })
  }, [])

  const handleDeleteFrame = useCallback(async (idx: number) => {
    if (!activeJobId) return
    try {
      const updated = await deleteFrame(activeJobId, idx)
      setJob(updated)
      setSelectedFrames((prev) => {
        if (!prev.has(idx)) return prev
        const next = new Set(prev)
        next.delete(idx)
        return next
      })
      if (expandedFrame === idx) setExpandedFrame(null)
      toast.success(`分镜 ${idx + 1} 已删除`)
    } catch (e) {
      toast.error("删除失败：" + (e instanceof Error ? e.message : String(e)))
    }
  }, [activeJobId, expandedFrame, setJob])

  const handleDeleteGenerated = useCallback(async (frameIdx: number, genId: string) => {
    if (!activeJobId) return
    try {
      const updated = await deleteGeneratedImage(activeJobId, frameIdx, genId)
      setJob(updated)
      toast.success("生成图已删除")
    } catch (e) {
      toast.error("删除失败：" + (e instanceof Error ? e.message : String(e)))
    }
  }, [activeJobId, setJob])

  const handlePushToStoryboard = useCallback(async (payload: { kind: "keyframe" | "cutout"; frameIdx: number; elementId?: string; cutoutId?: string; label?: string }) => {
    if (!activeJobId) return
    try {
      const updated = await pushStoryboardImage(activeJobId, {
        kind: payload.kind,
        frame_idx: payload.frameIdx,
        element_id: payload.elementId,
        cutout_id: payload.cutoutId,
        label: payload.label,
      })
      setJob(updated)
      toast.success("已推送到分镜头编排")
    } catch (e) {
      toast.error("推送失败：" + (e instanceof Error ? e.message : String(e)))
    }
  }, [activeJobId, setJob])

  // URL ?job=xxx,yyy 自动恢复多个 job
  useEffect(() => {
    const params = new URLSearchParams(window.location.search)
    const idsStr = params.get("job") ?? ""
    const ids = idsStr.split(",").filter(Boolean)
    if (ids.length === 0) return
    Promise.all(ids.map((id) => getJob(id).catch(() => null))).then((results) => {
      const valid = results.filter((j): j is Job => !!j)
      if (valid.length > 0) {
        setJobs(valid)
        setActiveJobId(valid[valid.length - 1].id)
      }
    })
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 写回 URL（所有 jobs id 用 , 分隔）
  useEffect(() => {
    if (jobs.length === 0) return
    const url = new URL(window.location.href)
    url.searchParams.set("job", jobs.map((j) => j.id).join(","))
    window.history.replaceState({}, "", url.toString())
  }, [jobs.length])

  // 轮询 Job（downloaded / transcribed / failed 三态停止）
  const prevStatusRef = useRef<string | null>(null)
  useEffect(() => {
    if (!job) return
    // 状态切到 downloaded 时提示用户点解析（仅一次）
    if (job.status === "downloaded" && prevStatusRef.current !== "downloaded") {
      toast.info("📥 视频已就绪 — 请点 Input 节点里的「点这里开始解析」按钮", { duration: 6000 })
    }
    prevStatusRef.current = job.status

    const TERMINAL: Job["status"][] = ["downloaded", "transcribed", "failed"]
    if (TERMINAL.includes(job.status)) {
      if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null }
      return
    }
    pollRef.current = setInterval(async () => {
      try {
        const latest = await getJob(job.id)
        setJob(latest)
      } catch { /* silent */ }
    }, 1500)
    return () => { if (pollRef.current) clearInterval(pollRef.current) }
  }, [job?.id, job?.status])

  const nodeData: NodeData = useMemo(() => ({
    job,
    jobs,
    activeJobId,
    submitting,
    analyzing,
    selectedFrames,
    expandedFrame,
    onSubmitUrl: handleSubmit,
    onUploadFile: handleUpload,
    onAnalyze: handleAnalyze,
    onToggleFrame: handleToggleFrame,
    onExpandFrame: setExpandedFrame,
    onCloseExpandedFrame: () => setExpandedFrame(null),
    onAddManualFrame: handleAddManualFrame,
    onOpenVideoLightbox: () => setVideoLightboxOpen(true),
    onSwitchJob: handleSwitchJob,
    onJobUpdate: setJob as any,
    onOpenPanel: (key: string) => dashboardRef.current?.openPanel(key),
    onDeleteFrame: handleDeleteFrame,
    onDeleteGenerated: handleDeleteGenerated,
    onOpenStoryboard: (idx: number) => setStoryboardFrame(idx),
    onPushToStoryboard: handlePushToStoryboard,
  }), [job, jobs, activeJobId, submitting, analyzing, selectedFrames, expandedFrame, handleSubmit, handleUpload, handleAnalyze, handleToggleFrame, handleAddManualFrame, handleSwitchJob, setJob, handleDeleteFrame, handleDeleteGenerated, handlePushToStoryboard])

  // 用 useNodesState 让 ReactFlow 自己管位置（避免轮询时重置 drag）
  const [nodes, setNodes, onNodesChange] = useNodesState<Node>(
    LAYOUT.map((n) => ({
      id: n.id,
      type: n.type,
      position: { x: n.x, y: n.y },
      data: nodeData,
      draggable: true,
    })),
  )
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>(
    EDGES_RAW.map(([from, to], i) => ({
      id: `e${i}`, source: from, target: to, animated: false, type: "default",
    })),
  )

  // Job 数据变化时只更新节点 data 不动 position
  useEffect(() => {
    setNodes((prev) => prev.map((n) => ({ ...n, data: nodeData })))
  }, [nodeData, setNodes])

  // 边的 animated 状态跟 Job 进度联动
  useEffect(() => {
    const doneOf: Record<string, boolean> = {
      input: !!job?.video_url,
      keyframe: !!job && job.frames.length > 0,
      asr: !!job && job.transcript.length > 0,
      translate: !!job && (job.transcript.some((s) => s.zh) ?? false),
    }
    setEdges((prev) => prev.map((e) => ({ ...e, animated: !!doneOf[e.source] })))
  }, [job, setEdges])

  return (
    <>
      <div className="canvas-bg" />
      <main className="relative h-screen w-screen overflow-hidden flex">
        {/* 主题切换 — 左下角 Controls 上方（错开） */}
        <div className="absolute z-30 pointer-events-auto" style={{ bottom: 180, left: 12 }}>
          <ThemeToggle />
        </div>

        {/* sidebar tile 列已去掉 · Dashboard 仍渲染（hidden）以保留 keyframe lightbox 等 drawer portal */}
        <aside className="hidden">
          <Dashboard ref={dashboardRef} data={nodeData} />
        </aside>

        {/* 右区：顶部 storyboard bar + DAG 节点流图 */}
        <section className="relative flex-1 min-h-0 flex flex-col">
          <StoryboardBar
            job={job}
            selectedFrames={selectedFrames}
            focusedFrame={storyboardFrame}
            onFocusFrame={setStoryboardFrame}
            onJobUpdate={setJob as any}
            onOpenWorkbench={() => setWorkbenchOpen(true)}
          />
          <div className="relative flex-1 min-h-0">
          <ReactFlow
            nodes={nodes}
            edges={edges}
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChange}
            nodeTypes={NODE_TYPES}
            colorMode={resolvedTheme === "light" ? "light" : "dark"}
            fitView
            fitViewOptions={{ padding: 0.12 }}
            minZoom={0.2}
            maxZoom={1.5}
            proOptions={{ hideAttribution: true }}
          >
            <Background variant={BackgroundVariant.Dots} gap={28} size={1.4} />
            <Controls position="bottom-left" />
            <MiniMap position="bottom-right" pannable zoomable nodeStrokeWidth={2} />
          </ReactFlow>
          </div>
        </section>

        <Toaster theme="system" position="bottom-center" />

        {/* FrameLightbox 已嵌入 dashboard 的 keyframe drawer（embedded mode），不再独立浮动 */}

        {/* Video lightbox — InputNode 缩略图点击进入 */}
        <VideoLightbox
          jobId={job?.id ?? null}
          open={videoLightboxOpen}
          onClose={() => setVideoLightboxOpen(false)}
          onAddFrame={handleAddManualFrame}
        />

        {/* 分镜头编排工作台 — 全屏覆盖 DAG */}
        <StoryboardWorkbench
          job={job}
          selectedFrames={selectedFrames}
          open={workbenchOpen}
          onClose={() => setWorkbenchOpen(false)}
          onJobUpdate={setJob as any}
        />

      </main>
    </>
  )
}
