/**
 * 一键生成页面 — V21 原型 1:1 还原
 * 5 步向导：选择模板 → 选择素材 → 选择标题 → 选择配音 → 确认生成
 * 左右布局：左侧 generate-form + 右侧 generate-preview
 * 保留所有现有业务逻辑（API 调用、URL 参数、CloneModal、TTS 等）
 */
import React, { useState, useRef, useCallback, useEffect } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { Typography, message, Select } from "antd";
import {
  AudioOutlined,
  ThunderboltOutlined,
  CheckCircleFilled,
  CheckCircleOutlined,
  CloseCircleOutlined,
  LoadingOutlined,
  PlayCircleOutlined,
  PauseCircleOutlined,
  DownloadOutlined,
  ShareAltOutlined,
  SaveOutlined,
  PlusOutlined,
  MinusOutlined,
  CloseOutlined,
} from "@ant-design/icons";
import type { AssetItem } from "@/api/assets";
import { getAssets, getAssetLibraries } from "@/api/assets";
import { createEditPlan, generateEditPlan } from "@/api/editPlans";
import { getEditingTemplates } from "@/api/editingPlanner";
import { getTitles } from "@/api/titles";
import apiClient from "@/api/client";
import { fetchPresetVoices } from "@/api/voices";
import type { PresetVoiceItem } from "@/api/voices";
import { formatDuration } from "@/api/voiceClone";
import type { VoiceClone } from "@/api/voiceClone";
import CloneModal from "@/components/voice/CloneModal";
import { synthesizeSpeech, getTTSJobStatus, saveTtsToLibrary } from "@/api/tts";
import { getTags, createTag } from "@/api/tags";
import { useCloneProgress } from "@/hooks/useCloneProgress";
import { useSearchParams, useNavigate } from "react-router-dom";
import { getEditPlan } from "@/api/editPlans";
import "./generate.css";

const { Text } = Typography;

/* ── 克隆声音状态配置 ── */
const CLONE_STATUS_CONFIG: Record<string, { label: string; color: string }> = {
  ready: { label: "就绪", color: "var(--secondary-color, #10b981)" },
  processing: { label: "克隆中", color: "var(--accent-color, #f59e0b)" },
  failed: { label: "失败", color: "var(--error-color, #ef4444)" },
};

/* ── 模板渐变色映射（根据 mode 分配视觉样式） ── */
const MODE_GRADIENTS: Record<string, string> = {
  pip: "linear-gradient(135deg, #fbbf24, #f59e0b)",
  one_take: "linear-gradient(135deg, #3b82f6, #1d4ed8)",
  voice_over: "linear-gradient(135deg, #6366f1, #4f46e5)",
  voice_pip: "linear-gradient(135deg, #10b981, #059669)",
};
/* ── 配音预设卡片：从 API 动态生成，不再硬编码 ── */
const VOICE_GENDER_ICON: Record<string, string> = {
  female: "🎀",
  male: "🎙️",
  child: "🧒",
  neutral: "✨",
};

/* ── 时间线 Mock ──
 * TODO: 后端暂无时间线场景数据 API，当前使用硬编码预览数据。
 *       待后端提供 timeline/scene 接口后替换为真实 API 调用。
 */
interface TimelineScene {
  scene: string;
  time: string;
  duration: number;
}

const MOCK_TIMELINE: TimelineScene[] = [
  { scene: "主讲口播 · 开场钩子", time: "0-8s", duration: 8 },
  { scene: "产品特写 · B-roll", time: "8-20s", duration: 12 },
  { scene: "用户反馈 · 结尾", time: "20-30s", duration: 10 },
];

/* ── 步骤定义 ── */
const STEPS = [
  { key: 1, label: "选择模板" },
  { key: 2, label: "选择素材" },
  { key: 3, label: "选择标题" },
  { key: 4, label: "选择配音" },
  { key: 5, label: "确认生成" },
];

/* ── 标题选项从 API 加载，不再硬编码 ── */

/* ================================================================
   组件
   ================================================================ */

const GeneratePage: React.FC = () => {
  const navigate = useNavigate();

  /* ── 步骤状态 ── */
  const [currentStep, setCurrentStep] = useState(1);

  /* ── 模板（从 API 加载用户自制模板） ── */
  const [selectedTemplate, setSelectedTemplate] = useState("");
  const { data: userTemplates = [] } = useQuery({
    queryKey: ["generate-templates"],
    queryFn: () => getEditingTemplates(),
    staleTime: 60_000,
  });
  /* 模板加载完成后自动选中第一个 */
  useEffect(() => {
    if (userTemplates.length > 0 && !selectedTemplate) {
      setSelectedTemplate(userTemplates[0].id);
    }
  }, [userTemplates, selectedTemplate]);

  /* ── 素材 ── */
  const [selectedMaterials, setSelectedMaterials] = useState<string[]>([]);
  /* 素材选择模式：手动选择 / 自动匹配 */
  const [materialMode, setMaterialMode] = useState<"manual" | "auto">("manual");

  /* ── 标题 ── */
  const [title, setTitle] = useState("");
  const { data: userTitles = [] } = useQuery({
    queryKey: ["generate-titles"],
    queryFn: () => getTitles(),
    staleTime: 60_000,
  });
  /* 当选中模板开启了「AI自动匹配标题」，自动填入模板预设标题 */
  useEffect(() => {
    const tpl = userTemplates.find((t) => t.id === selectedTemplate);
    if (tpl?.title_config?.ai_auto_select && tpl.title_config.content) {
      setTitle(tpl.title_config.content);
    }
  }, [selectedTemplate, userTemplates]);

  /* ── 配音 ── */
  const [selectedVoice, setSelectedVoice] = useState<string>("");
  const [playingVoice, setPlayingVoice] = useState<string | null>(null);
  const [voiceMode, setVoiceMode] = useState<"preset" | "custom" | "clone">(
    "preset",
  );
  const [customVoiceText, setCustomVoiceText] = useState("");

  /* ── 生成数量 ── */
  const [generateCount, setGenerateCount] = useState(1);

  /* ── 克隆声音 ── */
  const [selectedClonedVoice, setSelectedClonedVoice] = useState<string>("");
  const [cloneModalOpen, setCloneModalOpen] = useState(false);
  const { clones: clonedVoices, addClone, hasProcessing } = useCloneProgress();

  /* ── 高级设置（隐藏但保留） ── */
  const [videoRatio] = useState("16:9");
  const [duration] = useState(30);
  const [style] = useState("business");
  const [autoSubtitles] = useState(true);
  const [bgm] = useState(true);

  /* ── 生成状态 ── */
  const [generating, setGenerating] = useState(false);
  const [progress, setProgress] = useState(0);
  const [generated, setGenerated] = useState(false);
  const [generateError, setGenerateError] = useState<string | null>(null);

  const progressTimer = useRef<ReturnType<typeof setInterval>>(undefined);
  const audioRef = useRef<HTMLAudioElement | null>(null);

  /* ── URL 参数：从剪辑计划跳转过来时携带 edit_plan_id + plan_config ── */
  const [searchParams] = useSearchParams();
  const editPlanId = searchParams.get("edit_plan_id");
  const planConfigStr = searchParams.get("plan_config");

  /** 解析 plan_config 并自动填充表单 */
  useEffect(() => {
    if (!planConfigStr) return;
    try {
      const config = JSON.parse(planConfigStr) as {
        title_config?: { content?: string; ai_auto_select?: boolean };
        subtitle_config?: { enabled?: boolean };
        bgm_config?: { enabled?: boolean; music_id?: string };
        mode?: string;
        total_duration?: number;
        segments?: Array<{ media_asset_id?: string; material_type?: string }>;
      };

      if (config.title_config?.content) {
        setTitle(config.title_config.content);
      }
      if (config.segments && config.segments.length > 0) {
        const assetIds = config.segments
          .map((s) => s.media_asset_id)
          .filter((id): id is string => !!id);
        if (assetIds.length > 0) {
          setSelectedMaterials(assetIds);
        }
      }
    } catch (err) {
      console.warn("解析 plan_config 失败:", err);
    }
  }, [planConfigStr]);

  /** 如果没有 plan_config，尝试通过 edit_plan_id 从后端拉取配置 */
  useEffect(() => {
    if (!editPlanId || planConfigStr) return;
    const loadPlanConfig = async () => {
      try {
        const plan = await getEditPlan(editPlanId);
        if (plan.name) setTitle(plan.name);
        const cfg = plan.config as Record<string, unknown>;
        if (cfg && Array.isArray(cfg.asset_ids)) {
          setSelectedMaterials(
            cfg.asset_ids.filter((v): v is string => typeof v === "string"),
          );
        }
      } catch (err) {
        console.warn("加载剪辑计划配置失败:", err);
      }
    };
    loadPlanConfig();
  }, [editPlanId, planConfigStr]);

  /* ── 预置音色 API ── */
  const { data: presetVoicesData, isLoading: presetVoicesLoading } = useQuery({
    queryKey: ["preset-voices"],
    queryFn: fetchPresetVoices,
  });
  const presetVoices: PresetVoiceItem[] = presetVoicesData?.items ?? [];

  /* ── TTS 自定义合成状态 ── */
  const [customAudioUrl, setCustomAudioUrl] = useState<string | null>(null);
  const [ttsError, setTtsError] = useState<string | null>(null);
  const [ttsJobId, setTtsJobId] = useState<string | null>(null);
  /** 合成完成后保留的 job ID，用于"存为素材" */
  const [completedTtsJobId, setCompletedTtsJobId] = useState<string | null>(
    null,
  );

  /* ── 存为素材弹窗状态 ── */
  const [saveModalOpen, setSaveModalOpen] = useState(false);
  const [saveName, setSaveName] = useState("");
  const [saveTagIds, setSaveTagIds] = useState<string[]>([]);
  const [saveNewTag, setSaveNewTag] = useState("");

  /* ── 标签列表（用于存为素材弹窗） ── */
  const { data: allTags = [] } = useQuery({
    queryKey: ["generate-save-tags"],
    queryFn: getTags,
    staleTime: 30_000,
  });

  /* ── 素材数据 API ── */
  const { data: libraries = [] } = useQuery({
    queryKey: ["asset-libraries"],
    queryFn: getAssetLibraries,
  });
  const [selectedLibraryId, setSelectedLibraryId] = useState<string>("");

  // 自动选中第一个素材库
  useEffect(() => {
    if (libraries.length > 0 && !selectedLibraryId) {
      setSelectedLibraryId(libraries[0].id);
    }
  }, [libraries, selectedLibraryId]);

  const { data: materials = [], isLoading: materialsLoading } = useQuery<
    AssetItem[]
  >({
    queryKey: ["generate-assets", selectedLibraryId],
    queryFn: () => getAssets(selectedLibraryId),
    enabled: !!selectedLibraryId,
  });

  const handleCloneSuccess = useCallback(
    (voice: VoiceClone) => {
      addClone(voice);
      setCloneModalOpen(false);
      message.success("音色克隆成功！");
    },
    [addClone],
  );

  /* ── 事件 ── */

  const toggleVoicePlay = useCallback(
    (voiceId: string, previewUrl: string | null) => {
      if (playingVoice === voiceId) {
        audioRef.current?.pause();
        audioRef.current = null;
        setPlayingVoice(null);
        return;
      }
      audioRef.current?.pause();
      if (!previewUrl) {
        message.warning("该音色暂无试听音频");
        return;
      }
      const audio = new Audio(previewUrl);
      audioRef.current = audio;
      audio.play().catch(() => {
        message.error("播放失败，请检查网络");
      });
      audio.onended = () => {
        setPlayingVoice(null);
        audioRef.current = null;
      };
      setPlayingVoice(voiceId);
    },
    [playingVoice],
  );

  /* ── TTS mutation ── */
  const synthesizeMutation = useMutation({
    mutationFn: synthesizeSpeech,
    onSuccess: (data) => {
      setTtsJobId(data.job_id);
      message.info("语音合成已提交，等待处理…");
    },
    onError: () => {
      setTtsError("语音合成请求失败，请重试");
    },
  });

  /** 轮询 TTS 任务状态 */
  useEffect(() => {
    if (!ttsJobId) return;
    let cancelled = false;
    let timer: ReturnType<typeof setTimeout>;

    const poll = async () => {
      try {
        const status = await getTTSJobStatus(ttsJobId);
        if (cancelled) return;
        if (status.status === "completed") {
          setCustomAudioUrl(status.output_audio_url);
          setCompletedTtsJobId(ttsJobId);
          setTtsJobId(null);
          setTtsError(null);
          message.success("语音合成完成！");
          return;
        }
        if (status.status === "failed" || status.status === "cancelled") {
          setTtsError(status.error_message || "语音合成失败");
          setTtsJobId(null);
          return;
        }
        timer = setTimeout(poll, 2000);
      } catch {
        if (!cancelled) {
          setTtsError("查询合成状态失败");
          setTtsJobId(null);
        }
      }
    };

    timer = setTimeout(poll, 2000);
    return () => {
      cancelled = true;
      clearTimeout(timer);
    };
  }, [ttsJobId]);

  /** 触发自定义文本 TTS 合成 */
  const handleSynthesizeVoice = useCallback(() => {
    if (!customVoiceText.trim()) {
      message.warning("请先输入配音文案");
      return;
    }
    setTtsError(null);
    setCustomAudioUrl(null);
    synthesizeMutation.mutate({
      text: customVoiceText.trim(),
      voice_id: selectedVoice || undefined,
      language: "zh-CN",
    });
  }, [customVoiceText, selectedVoice, synthesizeMutation]);

  /* ── 存为素材 mutation ── */
  const saveToLibraryMutation = useMutation({
    mutationFn: (params: { name?: string; tag_ids?: string[] }) =>
      saveTtsToLibrary(completedTtsJobId!, params),
    onSuccess: () => {
      message.success({
        content: (
          <span>
            已保存到配音素材库！{" "}
            <a
              onClick={handleGoToLibrary}
              style={{
                color: "var(--primary-500, #6366f1)",
                cursor: "pointer",
              }}
            >
              去素材库查看
            </a>
          </span>
        ),
        duration: 5,
      });
      setSaveModalOpen(false);
      setSaveName("");
      setSaveTagIds([]);
      setSaveNewTag("");
      setCompletedTtsJobId(null);
      setCustomAudioUrl(null);
    },
    onError: (err: Error) => {
      message.error(`保存失败：${err.message || "请重试"}`);
    },
  });

  /** 打开存为素材弹窗 */
  const handleOpenSaveModal = useCallback(() => {
    setSaveName("");
    setSaveTagIds([]);
    setSaveNewTag("");
    setSaveModalOpen(true);
  }, []);

  /** 确认保存 */
  const handleConfirmSave = useCallback(() => {
    if (!completedTtsJobId) return;
    saveToLibraryMutation.mutate({
      name: saveName.trim() || undefined,
      tag_ids: saveTagIds.length > 0 ? saveTagIds : undefined,
    });
  }, [completedTtsJobId, saveName, saveTagIds, saveToLibraryMutation]);

  /** 在弹窗中新增标签（先创建再选中） */
  const handleAddTagInModal = useCallback(
    async (tagName: string) => {
      const trimmed = tagName.trim();
      if (!trimmed) return;
      /* 已在选中列表则跳过 */
      const existing = allTags.find((t) => t.name === trimmed);
      if (existing) {
        if (!saveTagIds.includes(existing.id)) {
          setSaveTagIds((prev) => [...prev, existing.id]);
        }
        return;
      }
      try {
        const created = await createTag(trimmed);
        setSaveTagIds((prev) => [...prev, created.id]);
        setSaveNewTag("");
      } catch {
        message.error(`创建标签"${trimmed}"失败`);
      }
    },
    [allTags, saveTagIds],
  );

  /** 保存成功后跳转到素材库 */
  const handleGoToLibrary = useCallback(() => {
    navigate("/app/voice-materials");
  }, [navigate]);

  const handleGenerate = useCallback(async () => {
    console.log("[handleGenerate] 开始生成, 参数:", {
      title,
      selectedTemplate,
      selectedMaterials,
      voiceMode,
    });
    if (!title.trim()) {
      message.warning("请先选择或输入标题");
      return;
    }
    if (materialMode === "manual" && selectedMaterials.length === 0) {
      message.warning("请至少选择一个素材");
      return;
    }

    if (voiceMode === "clone" && !selectedClonedVoice) {
      message.warning("请先选择一个克隆音色");
      return;
    }

    setGenerating(true);
    setProgress(0);
    setGenerated(false);
    setGenerateError(null);

    try {
      const voiceConfig: Record<string, unknown> = {};
      if (voiceMode === "preset") {
        voiceConfig.voice_id = selectedVoice || undefined;
      } else if (voiceMode === "clone") {
        voiceConfig.voice_clone_profile_id = selectedClonedVoice || undefined;
      } else if (voiceMode === "custom") {
        voiceConfig.voice_id = selectedVoice || undefined;
        if (customAudioUrl) voiceConfig.custom_audio_url = customAudioUrl;
        if (customVoiceText.trim())
          voiceConfig.custom_text = customVoiceText.trim();
      }

      const plan = await createEditPlan({
        template_id: selectedTemplate,
        name: title.trim(),
        config: {
          asset_ids: selectedMaterials,
          ...voiceConfig,
          ratio: videoRatio,
          style,
          duration,
          auto_subtitles: autoSubtitles,
          bgm,
          generate_count: generateCount,
          material_mode: materialMode,
        },
        total_duration: duration,
        source_edit_plan_id: editPlanId || undefined,
      });

      await generateEditPlan(plan.id);

      const poll = async () => {
        try {
          const status = await apiClient.get(
            `/edit-plans/${plan.id}/generation-status`,
          );
          const data = status.data;

          if (data.plan_status === "completed") {
            setProgress(100);
            setGenerating(false);
            setGenerated(true);
            message.success("视频生成完成！");
            return;
          }
          if (data.plan_status === "failed") {
            setGenerating(false);
            // 提取后端返回的错误详情，便于排查
            const errorMsg =
              data.error_message ||
              data.error ||
              data.message ||
              (data.clips || []).find(
                (c: { status: string }) => c.status === "failed",
              )?.error_message ||
              "视频生成失败，请联系管理员或重试";
            console.error("[生成失败] planId:", plan.id, "响应:", data);
            setGenerateError(errorMsg);
            message.error(errorMsg);
            return;
          }

          const clips = data.clips || [];
          const total = clips.length || 1;
          const done = clips.filter(
            (c: { status: string }) => c.status === "completed",
          ).length;
          setProgress(Math.round((done / total) * 100));

          progressTimer.current = setTimeout(
            poll,
            2000,
          ) as unknown as ReturnType<typeof setInterval>;
        } catch (pollErr) {
          // 轮询接口本身出错（网络/鉴权等），记录并继续轮询一次
          console.error("[轮询出错] planId:", plan.id, pollErr);
          progressTimer.current = setTimeout(
            poll,
            3000,
          ) as unknown as ReturnType<typeof setInterval>;
        }
      };

      progressTimer.current = setTimeout(poll, 2000) as unknown as ReturnType<
        typeof setInterval
      >;
    } catch (err: unknown) {
      console.error("[handleGenerate] 生成失败:", err);
      setGenerating(false);
      // 提取 axios 响应中的后端错误信息
      const axiosErr = err as {
        response?: {
          data?: {
            message?: string;
            error?: string;
            detail?: string;
            msg?: string;
          };
        };
        message?: string;
      };
      const backendMsg =
        axiosErr.response?.data?.message ||
        axiosErr.response?.data?.error ||
        axiosErr.response?.data?.detail ||
        axiosErr.response?.data?.msg ||
        axiosErr.message ||
        "";
      console.error(
        "[handleGenerate] 错误信息:",
        backendMsg,
        "完整错误:",
        axiosErr,
      );
      const errorMsg = backendMsg || "生成失败，请检查网络后重试或联系管理员";
      setGenerateError(errorMsg);
      message.error(errorMsg);
    }
  }, [
    title,
    selectedMaterials,
    selectedVoice,
    voiceMode,
    selectedClonedVoice,
    customAudioUrl,
    customVoiceText,
    videoRatio,
    style,
    duration,
    autoSubtitles,
    bgm,
    editPlanId,
    selectedTemplate,
    generateCount,
    materialMode,
  ]);

  /* ── 步骤导航 ── */
  const goNext = useCallback(() => {
    if (currentStep === 1 && !selectedTemplate) {
      message.warning("请先选择一个模板");
      return;
    }
    if (
      currentStep === 2 &&
      materialMode === "manual" &&
      selectedMaterials.length === 0
    ) {
      message.warning("请至少选择一个素材");
      return;
    }
    if (currentStep === 3 && !title.trim()) {
      message.warning("请选择或输入标题");
      return;
    }
    if (currentStep < 5) {
      setCurrentStep((s) => s + 1);
    }
  }, [
    currentStep,
    selectedTemplate,
    selectedMaterials.length,
    title,
    materialMode,
  ]);

  const goPrev = useCallback(() => {
    if (currentStep > 1) {
      setCurrentStep((s) => s - 1);
    }
  }, [currentStep]);

  /* ── 辅助 ── */
  const getTemplateName = () =>
    userTemplates.find((t) => t.id === selectedTemplate)?.name ?? "未选择";

  const getVoiceName = () => {
    if (voiceMode === "clone") {
      const cv = clonedVoices.find((v) => v.id === selectedClonedVoice);
      return cv ? cv.name : "未选择";
    }
    const pv = presetVoices.find((v) => v.voice_id === selectedVoice);
    return pv ? pv.name : "未选择";
  };

  /* ================================================================
     渲染 — 步骤内容
     ================================================================ */

  /** 步骤 1：选择模板 */
  const renderStep1 = () => (
    <div className="xx-form-section">
      <h3>🎨 选择模板</h3>
      {userTemplates.length === 0 ? (
        <div className="xx-empty-state">
          <p>暂无可用模板</p>
          <p style={{ fontSize: 13, color: "var(--text-tertiary)" }}>
            请先在「剪辑计划编辑器」中创建模板
          </p>
        </div>
      ) : (
        <div className="xx-choice-list">
          {userTemplates.map((tpl) => (
            <div
              key={tpl.id}
              className={`xx-choice-item ${selectedTemplate === tpl.id ? "selected" : ""}`}
              onClick={() => setSelectedTemplate(tpl.id)}
              role="button"
              tabIndex={0}
              aria-pressed={selectedTemplate === tpl.id}
              onKeyDown={(e) => {
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault();
                  setSelectedTemplate(tpl.id);
                }
              }}
            >
              <span className="xx-choice-check">✓</span>
              <div
                className="xx-choice-thumb"
                style={{
                  background: MODE_GRADIENTS[tpl.mode] || MODE_GRADIENTS.pip,
                }}
              >
                🎬
              </div>
              <h4>{tpl.name}</h4>
              <p>
                {tpl.estimated_duration}s · {tpl.segments.length}片段
              </p>
              {tpl.tags.length > 0 && (
                <div
                  style={{
                    display: "flex",
                    gap: 4,
                    flexWrap: "wrap",
                    marginTop: 4,
                  }}
                >
                  {tpl.tags.map((tag) => (
                    <span
                      key={tag}
                      style={{
                        fontSize: 11,
                        padding: "1px 6px",
                        borderRadius: 6,
                        background: "var(--bg-secondary)",
                        color: "var(--text-secondary)",
                      }}
                    >
                      {tag}
                    </span>
                  ))}
                </div>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );

  /** 步骤 2：选择素材（双模式：手动选择 / 自动匹配） */
  const renderStep2 = () => (
    <div className="xx-form-section">
      <h3>📦 选择素材</h3>

      {/* ── 模式切换 Tab ── */}
      <div className="xx-material-mode-tabs">
        <button
          className={`xx-material-mode-tab ${materialMode === "manual" ? "active" : ""}`}
          onClick={() => setMaterialMode("manual")}
          type="button"
        >
          手动选择素材
        </button>
        <button
          className={`xx-material-mode-tab ${materialMode === "auto" ? "active" : ""}`}
          onClick={() => setMaterialMode("auto")}
          type="button"
        >
          选择素材库自动匹配
        </button>
      </div>

      {/* ── 素材库选择（两种模式共用） ── */}
      <div className="xx-form-field" style={{ marginTop: 12 }}>
        <label>选择素材库</label>
        <select
          value={selectedLibraryId}
          onChange={(e) => setSelectedLibraryId(e.target.value)}
        >
          {libraries.map((lib) => (
            <option key={lib.id} value={lib.id}>
              {lib.name}
            </option>
          ))}
        </select>
      </div>

      {/* ── 手动选择模式 ── */}
      {materialMode === "manual" && (
        <>
          <div
            style={{
              marginTop: 14,
              display: "flex",
              alignItems: "center",
              gap: 10,
            }}
          >
            <span className="xx-pill xx-pill-ok">
              已选 {selectedMaterials.length} 个素材
            </span>
          </div>

          {/* 素材列表 */}
          <div style={{ marginTop: 14 }}>
            {materialsLoading ? (
              <Text
                style={{ color: "var(--text-secondary)", padding: "16px 0" }}
              >
                加载素材中…
              </Text>
            ) : materials.length === 0 ? (
              <Text
                style={{ color: "var(--text-secondary)", padding: "16px 0" }}
              >
                暂无素材，请先在素材库中上传
              </Text>
            ) : (
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {materials.map((m) => {
                  const checked = selectedMaterials.includes(m.id);
                  return (
                    <label
                      key={m.id}
                      style={{
                        display: "flex",
                        alignItems: "center",
                        gap: 10,
                        padding: "8px 12px",
                        background: checked
                          ? "var(--primary-soft, #eef2ff)"
                          : "#f8fafc",
                        borderRadius: 10,
                        cursor: "pointer",
                        border: checked
                          ? "1px solid var(--primary-color, #4f46e5)"
                          : "1px solid transparent",
                        transition: "all 0.15s ease",
                      }}
                    >
                      <input
                        type="checkbox"
                        checked={checked}
                        onChange={() => {
                          setSelectedMaterials((prev) =>
                            prev.includes(m.id)
                              ? prev.filter((id) => id !== m.id)
                              : [...prev, m.id],
                          );
                        }}
                        style={{ accentColor: "var(--primary-color, #4f46e5)" }}
                      />
                      <span
                        style={{
                          fontSize: 13,
                          color: "var(--text-primary)",
                          flex: 1,
                        }}
                      >
                        {m.name}
                      </span>
                      <span
                        style={{
                          fontSize: 11,
                          color: "var(--text-tertiary, #94a3b8)",
                        }}
                      >
                        {m.mime_type.split("/")[1].toUpperCase()}
                      </span>
                    </label>
                  );
                })}
              </div>
            )}
          </div>
        </>
      )}

      {/* ── 自动匹配模式 ── */}
      {materialMode === "auto" && (
        <div className="xx-auto-match-card">
          <div className="xx-auto-match-icon">🤖</div>
          <div className="xx-auto-match-body">
            <h4 className="xx-auto-match-title">智能素材匹配</h4>
            <p className="xx-auto-match-desc">
              系统将根据所选模板和标题，从素材库中自动分析并匹配最合适的素材进行视频生成。
              无需手动挑选，AI
              会综合素材质量、时长、内容相关性等维度进行智能筛选。
            </p>
            <div className="xx-auto-match-features">
              <span className="xx-auto-match-feature">📊 质量评分筛选</span>
              <span className="xx-auto-match-feature">🎯 内容相关性匹配</span>
              <span className="xx-auto-match-feature">⏱️ 时长智能分配</span>
            </div>
          </div>
          {materialsLoading ? (
            <Text
              style={{
                color: "var(--text-secondary)",
                fontSize: 12,
                marginTop: 8,
              }}
            >
              扫描素材库中…
            </Text>
          ) : (
            <Text
              style={{
                color: "var(--text-tertiary, #94a3b8)",
                fontSize: 12,
                marginTop: 8,
              }}
            >
              当前素材库共 {materials.length} 个素材可供匹配
            </Text>
          )}
        </div>
      )}
    </div>
  );

  /** 步骤 3：选择标题 */
  const renderStep3 = () => (
    <div className="xx-form-section">
      <h3>📝 选择标题</h3>
      <div className="xx-form-field">
        <label>从标题库选择</label>
        <Select
          placeholder="请选择标题…"
          allowClear
          showSearch
          style={{ width: "100%" }}
          value={title || undefined}
          onChange={(val) => setTitle(val || "")}
          options={userTitles.map((t) => ({
            label: t.content,
            value: t.content,
          }))}
          filterOption={(input, option) =>
            ((option?.label as string) || "")
              .toLowerCase()
              .includes(input.toLowerCase())
          }
          notFoundContent={
            userTitles.length === 0 ? (
              <span style={{ color: "var(--text-tertiary)", fontSize: 13 }}>
                标题库为空，请前往「标题管理」添加
              </span>
            ) : null
          }
        />
      </div>
      <div className="xx-form-field" style={{ marginTop: 14 }}>
        <label>或手动输入</label>
        <input
          placeholder="输入自定义标题…"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          maxLength={50}
        />
      </div>
      {userTitles.length === 0 && (
        <p
          style={{ fontSize: 12, color: "var(--text-tertiary)", marginTop: 8 }}
        >
          标题库为空，请前往「标题管理」添加标题，或手动输入
        </p>
      )}
    </div>
  );

  /** 步骤 4：选择配音 */
  const renderStep4 = () => (
    <div className="xx-form-section">
      <h3>🎙️ 选择配音</h3>

      {/* 配音方式选择卡片 — 从 API 预设音色动态生成 */}
      <div className="xx-voice-choice-list" style={{ marginBottom: 16 }}>
        {presetVoices.slice(0, 3).map((v) => (
          <div
            key={v.voice_id}
            className={`xx-voice-choice-item ${voiceMode === "preset" && selectedVoice === v.voice_id ? "selected" : ""}`}
            onClick={() => {
              setVoiceMode("preset");
              setSelectedVoice(v.voice_id);
            }}
            role="button"
            tabIndex={0}
            aria-pressed={
              voiceMode === "preset" && selectedVoice === v.voice_id
            }
            onKeyDown={(e) => {
              if (e.key === "Enter" || e.key === " ") {
                e.preventDefault();
                setVoiceMode("preset");
                setSelectedVoice(v.voice_id);
              }
            }}
          >
            <span className="xx-voice-choice-check">✓</span>
            <div className="xx-voice-choice-avatar">
              {VOICE_GENDER_ICON[v.gender] ?? "✨"}
            </div>
            <div className="xx-voice-choice-info">
              <h4>{v.name}</h4>
              <p>{v.description}</p>
            </div>
          </div>
        ))}
        {presetVoicesLoading && (
          <div className="xx-voice-choice-item" style={{ opacity: 0.5 }}>
            <div className="xx-voice-choice-avatar">⏳</div>
            <div className="xx-voice-choice-info">
              <h4>加载中…</h4>
            </div>
          </div>
        )}
        {/* 克隆我的声音 */}
        <div
          className={`xx-voice-choice-item ${voiceMode === "clone" ? "selected" : ""}`}
          onClick={() => setVoiceMode("clone")}
          role="button"
          tabIndex={0}
          aria-pressed={voiceMode === "clone"}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.preventDefault();
              setVoiceMode("clone");
            }
          }}
        >
          <span className="xx-voice-choice-check">✓</span>
          <div
            className="xx-voice-choice-avatar"
            style={{ background: "linear-gradient(135deg, #10b981, #059669)" }}
          >
            🎤
          </div>
          <div className="xx-voice-choice-info">
            <h4>克隆我的声音</h4>
            <p>上传语音样本克隆</p>
          </div>
        </div>
      </div>

      {/* 预设音色详细列表（当 voiceMode === preset 时显示 API 返回的音色） */}
      {voiceMode === "preset" && (
        <div>
          <div className="xx-form-field">
            <label>从配音库选择</label>
            <select
              value={selectedVoice}
              onChange={(e) => setSelectedVoice(e.target.value)}
            >
              <option value="">请选择配音…</option>
              {presetVoicesLoading ? (
                <option disabled>加载中…</option>
              ) : (
                presetVoices.map((v) => (
                  <option key={v.voice_id} value={v.voice_id}>
                    {v.name} — {v.description}
                  </option>
                ))
              )}
            </select>
          </div>
          {/* 试听按钮 */}
          {presetVoices.length > 0 && (
            <div
              style={{
                display: "flex",
                gap: 8,
                flexWrap: "wrap",
                marginTop: 8,
              }}
            >
              {presetVoices.slice(0, 4).map((v) => (
                <button
                  key={v.voice_id}
                  className="xx-btn xx-btn-ghost"
                  style={{ height: 32, padding: "0 12px", fontSize: 12 }}
                  onClick={() => toggleVoicePlay(v.voice_id, v.preview_url)}
                >
                  {playingVoice === v.voice_id ? (
                    <>
                      <PauseCircleOutlined /> 停止
                    </>
                  ) : (
                    <>
                      <PlayCircleOutlined /> {v.name}
                    </>
                  )}
                </button>
              ))}
            </div>
          )}
        </div>
      )}

      {/* 自定义录制 */}
      {voiceMode === "custom" && (
        <div>
          <textarea
            placeholder="输入配音文案，点击合成按钮生成语音…"
            value={customVoiceText}
            onChange={(e) => setCustomVoiceText(e.target.value)}
            maxLength={500}
            style={{
              width: "100%",
              minHeight: 100,
              border: "1px solid var(--border-color, #e2e8f0)",
              borderRadius: "var(--radius-sm, 10px)",
              padding: 12,
              fontSize: 14,
              resize: "vertical",
              outline: "none",
            }}
          />
          <div style={{ marginTop: 12, display: "flex", gap: 8 }}>
            <button
              className="xx-btn xx-btn-ghost"
              disabled={!customVoiceText.trim() || synthesizeMutation.isPending}
              onClick={handleSynthesizeVoice}
            >
              <AudioOutlined />{" "}
              {synthesizeMutation.isPending ? "合成中…" : "合成语音"}
            </button>
          </div>
          {ttsError && (
            <Text
              style={{
                color: "var(--error, #ef4444)",
                marginTop: 8,
                display: "block",
              }}
            >
              {ttsError}
            </Text>
          )}
          {customAudioUrl && completedTtsJobId && (
            <div
              style={{
                marginTop: 8,
                display: "flex",
                alignItems: "center",
                gap: 12,
              }}
            >
              <Text style={{ color: "var(--success, #10b981)" }}>
                ✓ 语音合成完成
              </Text>
              <button
                className="xx-btn xx-btn-primary"
                style={{ height: 30, padding: "0 14px", fontSize: 12 }}
                onClick={handleOpenSaveModal}
              >
                <SaveOutlined /> 存为素材
              </button>
            </div>
          )}

          {/* ── 存为素材弹窗 ── */}
          {saveModalOpen && (
            <div
              className="xx-save-modal-overlay"
              onClick={() => setSaveModalOpen(false)}
            >
              <div
                className="xx-save-modal"
                onClick={(e) => e.stopPropagation()}
              >
                <div className="xx-save-modal-header">
                  <span>保存到配音素材库</span>
                  <button
                    className="xx-save-modal-close"
                    onClick={() => setSaveModalOpen(false)}
                  >
                    <CloseOutlined />
                  </button>
                </div>
                <div className="xx-save-modal-body">
                  <label className="xx-save-modal-label">素材名称</label>
                  <input
                    className="xx-save-modal-input"
                    placeholder="留空则自动生成名称"
                    value={saveName}
                    onChange={(e) => setSaveName(e.target.value)}
                    maxLength={50}
                  />
                  <label className="xx-save-modal-label">
                    标签
                    <span
                      style={{
                        fontWeight: 400,
                        color: "var(--text-tertiary, #94a3b8)",
                      }}
                    >
                      （可选）
                    </span>
                  </label>
                  <div className="xx-save-modal-tags">
                    {saveTagIds.map((id) => {
                      const tag = allTags.find((t) => t.id === id);
                      return tag ? (
                        <span key={id} className="xx-save-modal-tag active">
                          {tag.name}
                          <CloseOutlined
                            className="xx-save-modal-tag-remove"
                            onClick={() =>
                              setSaveTagIds((prev) =>
                                prev.filter((x) => x !== id),
                              )
                            }
                          />
                        </span>
                      ) : null;
                    })}
                    <input
                      className="xx-save-modal-tag-input"
                      placeholder="输入标签名回车添加"
                      value={saveNewTag}
                      onChange={(e) => setSaveNewTag(e.target.value)}
                      onKeyDown={(e) => {
                        if (e.key === "Enter") {
                          e.preventDefault();
                          handleAddTagInModal(saveNewTag);
                        }
                      }}
                    />
                  </div>
                  {allTags.length > 0 && (
                    <div className="xx-save-modal-tag-presets">
                      {allTags
                        .filter((t) => !saveTagIds.includes(t.id))
                        .slice(0, 12)
                        .map((t) => (
                          <button
                            key={t.id}
                            className="xx-save-modal-tag-preset"
                            onClick={() =>
                              setSaveTagIds((prev) => [...prev, t.id])
                            }
                          >
                            {t.name}
                            <PlusOutlined
                              style={{ fontSize: 10, marginLeft: 4 }}
                            />
                          </button>
                        ))}
                    </div>
                  )}
                </div>
                <div className="xx-save-modal-footer">
                  <button
                    className="xx-btn xx-btn-ghost"
                    onClick={() => setSaveModalOpen(false)}
                  >
                    取消
                  </button>
                  <button
                    className="xx-btn xx-btn-primary"
                    disabled={saveToLibraryMutation.isPending}
                    onClick={handleConfirmSave}
                  >
                    {saveToLibraryMutation.isPending ? "保存中…" : "保存"}
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
      )}

      {/* 克隆声音展开区域 */}
      {voiceMode === "clone" && (
        <div className="xx-clone-section">
          <p className="xx-clone-section-title">克隆我的声音</p>
          <Text
            style={{ fontSize: 12, color: "var(--text-tertiary, #94a3b8)" }}
          >
            上传一段您的语音样本，AI 将克隆您的声音用于视频配音
          </Text>

          <div style={{ marginTop: 12 }}>
            <button
              className="xx-btn xx-btn-primary"
              style={{ height: 36, fontSize: 13 }}
              onClick={() => setCloneModalOpen(true)}
            >
              <ThunderboltOutlined /> 克隆新声音
            </button>
          </div>

          {/* 轮询提示 */}
          {hasProcessing && (
            <div className="xx-clone-polling-hint" style={{ marginTop: 10 }}>
              <span className="xx-clone-polling-dot" />
              正在同步克隆进度...
            </div>
          )}

          {/* 已克隆声音列表 */}
          {clonedVoices.length > 0 && (
            <div className="xx-clone-voices-list">
              {clonedVoices.map((cv) => {
                const statusCfg = CLONE_STATUS_CONFIG[cv.status];
                const isReady = cv.status === "ready";
                const selected = selectedClonedVoice === cv.id;
                return (
                  <div
                    key={cv.id}
                    className={`xx-clone-voice-row ${selected ? "selected" : ""} ${!isReady ? "disabled" : ""}`}
                    onClick={() => {
                      if (isReady) setSelectedClonedVoice(cv.id);
                    }}
                    role="button"
                    tabIndex={isReady ? 0 : -1}
                    aria-pressed={selected}
                  >
                    <div className={`xx-clone-avatar ${cv.status}`}>
                      <AudioOutlined />
                    </div>
                    <div className="xx-clone-info">
                      <div className="xx-clone-name">{cv.name}</div>
                      <div className="xx-clone-status">
                        <span
                          className="xx-clone-status-dot"
                          style={{ background: statusCfg.color }}
                        />
                        <span style={{ color: statusCfg.color }}>
                          {statusCfg.label}
                        </span>
                        {isReady && (
                          <span
                            style={{
                              color: "var(--text-secondary)",
                              marginLeft: 8,
                            }}
                          >
                            {formatDuration(cv.duration_seconds)}
                          </span>
                        )}
                      </div>
                    </div>
                    {selected && isReady && (
                      <CheckCircleFilled
                        style={{ color: "var(--primary-color, #4f46e5)" }}
                      />
                    )}
                  </div>
                );
              })}
            </div>
          )}

          {clonedVoices.length === 0 && (
            <Text
              style={{
                color: "var(--text-secondary)",
                display: "block",
                textAlign: "center",
                padding: "16px 0",
              }}
            >
              暂无克隆音色，点击「克隆新声音」开始
            </Text>
          )}

          <Text
            style={{
              fontSize: 12,
              color: "var(--text-tertiary, #94a3b8)",
              marginTop: 10,
              display: "block",
            }}
          >
            💡 提示：录音环境越安静，克隆效果越好。
          </Text>
        </div>
      )}
    </div>
  );

  /** 步骤 5：确认生成 */
  const renderStep5 = () => (
    <div className="xx-form-section">
      <h3>✨ 确认生成</h3>
      <div className="xx-summary-card">
        <div className="xx-summary-row">
          <span className="xx-summary-label">模板</span>
          <span className="xx-summary-value">{getTemplateName()}</span>
        </div>
        <div className="xx-summary-row">
          <span className="xx-summary-label">素材</span>
          <span className="xx-summary-value">
            {materialMode === "auto"
              ? "自动匹配"
              : `${selectedMaterials.length} 个素材`}
          </span>
        </div>
        <div className="xx-summary-row">
          <span className="xx-summary-label">标题</span>
          <span className="xx-summary-value">{title || "未选择"}</span>
        </div>
        <div className="xx-summary-row">
          <span className="xx-summary-label">配音</span>
          <span className="xx-summary-value">{getVoiceName()}</span>
        </div>
        <div className="xx-summary-row">
          <span className="xx-summary-label">生成数量</span>
          <span className="xx-summary-value">
            <div className="xx-count-stepper">
              <button
                className="xx-count-stepper-btn"
                disabled={generateCount <= 1 || generating}
                onClick={() => setGenerateCount((c) => Math.max(1, c - 1))}
              >
                <MinusOutlined />
              </button>
              <span className="xx-count-stepper-value">{generateCount}</span>
              <button
                className="xx-count-stepper-btn"
                disabled={generateCount >= 10 || generating}
                onClick={() => setGenerateCount((c) => Math.min(10, c + 1))}
              >
                <PlusOutlined />
              </button>
              <span className="xx-count-stepper-hint">条视频</span>
            </div>
          </span>
        </div>
      </div>

      {/* 生成进度 / 结果反馈 */}
      {(generating || generated || generateError) && (
        <div style={{ marginTop: 16 }}>
          {generating && (
            <>
              <div className="xx-progress-bar">
                <div
                  className="xx-progress-bar-fill"
                  style={{ width: `${Math.min(Math.round(progress), 100)}%` }}
                />
              </div>
              <Text style={{ color: "var(--text-secondary)", fontSize: 13 }}>
                <LoadingOutlined style={{ marginRight: 6 }} />
                正在生成视频，请稍候… {Math.round(progress)}%
              </Text>
            </>
          )}
          {generated && !generating && (
            <div
              style={{
                padding: "12px 16px",
                borderRadius: 8,
                background: "rgba(82, 196, 26, 0.08)",
                border: "1px solid rgba(82, 196, 26, 0.3)",
                display: "flex",
                alignItems: "center",
                gap: 8,
              }}
            >
              <CheckCircleOutlined style={{ color: "#52c41a", fontSize: 18 }} />
              <div>
                <Text
                  strong
                  style={{ color: "#52c41a", display: "block", fontSize: 14 }}
                >
                  视频生成完成！
                </Text>
                <Text style={{ color: "var(--text-secondary)", fontSize: 12 }}>
                  可在右侧预览或前往成片库查看
                </Text>
              </div>
            </div>
          )}
          {generateError && !generating && (
            <div
              style={{
                padding: "12px 16px",
                borderRadius: 8,
                background: "rgba(255, 77, 79, 0.08)",
                border: "1px solid rgba(255, 77, 79, 0.3)",
                display: "flex",
                alignItems: "flex-start",
                gap: 8,
              }}
            >
              <CloseCircleOutlined
                style={{
                  color: "#ff4d4f",
                  fontSize: 18,
                  marginTop: 2,
                  flexShrink: 0,
                }}
              />
              <div>
                <Text
                  strong
                  style={{ color: "#ff4d4f", display: "block", fontSize: 14 }}
                >
                  生成失败
                </Text>
                <Text style={{ color: "var(--text-secondary)", fontSize: 12 }}>
                  {generateError}
                </Text>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );

  /** 渲染当前步骤 */
  const renderCurrentStep = () => {
    switch (currentStep) {
      case 1:
        return renderStep1();
      case 2:
        return renderStep2();
      case 3:
        return renderStep3();
      case 4:
        return renderStep4();
      case 5:
        return renderStep5();
      default:
        return null;
    }
  };

  /* ================================================================
     渲染 — 主页面
     ================================================================ */

  return (
    <div className="xx-generate-page">
      {/* ── 页头 ── */}
      <div className="xx-generate-head">
        <div>
          <h2>
            <ThunderboltOutlined style={{ marginRight: 8 }} />
            一键生成
          </h2>
          <p>快速生成短视频，支持多种风格和素材组合</p>
        </div>
        {editPlanId && (
          <span
            style={{
              background: "#dbeafe",
              color: "#1d4ed8",
              fontSize: 12,
              padding: "4px 10px",
              borderRadius: 999,
              fontWeight: 600,
            }}
          >
            🎬 来自剪辑计划
          </span>
        )}
      </div>

      {/* ── 步骤条 ── */}
      <div className="xx-steps-bar">
        {STEPS.map((step, idx) => {
          const isActive = currentStep === step.key;
          const isDone = currentStep > step.key;
          const cls = [
            "xx-step-item",
            isActive ? "active" : "",
            isDone ? "done" : "",
          ]
            .filter(Boolean)
            .join(" ");
          return (
            <React.Fragment key={step.key}>
              {idx > 0 && <span className="xx-step-arrow">→</span>}
              <div
                className={cls}
                onClick={() => {
                  // 允许点击已完成的步骤回退
                  if (isDone) setCurrentStep(step.key);
                }}
                role="button"
                tabIndex={0}
              >
                <div className="xx-step-num">{isDone ? "✓" : step.key}</div>
                <span className="xx-step-label">{step.label}</span>
              </div>
            </React.Fragment>
          );
        })}
      </div>

      {/* ── 主布局 ── */}
      <div className="xx-generate-layout">
        {/* ════ 左侧：表单区 ════ */}
        <div className="xx-generate-form">
          {/* 当前步骤 */}
          {renderCurrentStep()}

          {/* 底部操作按钮 */}
          <div className="xx-step-actions">
            <button
              className="xx-btn xx-btn-ghost"
              onClick={goPrev}
              disabled={currentStep === 1}
            >
              ← 上一步
            </button>
            {currentStep < 5 ? (
              <button className="xx-btn xx-btn-primary" onClick={goNext}>
                下一步 →
              </button>
            ) : (
              <button
                className="xx-btn xx-btn-primary"
                onClick={handleGenerate}
                disabled={generating || (generated && !generateError)}
              >
                <ThunderboltOutlined />
                {generating
                  ? "生成中…"
                  : generated && !generateError
                    ? "已生成"
                    : generateError
                      ? "🔄 重新生成"
                      : "✨ 确认生成"}
              </button>
            )}
          </div>
        </div>

        {/* ════ 右侧：预览区 ════ */}
        <div className="xx-generate-preview">
          {/* 视频预览 */}
          <div className="xx-preview-video">
            {generated ? (
              <video src="" controls preload="none" />
            ) : (
              <button className="xx-play-btn" type="button">
                <PlayCircleOutlined />
              </button>
            )}
          </div>

          {/* 字幕预览 */}
          <div className="xx-preview-caption">
            {title || "3秒抓住注意力，30秒讲清卖点"}
          </div>

          {/* 时间线标题 */}
          <div className="xx-preview-title">剪辑计划预览</div>

          {/* 时间线列表 */}
          <div className="xx-preview-timeline">
            {MOCK_TIMELINE.map((item, idx) => (
              <div key={idx} className="xx-timeline-item">
                <div className="num">{idx + 1}</div>
                <span className="scene-name">{item.scene}</span>
                <span>{item.time}</span>
              </div>
            ))}
          </div>

          {/* 生成操作按钮 */}
          <div className="xx-generate-actions">
            <button
              className="xx-btn xx-btn-ghost"
              onClick={handleGenerate}
              disabled={generating}
            >
              重新生成
            </button>
            <button
              className="xx-btn xx-btn-primary"
              onClick={handleGenerate}
              disabled={generating}
            >
              <ThunderboltOutlined />
              {generating ? "生成中…" : "✨ 确认生成"}
            </button>
          </div>

          {/* 生成完成后显示下载/分享 */}
          {generated && (
            <div className="xx-generate-actions" style={{ marginTop: 8 }}>
              <button className="xx-btn xx-btn-ghost">
                <DownloadOutlined /> 下载视频
              </button>
              <button className="xx-btn xx-btn-ghost">
                <ShareAltOutlined /> 分享
              </button>
            </div>
          )}
        </div>
      </div>

      {/* ── 音色克隆弹窗 ── */}
      <CloneModal
        open={cloneModalOpen}
        onClose={() => setCloneModalOpen(false)}
        onSuccess={handleCloneSuccess}
      />
    </div>
  );
};

export default GeneratePage;
