﻿import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
import "../../styles/pages/ecommerce-video.css";
import {
  CloseOutlined,
  CopyOutlined,
  DownloadOutlined,
  FolderAddOutlined,
  HistoryOutlined,
  LoadingOutlined,
  ReloadOutlined,
  SendOutlined,
  StopOutlined,
} from "@ant-design/icons";
import {
  runVideoPlan,
  buildSceneTasks,
  saveVideoHistory,
  buildComplianceFailureMessage,
} from "./ecommerceVideoService";
import {
  PLAN_STEP_LABELS,
  PLAN_STEPS_DISPLAY,
  type EcommerceVideoStage,
  type EcommerceVideoSceneTask,
  type EcommerceVideoPlanProgress,
  type EcommerceVideoPlanResult,
  type PlanStep,
} from "./ecommerceVideoTypes";
import type { AdVideoUserConfig } from "../../api/adVideoPlanClient";
import { saveToolResultToLocal, addToolResultToAssetLibrary } from "../workbench/toolResultActions";
import { useAppStore } from "../../stores";
import { useGenerationTasks } from "../../hooks/useGenerationTasks";
import {
  saveEcommerceVideoState,
  loadEcommerceVideoState,
  clearEcommerceVideoState,
} from "./ecommerceVideoKeepalive";
import { saveUnifiedEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { useVideoSceneRunner } from "./useVideoSceneRunner";

interface EcommerceVideoWorkspaceProps {
  isAuthenticated: boolean;
  productImageDataUrls: string[];
  productImageFiles?: Array<File | undefined>;
  requirement: string;
  platform: string;
  aspectRatio: string;
  durationSeconds: number;
  resolution: string;
  onRequestLogin?: () => void;
  onOpenHistory?: () => void;
  triggerPlan?: number;
  saveRef?: { current: (() => void) | null };
}

const ALL_STEPS: PlanStep[] = [
  "upload", "analyze", "summary", "selling",
  "creative", "storyboard", "prompts", "compliance",
];

function hashString(value: string): string {
  let hash = 2166136261;
  for (let index = 0; index < value.length; index += 1) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return (hash >>> 0).toString(36);
}

function buildInputFingerprint(input: {
  productImageDataUrls: string[];
  requirement: string;
  platform: string;
  aspectRatio: string;
  durationSeconds: number;
  resolution: string;
}): string {
  const imageSignature = input.productImageDataUrls
    .map((source) => `${source.length}:${hashString(source)}`)
    .join("|");
  return hashString([
    imageSignature,
    input.requirement.trim(),
    input.platform,
    input.aspectRatio,
    input.durationSeconds,
    input.resolution,
  ].join("::"));
}

function planAllowsVideoGeneration(plan: EcommerceVideoPlanResult | null): boolean {
  return plan?.compliance.allow_video_generation !== false;
}

function mapResolutionToQuality(res: string): "720P" | "1080P" {
  return res.includes("720") ? "720P" : "1080P";
}

function stepCompletedFromProgress(step: PlanStep, p: EcommerceVideoPlanProgress): boolean {
  switch (step) {
    case "upload": return Boolean(p.imageUrls?.length);
    case "analyze": return p.imageDescription !== undefined;
    case "summary": return Boolean(p.summary);
    case "selling": return Boolean(p.selling);
    case "creative": return Boolean(p.creatives?.length);
    case "storyboard": return Boolean(p.storyboard);
    case "prompts": return Boolean(p.videoPrompts);
    case "compliance": return Boolean(p.compliance);
  }
}

export default function EcommerceVideoWorkspace({
  isAuthenticated,
  productImageDataUrls,
  productImageFiles = [],
  requirement,
  platform,
  aspectRatio,
  durationSeconds,
  resolution,
  onRequestLogin,
  onOpenHistory,
  triggerPlan,
  saveRef,
}: EcommerceVideoWorkspaceProps) {
  const [stage, setStage] = useState<EcommerceVideoStage>("idle");
  const [planResult, setPlanResult] = useState<EcommerceVideoPlanResult | null>(null);
  const [planProgress, setPlanProgress] = useState<EcommerceVideoPlanProgress | null>(null);
  const [scenes, setScenes] = useState<EcommerceVideoSceneTask[]>([]);
  const [completedSteps, setCompletedSteps] = useState<PlanStep[]>([]);
  const [sourceImageUrls, setSourceImageUrls] = useState<string[]>([]);
  const [currentStep, setCurrentStep] = useState<PlanStep | null>(null);
  const [failedStep, setFailedStep] = useState<PlanStep | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [actionNotice, setActionNotice] = useState<string | null>(null);
  const [previewMedia, setPreviewMedia] = useState<{ url: string; type: "image" | "video" } | null>(null);
  const [flowZoom, setFlowZoom] = useState(1);
  const actionNoticeTimerRef = useRef<number | null>(null);
  const setView = useAppStore((s) => s.setView);
  const keepaliveRestoredFingerprintRef = useRef<string | null>(null);
  const keepalivePollingStartedRef = useRef(false);
  const generation = useGenerationTasks({ sourceView: "ecommerce" });
  const sceneStoreIdMap = useRef<Map<number, string>>(new Map());
  const inputFingerprint = useMemo(
    () => buildInputFingerprint({ productImageDataUrls, requirement, platform, aspectRatio, durationSeconds, resolution }),
    [productImageDataUrls, requirement, platform, aspectRatio, durationSeconds, resolution],
  );

  const {
    abortControllerRef,
    renderAbortRef,
    runImagePhase,
    runVideoPhase,
    resumePolling,
    cancel,
    retryScene,
  } = useVideoSceneRunner({
    inputFingerprint,
    planResult,
    completedSteps,
    sourceImageUrls,
    aspectRatio,
    resolution,
    generation: generation as unknown as Parameters<typeof useVideoSceneRunner>[0]["generation"],
    sceneStoreIdMap,
    onScenesChange: setScenes,
    onStageChange: setStage,
    onError: setError,
  });

  // ── Keep-alive: restore saved state on mount ─────────────
  useEffect(() => {
    if (keepaliveRestoredFingerprintRef.current === inputFingerprint) return;
    keepaliveRestoredFingerprintRef.current = inputFingerprint;
    const saved = loadEcommerceVideoState(inputFingerprint);
    if (!saved) return;
    if (saved.stage === "idle" || saved.stage === "cancelled") return;
    // Restore completed / in-progress states — results persist across page switches
    setStage(saved.stage);
    setCompletedSteps(saved.completedSteps || []);
    setPlanResult(saved.planResult);
    setPlanProgress((saved as { planProgress?: EcommerceVideoPlanProgress | null }).planProgress || null);
    setScenes(saved.scenes || []);
    setSourceImageUrls(saved.sourceImageUrls || saved.planResult?.imageUrls || []);
  }, [inputFingerprint]);

  // ── Keep-alive: save state on changes ───────────────────
  useEffect(() => {
    if (stage === "idle" || stage === "cancelled") return;
    saveEcommerceVideoState({ inputFingerprint, stage, completedSteps, planResult, planProgress, scenes, sourceImageUrls });
  }, [inputFingerprint, stage, completedSteps, planResult, planProgress, scenes, sourceImageUrls]);

  // ── Auto-advance: automatically run the full pipeline ─────────
  useEffect(() => {
    const delay = 600;
    if (stage === "planned" && planResult && scenes.length > 0) {
      if (!planAllowsVideoGeneration(planResult)) {
        setError(buildComplianceFailureMessage(planResult.compliance));
        return;
      }
      const timer = setTimeout(() => { void runImagePhase(scenes); }, delay);
      return () => clearTimeout(timer);
    }
    if (stage === "imaged" && scenes.every((s) => s.imageUrl)) {
      const timer = setTimeout(() => { void runVideoPhase(scenes); }, delay);
      return () => clearTimeout(timer);
    }
  }, [stage, scenes, planResult]);

  // ── External trigger: start plan from parent ────────────────
  const triggerPlanPrevRef = useRef(0);
  useEffect(() => {
    if (typeof triggerPlan === "number" && triggerPlan > 0 && triggerPlan !== triggerPlanPrevRef.current) {
      triggerPlanPrevRef.current = triggerPlan;
      void handlePlan();
    }
  }, [triggerPlan]);

  // ── Auto-save: persist completed results to server ──────────
  const historySavedRef = useRef(false);
  useEffect(() => {
    if (stage !== "completed") { historySavedRef.current = false; return; }
    if (historySavedRef.current) return;
    if (!planResult || !scenes.length) return;
    historySavedRef.current = true;
    const title = planResult.storyboard?.video_title || planResult.summary?.product_name || "电商短视频";
    saveVideoHistory({
      title,
      config: { platform, aspectRatio, durationSeconds, resolution },
      plan: planResult as unknown as Record<string, unknown>,
      scenes: scenes.map((s) => ({ sceneId: s.sceneId, prompt: s.prompt, imageUrl: s.imageUrl, videoUrl: s.resultUrl })),
      sourceImageUrls,
    }).catch(() => {});
    void saveUnifiedEcommerceGenerationRecord({
      clientRecordId: `ecommerce-video-${inputFingerprint}-${Date.now()}`,
      title,
      mode: "short-video",
      prompt: requirement,
      sourceImages: sourceImageUrls.map((url, index) => ({ url, label: `source-${index + 1}` })),
      results: scenes
        .filter((scene) => Boolean(scene.resultUrl))
        .map((scene) => ({
          url: scene.resultUrl!,
          label: `scene-${scene.sceneId}`,
          mediaType: "video",
          taskId: scene.taskId,
        })),
      taskIds: scenes.map((scene) => scene.taskId).filter((taskId): taskId is string => Boolean(taskId)),
      config: { platform, aspectRatio, durationSeconds, resolution },
      result: {
        plan: planResult as unknown as Record<string, unknown>,
        scenes: scenes.map((scene) => ({
          sceneId: scene.sceneId,
          prompt: scene.prompt,
          imageUrl: scene.imageUrl,
          videoUrl: scene.resultUrl,
          status: scene.status,
        })),
      },
      metadata: { inputFingerprint },
    });
  }, [stage, planResult, scenes, sourceImageUrls, platform, aspectRatio, durationSeconds, resolution, inputFingerprint, requirement]);

  // ── Expose manual save via ref ──────────────────────────
  const planResultRef = useRef(planResult);
  planResultRef.current = planResult;
  const scenesRef = useRef(scenes);
  scenesRef.current = scenes;
  const sourceImageUrlsRef = useRef(sourceImageUrls);
  sourceImageUrlsRef.current = sourceImageUrls;

  useEffect(() => {
    if (!saveRef) return;
    saveRef.current = () => {
      const currentPlan = planResultRef.current;
      const currentScenes = scenesRef.current;
      const currentSources = sourceImageUrlsRef.current;
      if (!currentPlan || !currentScenes.length) return;
      const title = currentPlan.storyboard?.video_title || currentPlan.summary?.product_name || "电商短视频";
      saveVideoHistory({
        title,
        config: { platform, aspectRatio, durationSeconds, resolution },
        plan: currentPlan as unknown as Record<string, unknown>,
        scenes: currentScenes.map((s) => ({ sceneId: s.sceneId, prompt: s.prompt, imageUrl: s.imageUrl, videoUrl: s.resultUrl })),
        sourceImageUrls: currentSources,
      }).catch(() => {});
      void saveUnifiedEcommerceGenerationRecord({
        clientRecordId: `ecommerce-video-manual-${inputFingerprint}-${Date.now()}`,
        title,
        mode: "short-video",
        prompt: requirement,
        sourceImages: currentSources.map((url, index) => ({ url, label: `source-${index + 1}` })),
        results: currentScenes
          .filter((scene) => Boolean(scene.resultUrl))
          .map((scene) => ({
            url: scene.resultUrl!,
            label: `scene-${scene.sceneId}`,
            mediaType: "video",
            taskId: scene.taskId,
          })),
        taskIds: currentScenes.map((scene) => scene.taskId).filter((taskId): taskId is string => Boolean(taskId)),
        config: { platform, aspectRatio, durationSeconds, resolution },
        result: {
          plan: currentPlan as unknown as Record<string, unknown>,
          scenes: currentScenes.map((scene) => ({
            sceneId: scene.sceneId,
            prompt: scene.prompt,
            imageUrl: scene.imageUrl,
            videoUrl: scene.resultUrl,
            status: scene.status,
          })),
        },
        metadata: { inputFingerprint, manual: true },
      });
    };
  }, [saveRef, platform, aspectRatio, durationSeconds, resolution, inputFingerprint, requirement]);

  // ── Keep-alive: resume polling for running tasks ──────────
  useEffect(() => {
    if (keepalivePollingStartedRef.current) return;
    if (!scenes.length || stage === "idle" || stage === "cancelled" || stage === "completed") return;
    const hasRunningScenes = scenes.some((s) => s.status === "running" || s.status === "pending");
    if (!hasRunningScenes) return;
    keepalivePollingStartedRef.current = true;
    void resumePolling(stage, scenes);
  }, [scenes, stage, resumePolling]);

  // Note: keep-alive is NOT cleared on completion — results persist across page switches.
  // Only cleared when user explicitly starts a new plan via handlePlan.

  useEffect(() => {
    return () => {
      if (actionNoticeTimerRef.current !== null) {
        window.clearTimeout(actionNoticeTimerRef.current);
      }
    };
  }, []);

  const showNotice = (msg: string) => {
    setActionNotice(msg);
    if (actionNoticeTimerRef.current !== null) {
      window.clearTimeout(actionNoticeTimerRef.current);
    }
    actionNoticeTimerRef.current = window.setTimeout(() => {
      actionNoticeTimerRef.current = null;
      setActionNotice(null);
    }, 3000);
  };

  const handleDownload = async (url: string) => {
    try {
      await saveToolResultToLocal({
        url, name: `ecommerce-video-${Date.now()}`, type: "video",
        isVideo: true, tags: ["电商", "短视频", "生成视频"],
      });
      showNotice("下载完成");
    } catch {
      const a = document.createElement("a"); a.href = url; a.download = "ecommerce-video.mp4"; a.click();
    }
  };

  const handleSaveAsset = async (url: string) => {
    try {
      const result = await addToolResultToAssetLibrary({
        url, name: `电商短视频-${Date.now()}.mp4`, description: "电商短视频生成结果",
        type: "video", isVideo: true, tags: ["电商", "短视频"],
        metadata: { source: "ecommerce-video", platform },
      });
      showNotice(result === "server" ? "已保存到资产库" : "已保存到本地资产库");
    } catch { showNotice("保存失败"); }
  };

  const handleSaveAllAssets = async () => {
    if (!completedScenes.length) return;
    let saved = 0;
    for (const scene of completedScenes) {
      try {
        await addToolResultToAssetLibrary({
          url: scene.resultUrl!, name: `电商短视频-镜头${scene.sceneId}-${Date.now()}.mp4`,
          description: `电商短视频 - 镜头${scene.sceneId}`,
          type: "video", isVideo: true, tags: ["电商", "短视频"],
          metadata: { source: "ecommerce-video", platform, sceneId: scene.sceneId },
        });
        saved++;
      } catch { /* continue */ }
    }
    showNotice(saved > 0 ? `已保存 ${saved}/${completedScenes.length} 个视频到资产库` : "保存失败");
  };

  const handleDownloadAll = async () => {
    for (const scene of completedScenes) {
      await new Promise((r) => setTimeout(r, 300));
      const a = document.createElement("a");
      a.href = scene.resultUrl!;
      a.download = `ecommerce-video-scene-${scene.sceneId}.mp4`;
      a.click();
    }
    showNotice(`正在下载 ${completedScenes.length} 个视频`);
  };

  const handleImportToCanvas = async (url: string) => {
    try {
      await addToolResultToAssetLibrary({
        url, name: `电商短视频-${Date.now()}.mp4`, description: "电商短视频 - 导入画布",
        type: "video", isVideo: true, tags: ["电商", "短视频", "画布导入"],
        metadata: { source: "ecommerce-video", platform },
      });
      setView("canvas");
      showNotice("已保存资产并跳转画布");
    } catch { showNotice("导入失败"); }
  };

  const buildConfig = useCallback((): AdVideoUserConfig => ({
    platform, aspectRatio, durationSeconds,
    style: "痛点解决", language: "中文", market: "中国",
    needVoiceover: true, needSubtitle: true, conversionFocus: "conversion",
  }), [platform, aspectRatio, durationSeconds]);

  // ── Phase 1: Planning ──────────────────────────────────────

  const runPlanFlow = async (resume: EcommerceVideoPlanProgress | null) => {
    abortControllerRef.current?.abort();
    const controller = new AbortController();
    abortControllerRef.current = controller;
    setStage("planning"); setError(null); setFailedStep(null);
    if (!resume) {
      setCompletedSteps([]); setPlanResult(null); setScenes([]); setSourceImageUrls([]); setPlanProgress(null);
    }
    setCurrentStep(null);
    // Mutable snapshot — async handlers must persist to localStorage directly since the component may unmount
    let livePlanProgress: EcommerceVideoPlanProgress = resume ? { ...resume } : {};
    let liveCompletedSteps: PlanStep[] = resume
      ? ALL_STEPS.filter((s) => stepCompletedFromProgress(s, resume))
      : [];
    let liveCurrentStep: PlanStep | null = null;
    const persist = (stageNow: EcommerceVideoStage) => {
      saveEcommerceVideoState({
        inputFingerprint,
        stage: stageNow,
        completedSteps: liveCompletedSteps,
        planResult: null,
        planProgress: livePlanProgress,
        scenes: [],
        sourceImageUrls: livePlanProgress.imageUrls || [],
      });
    };
    try {
      const productImageSources = productImageDataUrls.map((url, index) => productImageFiles[index] ?? url);
      const result = await runVideoPlan(
        productImageSources, requirement, buildConfig(),
        {
          onStepStart: (step) => {
            liveCurrentStep = step;
            setCurrentStep(step);
          },
          onStepDone: (step) => {
            liveCompletedSteps = [...liveCompletedSteps, step];
            setCompletedSteps((prev) => [...prev, step]);
          },
          onImagesUploaded: (urls) => {
            setSourceImageUrls(urls);
            livePlanProgress = { ...livePlanProgress, imageUrls: urls };
            persist("planning");
          },
          onUploadRejected: (messages) => {
            if (messages.length) showNotice(`已跳过 ${messages.length} 张上传失败的图片`);
          },
          onPartialProgress: (progress) => {
            livePlanProgress = progress;
            setPlanProgress(progress);
            persist("planning");
          },
          resumeFrom: resume || undefined,
          signal: controller.signal,
        },
      );
      const builtScenes = buildSceneTasks(result);
      setPlanResult(result);
      setPlanProgress(null);
      setScenes(builtScenes);
      setStage("planned");
      saveEcommerceVideoState({ inputFingerprint, stage: "planned", completedSteps: [...ALL_STEPS], planResult: result, planProgress: null, scenes: builtScenes, sourceImageUrls: result.imageUrls });
    } catch (err) {
      if ((err as Error).name === "AbortError" && controller.signal.aborted) return;
      const message = err instanceof Error ? err.message : "策划失败";
      setError(message);
      // Mark the step that was in-progress as failed so user can resume
      setFailedStep((prev) => prev || liveCurrentStep);
      setStage("idle");
      // Persist partial progress so the user can resume after a page switch
      persist("idle");
    } finally { setCurrentStep(null); }
  };

  const handlePlan = async () => {
    if (!isAuthenticated) { onRequestLogin?.(); return; }
    if (!productImageDataUrls.length) {
      setError("请先上传商品图片"); return;
    }
    await runPlanFlow(null);
  };

  const handleResumePlan = async () => {
    if (!isAuthenticated) { onRequestLogin?.(); return; }
    if (!planProgress) { void handlePlan(); return; }
    await runPlanFlow(planProgress);
  };





  // ── Derived state ───────────────────────────────────────────

  const completedScenes = scenes.filter((s) => s.status === "completed" && s.resultUrl);
  const imagedScenes = scenes.filter((s) => s.imageUrl);
  const primaryVideo = completedScenes[0]?.resultUrl;
  const sourceImage = sourceImageUrls[0] || planResult?.imageUrls[0] || productImageDataUrls[0] || "";
  const flowMeta = `${platform} / ${aspectRatio} / ${durationSeconds}s / ${resolution}`;
  const hasImaging = stage === "imaging" || stage === "imaged" || stage === "rendering" || stage === "completed" || stage === "partial_failed";
  const hasRendering = stage === "rendering" || stage === "completed" || stage === "partial_failed";
  const visiblePlanSteps = PLAN_STEPS_DISPLAY.filter((s) => completedSteps.includes(s));

  return (
    <div className="ecom-video-workspace" data-stage={stage}>
      {/* ── Preview header ─────────────────────────────── */}
      <header className="ecom-video-flowbar ecom-video-preview-head" title={flowMeta}>
        <div className="ecom-video-preview-copy">
          <h1>预览</h1>
          <p>上传商品图，AI 即刻生成 <span>符合多电商平台规范</span> 的高转化率短视频素材。</p>
          <div className="ecom-video-flowbar__zoom">
            <button type="button" onClick={() => setFlowZoom((z) => Math.max(0.25, z - 0.1))} disabled={flowZoom <= 0.25} aria-label="缩小">−</button>
            <span>{Math.round(flowZoom * 100)}%</span>
            <button type="button" onClick={() => setFlowZoom((z) => Math.min(2, z + 0.1))} disabled={flowZoom >= 2} aria-label="放大">+</button>
          </div>
        </div>

        <div className="ecom-video-step-dots" aria-label="策划进度">
          {ALL_STEPS.map((step) => {
            const isDone = completedSteps.includes(step);
            const isActive = currentStep === step;
            return <span key={step} className={`ecom-video-step-dot ${isDone ? "is-done" : isActive ? "is-active" : ""}`} title={PLAN_STEP_LABELS[step]} />;
          })}
        </div>

        <div className="ecom-video-flowbar__actions">
          {onOpenHistory ? (
            <button type="button" className="ecom-video-flow-action ecom-video-flow-action--ghost" onClick={onOpenHistory} title="生成记录">
              <HistoryOutlined />
            </button>
          ) : null}
          {error ? <span className="ecom-video-flowbar__error" role="alert">{error}</span> : null}
          {stage === "idle" && planProgress && (planProgress.summary || planProgress.creatives || planProgress.storyboard) ? (
            <button type="button" className="ecom-video-flow-action ecom-video-flow-action--ghost"
              onClick={() => void handleResumePlan()} title={`从「${failedStep ? PLAN_STEP_LABELS[failedStep] : "已中断处"}」继续策划`}>
              <ReloadOutlined /> 继续
            </button>
          ) : null}
          {stage === "planned" || stage === "imaged" ? (
            <button type="button" className="ecom-video-flow-action ecom-video-flow-action--ghost"
              onClick={() => void runImagePhase(scenes)} title={stage === "imaged" ? "重新生成分镜图" : "生成图片"}>
              {stage === "imaged" ? <ReloadOutlined /> : <SendOutlined />}
            </button>
          ) : null}
          {stage === "imaged" || (stage === "partial_failed" && imagedScenes.length > 0) ? (
            <button type="button" className="ecom-video-flow-action ecom-video-flow-action--ghost"
              onClick={() => void runVideoPhase(scenes)} title={stage === "partial_failed" ? "重新生成失败的视频" : "生成视频"}>
              <SendOutlined />
            </button>
          ) : null}
          {stage === "planning" ? (
            <span className="ecom-video-flowbar__stage-label"><LoadingOutlined /> {currentStep ? PLAN_STEP_LABELS[currentStep] : "策划中"}</span>
          ) : null}
          {stage === "imaging" ? (
            <span className="ecom-video-flowbar__stage-label"><LoadingOutlined /> 生成图片中</span>
          ) : null}
          {stage === "rendering" ? (
            <span className="ecom-video-flowbar__stage-label"><LoadingOutlined /> 生成视频中</span>
          ) : null}
          {stage === "planning" || stage === "imaging" || stage === "rendering" ? (
            <button type="button" className="ecom-video-flow-action ecom-video-flow-action--danger" onClick={cancel} title="终止">
              <StopOutlined />
            </button>
          ) : null}
        </div>
      </header>

      {/* ── Flow canvas ──────────────────────────────────── */}
      <section className="ecom-video-flow-canvas" aria-label="视频分镜流程图">
        <div style={{ zoom: flowZoom, flexShrink: 0, display: "flex", alignItems: "flex-start", justifyContent: "center", minWidth: "max-content" }}>
        {!sourceImage ? (
          <div className="ecom-video-empty">
            <span>上传商品图并点击"一键策划"开始</span>
          </div>
        ) : (
          <div className="ecom-video-tree">
            {/* Source Node — 附件原图 */}
            <div className="ecom-video-tree__source">
              <article className="ecom-video-tree-node ecom-video-tree-node--source">
                <img src={sourceImage} alt="商品原图" />
              </article>
              <span className="ecom-video-tree-node__label">附件原图</span>
            </div>
      
            {/* Branch Connector — 分支连接线 */}
            <div className="ecom-video-tree__trunk" aria-hidden="true">
              <div className="ecom-video-tree__trunk-line" />
              <div className="ecom-video-tree__branches-line">
                {scenes.length > 0 ? scenes.map((s) => (
                  <div key={`trunk-${s.sceneId}`} className="ecom-video-tree__branch-tap" />
                )) : (
                  <div className="ecom-video-tree__branch-tap" />
                )}
              </div>
            </div>
      
            {/* Branches — 每个场景一条分支 */}
            <div className="ecom-video-tree__rows">
              {scenes.length > 0 ? scenes.map((scene, idx) => {
                const planDone = completedSteps.length >= ALL_STEPS.length;
                const imgReady = !!scene.imageUrl;
                const imgRunning = stage === "imaging" && (scene.status === "running" || scene.status === "pending") && !scene.imageUrl;
                const vidReady = scene.status === "completed" && scene.resultUrl;
                const vidRunning = stage === "rendering" && (scene.status === "running" || scene.status === "pending");
                const vidFailed = scene.status === "failed";
      
                return (
                  <div key={scene.sceneId} className="ecom-video-tree__row" style={{ animationDelay: `${idx * 120}ms` }}>
                    <article className={`ecom-video-tree-node ecom-video-tree-node--text${planDone ? " is-completed" : currentStep ? " is-active" : ""}`}>
                      <div className="ecom-video-tree-node__inner">
                        <span className="ecom-video-tree-node__title">分镜文本{scene.sceneId}</span>
                        <span className="ecom-video-tree-node__desc">
                          {planDone ? "已完成" : stage === "planning" ? "策划中..." : "等待策划"}
                        </span>
                      </div>
                    </article>
      
                    <div className="ecom-video-tree__arrow" aria-hidden="true">
                      <svg viewBox="0 0 40 20" fill="none"><path d="M0 10 H28 M22 4 L30 10 L22 16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
                    </div>
      
                    <article className={`ecom-video-tree-node ecom-video-tree-node--image${imgReady ? " is-completed" : imgRunning ? " is-active" : ""}`} onClick={imgReady ? () => setPreviewMedia({ url: scene.imageUrl!, type: "image" }) : undefined} style={imgReady ? { cursor: "pointer" } : undefined}>
                      {imgReady ? (
                        <img src={scene.imageUrl!} alt={`分镜${scene.sceneId}`} />
                      ) : (
                        <div className="ecom-video-tree-node__placeholder">
                          {imgRunning ? <LoadingOutlined /> : <span>待生成</span>}
                        </div>
                      )}
                      {imgRunning ? <span className="ecom-video-tree-node__progress">{scene.progress || 0}%</span> : null}
                      <span className="ecom-video-tree-node__tag">分镜图{scene.sceneId}</span>
                    </article>
      
                    <div className="ecom-video-tree__arrow" aria-hidden="true">
                      <svg viewBox="0 0 40 20" fill="none"><path d="M0 10 H28 M22 4 L30 10 L22 16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
                    </div>
      
                    <article className={`ecom-video-tree-node ecom-video-tree-node--video${vidReady ? " is-completed" : vidRunning ? " is-active" : vidFailed ? " is-failed" : ""}`} onClick={vidReady ? () => setPreviewMedia({ url: scene.resultUrl!, type: "video" }) : undefined} style={vidReady ? { cursor: "pointer" } : undefined}>
                      {vidReady ? (
                        <video src={scene.resultUrl!} muted playsInline loop autoPlay />
                      ) : (
                        <div className="ecom-video-tree-node__placeholder">
                          {vidRunning ? <LoadingOutlined /> : vidFailed ? <span>失败</span> : <span>待生成</span>}
                        </div>
                      )}
                      {vidRunning ? <span className="ecom-video-tree-node__progress">{scene.progress || 0}%</span> : null}
                      <span className="ecom-video-tree-node__tag">分镜视频{scene.sceneId}</span>
                      {vidFailed ? (
                        <button type="button" className="ecom-video-tree-node__retry"
                          onClick={(e) => { e.stopPropagation(); void retryScene(scene); }}
                          title="重试此镜头">
                          <ReloadOutlined />
                        </button>
                      ) : null}
                    </article>
                  </div>
                );
              }) : (
                <div className={`ecom-video-tree__row ecom-video-tree__row--empty${stage === "planning" ? " is-planning" : ""}`}>
                  <article className="ecom-video-tree-node ecom-video-tree-node--text">
                    <div className="ecom-video-tree-node__inner">
                      <span className="ecom-video-tree-node__title">分镜策划</span>
                      <span className="ecom-video-tree-node__desc">{stage === "planning" ? "策划中..." : "点击一键策划开始"}</span>
                    </div>
                  </article>
                  <div className="ecom-video-tree__arrow" aria-hidden="true">
                    <svg viewBox="0 0 40 20" fill="none"><path d="M0 10 H28 M22 4 L30 10 L22 16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
                  </div>
                  <article className="ecom-video-tree-node ecom-video-tree-node--image">
                    <div className="ecom-video-tree-node__placeholder">
                      {stage === "planning" ? <LoadingOutlined /> : <span>待生成</span>}
                    </div>
                    <span className="ecom-video-tree-node__tag">分镜图</span>
                  </article>
                  <div className="ecom-video-tree__arrow" aria-hidden="true">
                    <svg viewBox="0 0 40 20" fill="none"><path d="M0 10 H28 M22 4 L30 10 L22 16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
                  </div>
                  <article className="ecom-video-tree-node ecom-video-tree-node--video">
                    <div className="ecom-video-tree-node__placeholder">
                      {stage === "planning" ? <LoadingOutlined /> : <span>待生成</span>}
                    </div>
                    <span className="ecom-video-tree-node__tag">分镜视频</span>
                  </article>
                </div>
              )}
            </div>
          </div>
        )}
        </div>

        {/* ── Delivery dock ────────────────────────────── */}
        {primaryVideo ? (
          <div className="ecom-video-flow-dock" aria-label="视频交付操作">
            <button type="button" onClick={() => void handleDownloadAll()} title={`下载全部 ${completedScenes.length} 个视频`}><DownloadOutlined /></button>
            <button type="button" onClick={() => void handleSaveAllAssets()} title={`保存全部 ${completedScenes.length} 个视频到资产库`}><FolderAddOutlined /></button>
            {primaryVideo ? <button type="button" onClick={() => void handleImportToCanvas(primaryVideo)} title="导入画布"><SendOutlined /></button> : null}
            {primaryVideo ? <button type="button" onClick={() => void navigator.clipboard.writeText(primaryVideo)} title="复制链接"><CopyOutlined /></button> : null}
          </div>
        ) : null}
        {actionNotice ? <div className="ecom-video-flow-notice">{actionNotice}</div> : null}
      </section>

      {previewMedia ? (
        <div className="ecom-video-preview-overlay" onClick={() => setPreviewMedia(null)}>
          <button type="button" className="ecom-video-preview-overlay__close" onClick={() => setPreviewMedia(null)}>
            <CloseOutlined />
          </button>
          {previewMedia.type === "image" ? (
            <img src={previewMedia.url} alt="预览" onClick={(e) => e.stopPropagation()} />
          ) : (
            <video src={previewMedia.url} controls autoPlay onClick={(e) => e.stopPropagation()} />
          )}
        </div>
      ) : null}
    </div>
  );
}
