import { useEffect, useMemo, useRef, useState } from "react";
import api from "@/shared/api/axios";
import {
  buildTextShadow,
  formatDate,
  getApiBaseUrl,
  getFontFormat,
  resolveAssetUrl,
  resolveBgmUrl,
  resolveMediaUrl,
} from "@/shared/lib/media";
import { clampTitle, clampSecondaryTitle, SECONDARY_TITLE_MAX_LENGTH } from "@/shared/lib/title";
import { useTitleInput } from "@/shared/hooks/useTitleInput";
import { useAuth } from "@/shared/contexts/AuthContext";
import { useTask } from "@/shared/contexts/TaskContext";
import { toast } from "sonner";
import { usePublishPrefetch } from "@/shared/hooks/usePublishPrefetch";
import { PublishAccount } from "@/shared/types/publish";
import { useBgm } from "@/features/home/model/useBgm";
import { useGeneratedVideos } from "@/features/home/model/useGeneratedVideos";
import { useGeneratedAudios } from "@/features/home/model/useGeneratedAudios";
import { useHomePersistence } from "@/features/home/model/useHomePersistence";
import { useMaterials } from "@/features/home/model/useMaterials";
import { useMediaPlayers } from "@/features/home/model/useMediaPlayers";
import { useRefAudios } from "@/features/home/model/useRefAudios";
import { useTitleSubtitleStyles } from "@/features/home/model/useTitleSubtitleStyles";
import { useTimelineEditor } from "@/features/home/model/useTimelineEditor";
import { useSavedScripts } from "@/features/home/model/useSavedScripts";
import { useVideoFrameCapture } from "@/features/home/model/useVideoFrameCapture";
import { ApiResponse, unwrap } from "@/shared/api/types";

const VOICES: Record<string, { id: string; name: string }[]> = {
  "zh-CN": [
    { id: "zh-CN-YunxiNeural", name: "云溪 (男声-年轻)" },
    { id: "zh-CN-YunjianNeural", name: "云健 (男声-新闻)" },
    { id: "zh-CN-YunyangNeural", name: "云扬 (男声-专业)" },
    { id: "zh-CN-XiaoxiaoNeural", name: "晓晓 (女声-活泼)" },
    { id: "zh-CN-XiaoyiNeural", name: "晓伊 (女声-温柔)" },
  ],
  "en-US": [
    { id: "en-US-GuyNeural", name: "Guy (Male)" },
    { id: "en-US-JennyNeural", name: "Jenny (Female)" },
  ],
  "ja-JP": [
    { id: "ja-JP-KeitaNeural", name: "圭太 (男声)" },
    { id: "ja-JP-NanamiNeural", name: "七海 (女声)" },
  ],
  "ko-KR": [
    { id: "ko-KR-InJoonNeural", name: "인준 (男声)" },
    { id: "ko-KR-SunHiNeural", name: "선히 (女声)" },
  ],
  "fr-FR": [
    { id: "fr-FR-HenriNeural", name: "Henri (Male)" },
    { id: "fr-FR-DeniseNeural", name: "Denise (Female)" },
  ],
  "de-DE": [
    { id: "de-DE-ConradNeural", name: "Conrad (Male)" },
    { id: "de-DE-KatjaNeural", name: "Katja (Female)" },
  ],
  "es-ES": [
    { id: "es-ES-AlvaroNeural", name: "Álvaro (Male)" },
    { id: "es-ES-ElviraNeural", name: "Elvira (Female)" },
  ],
  "ru-RU": [
    { id: "ru-RU-DmitryNeural", name: "Дмитрий (Male)" },
    { id: "ru-RU-SvetlanaNeural", name: "Светлана (Female)" },
  ],
  "it-IT": [
    { id: "it-IT-DiegoNeural", name: "Diego (Male)" },
    { id: "it-IT-ElsaNeural", name: "Elsa (Female)" },
  ],
  "pt-BR": [
    { id: "pt-BR-AntonioNeural", name: "Antonio (Male)" },
    { id: "pt-BR-FranciscaNeural", name: "Francisca (Female)" },
  ],
};

const LANG_TO_LOCALE: Record<string, string> = {
  "中文": "zh-CN",
  "English": "en-US",
  "日本語": "ja-JP",
  "한국어": "ko-KR",
  "Français": "fr-FR",
  "Deutsch": "de-DE",
  "Español": "es-ES",
  "Русский": "ru-RU",
  "Italiano": "it-IT",
  "Português": "pt-BR",
};

const DEFAULT_SHORT_TITLE_DURATION = 4;



const scrollContainerToItem = (container: HTMLDivElement, item: HTMLDivElement) => {
  const containerRect = container.getBoundingClientRect();
  const itemRect = item.getBoundingClientRect();
  const itemTop = itemRect.top - containerRect.top + container.scrollTop;
  const itemBottom = itemTop + itemRect.height;
  const viewTop = container.scrollTop;
  const viewBottom = viewTop + container.clientHeight;

  if (itemTop < viewTop) {
    container.scrollTo({ top: Math.max(itemTop - 8, 0), behavior: "smooth" });
  } else if (itemBottom > viewBottom) {
    container.scrollTo({ top: itemBottom - container.clientHeight + 8, behavior: "smooth" });
  }
};

interface GeneratedVideo {
  id: string;
  name: string;
  path: string;
  size_mb: number;
  created_at: number;
}

interface RefAudio {
  id: string;
  name: string;
  path: string;
  ref_text: string;
  duration_sec: number;
  created_at: number;
}

type LipsyncModelMode = "default" | "fast" | "advanced";

import type { Material } from "@/shared/types/material";

export const useHomeController = () => {
  const apiBase = getApiBaseUrl();

  const [selectedMaterials, setSelectedMaterials] = useState<string[]>([]);
  const [previewMaterial, setPreviewMaterial] = useState<string | null>(null);

  const [text, setText] = useState<string>("");
  const [voice, setVoice] = useState<string>("zh-CN-YunxiNeural");
  const [textLang, setTextLang] = useState<string>("zh-CN");

  // 使用全局任务状态
  const { currentTask, isGenerating, startTask } = useTask();
  const prevIsGenerating = useRef(isGenerating);

  const [generatedVideo, setGeneratedVideo] = useState<string | null>(null);
  const [selectedVideoId, setSelectedVideoId] = useState<string | null>(null);

  // 字幕和标题相关状态
  const [videoTitle, setVideoTitle] = useState<string>("");
  const [selectedSubtitleStyleId, setSelectedSubtitleStyleId] = useState<string>("");
  const [selectedTitleStyleId, setSelectedTitleStyleId] = useState<string>("");
  const [subtitleFontSize, setSubtitleFontSize] = useState<number>(80);
  const [titleFontSize, setTitleFontSize] = useState<number>(120);
  const [subtitleSizeLocked, setSubtitleSizeLocked] = useState<boolean>(false);
  const [titleSizeLocked, setTitleSizeLocked] = useState<boolean>(false);
  const [titleTopMargin, setTitleTopMargin] = useState<number>(62);
  const [titleDisplayMode, setTitleDisplayMode] = useState<"short" | "persistent">("short");
  const [subtitleBottomMargin, setSubtitleBottomMargin] = useState<number>(80);
  const [outputAspectRatio, setOutputAspectRatio] = useState<"9:16" | "16:9">("9:16");
  const [lipsyncModelMode, setLipsyncModelMode] = useState<LipsyncModelMode>("default");
  const [showStylePreview, setShowStylePreview] = useState<boolean>(false);
  const [materialDimensions, setMaterialDimensions] = useState<{ width: number; height: number } | null>(null);

  // 副标题相关状态
  const [videoSecondaryTitle, setVideoSecondaryTitle] = useState<string>("");
  const [selectedSecondaryTitleStyleId, setSelectedSecondaryTitleStyleId] = useState<string>("");
  const [secondaryTitleFontSize, setSecondaryTitleFontSize] = useState<number>(48);
  const [secondaryTitleTopMargin, setSecondaryTitleTopMargin] = useState<number>(12);
  const [secondaryTitleSizeLocked, setSecondaryTitleSizeLocked] = useState<boolean>(false);


  // 背景音乐相关状态
  const [selectedBgmId, setSelectedBgmId] = useState<string>("");
  const [enableBgm, setEnableBgm] = useState<boolean>(false);
  const [bgmVolume, setBgmVolume] = useState<number>(0.2);

  // 声音克隆相关状态
  const [ttsMode, setTtsMode] = useState<"edgetts" | "voiceclone">("edgetts");
  const [selectedRefAudio, setSelectedRefAudio] = useState<RefAudio | null>(null);
  const [refText, setRefText] = useState("");

  // 预生成配音选中 ID
  const [selectedAudioId, setSelectedAudioId] = useState<string | null>(null);

  // 语速控制
  const [speed, setSpeed] = useState<number>(1.0);

  // 语气控制（仅声音克隆模式）
  const [emotion, setEmotion] = useState<string>("normal");

  // ClipTrimmer 模态框状态
  const [clipTrimmerOpen, setClipTrimmerOpen] = useState(false);
  const [clipTrimmerSegmentId, setClipTrimmerSegmentId] = useState<string | null>(null);

  // 音频预览与重命名状态
  const [editingAudioId, setEditingAudioId] = useState<string | null>(null);
  const [editName, setEditName] = useState("");
  const [editingMaterialId, setEditingMaterialId] = useState<string | null>(null);
  const [editMaterialName, setEditMaterialName] = useState("");
  const bgmItemRefs = useRef<Record<string, HTMLDivElement | null>>({});
  const bgmListContainerRef = useRef<HTMLDivElement | null>(null);

  const materialItemRefs = useRef<Record<string, HTMLDivElement | null>>({});
  const videoItemRefs = useRef<Record<string, HTMLDivElement | null>>({});

  // 重命名参考音频
  const startEditing = (audio: RefAudio, e: React.MouseEvent) => {
    e.stopPropagation();
    setEditingAudioId(audio.id);
    // 去掉后缀名进行编辑 (体验更好)
    const nameWithoutExt = audio.name.substring(0, audio.name.lastIndexOf("."));
    setEditName(nameWithoutExt || audio.name);
  };

  const cancelEditing = (e: React.MouseEvent) => {
    e.stopPropagation();
    setEditingAudioId(null);
    setEditName("");
  };

  const saveEditing = async (audioId: string, e: React.MouseEvent) => {
    e.stopPropagation();
    if (!editName.trim()) return;

    try {
      await api.put(`/api/ref-audios/${encodeURIComponent(audioId)}`, { new_name: editName });
      setEditingAudioId(null);
      fetchRefAudios(); // 刷新列表
    } catch (err: unknown) {
      toast.error("重命名失败: " + String(err));
    }
  };

  const startMaterialEditing = (material: Material, e: React.MouseEvent) => {
    e.stopPropagation();
    setEditingMaterialId(material.id);
    const nameWithoutExt = material.name.substring(0, material.name.lastIndexOf("."));
    setEditMaterialName(nameWithoutExt || material.name);
  };

  const cancelMaterialEditing = (e: React.MouseEvent) => {
    e.stopPropagation();
    setEditingMaterialId(null);
    setEditMaterialName("");
  };

  const saveMaterialEditing = async (materialId: string, e: React.MouseEvent) => {
    e.stopPropagation();
    if (!editMaterialName.trim()) return;

    try {
      const { data: res } = await api.put<ApiResponse<{ id: string }>>(
        `/api/materials/${encodeURIComponent(materialId)}`,
        { new_name: editMaterialName.trim() }
      );
      const payload = unwrap(res);
      if (selectedMaterials.includes(materialId) && payload?.id) {
        setSelectedMaterials((prev) => prev.map((x) => (x === materialId ? payload.id : x)));
      }
      setEditingMaterialId(null);
      setEditMaterialName("");
      fetchMaterials();
    } catch (err: unknown) {
      const axiosErr = err as { response?: { data?: { message?: string } }; message?: string };
      const errorMsg = axiosErr.response?.data?.message || axiosErr.message || String(err);
      toast.error(`重命名失败: ${errorMsg}`);
    }
  };

  // AI 生成标题标签
  const [isGeneratingMeta, setIsGeneratingMeta] = useState(false);

  // AI 多语言翻译
  const [isTranslating, setIsTranslating] = useState(false);
  const [originalText, setOriginalText] = useState<string | null>(null);

  // 在线录音相关
  const [isRecording, setIsRecording] = useState(false);
  const [recordedBlob, setRecordedBlob] = useState<Blob | null>(null);
  const [recordingTime, setRecordingTime] = useState(0);
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
  const recordingIntervalRef = useRef<NodeJS.Timeout | null>(null);

  // 使用全局认证状态
  const { userId, isLoading: isAuthLoading } = useAuth();

  // 文案提取模态框
  const [extractModalOpen, setExtractModalOpen] = useState(false);

  // AI 改写模态框
  const [rewriteModalOpen, setRewriteModalOpen] = useState(false);

  // 获取存储 key 的前缀（登录用户使用 userId，未登录使用 guest）
  const storageKey = userId || "guest";

  // 使用共用的发布预加载 hook
  const { updatePrefetch: updatePublishPrefetch } = usePublishPrefetch();

  const {
    materials,
    fetchError,
    isFetching,
    lastMaterialCount,
    isUploading,
    uploadProgress,
    uploadError,
    setUploadError,
    fetchMaterials,
    toggleMaterial,
    deleteMaterial,
    handleUpload,
  } = useMaterials({
    selectedMaterials,
    setSelectedMaterials,
  });

  const {
    subtitleStyles,
    titleStyles,
    refreshSubtitleStyles,
    refreshTitleStyles,
  } = useTitleSubtitleStyles({
    isAuthLoading,

    setSelectedSubtitleStyleId,
    setSelectedTitleStyleId,
  });

  const {
    refAudios,
    isUploadingRef,
    uploadRefError,
    setUploadRefError,
    fetchRefAudios,
    uploadRefAudio,
    deleteRefAudio,
    retranscribeRefAudio,
    retranscribingId,
  } = useRefAudios({
    selectedRefAudio,
    setSelectedRefAudio,
    setRefText,
  });

  const {
    bgmList,
    bgmLoading,
    bgmError,
    fetchBgmList,
  } = useBgm({

    selectedBgmId,
    setSelectedBgmId,
  });

  const {
    playingAudioId,
    playingBgmId,
    togglePlayPreview,
    toggleBgmPreview,
  } = useMediaPlayers({
    bgmVolume,
    resolveBgmUrl,
    resolveMediaUrl,
    setSelectedBgmId,
    setEnableBgm,
  });

  const {
    generatedVideos,
    fetchGeneratedVideos,
    deleteVideo,
  } = useGeneratedVideos({
    storageKey,
    selectedVideoId,
    setSelectedVideoId,
    setGeneratedVideo,
    resolveMediaUrl,
  });

  const {
    generatedAudios,
    selectedAudio,
    isGeneratingAudio,
    audioTask,
    fetchGeneratedAudios,
    generateAudio,
    deleteAudio,
    renameAudio,
    selectAudio,
  } = useGeneratedAudios({
    selectedAudioId,
    setSelectedAudioId,
  });

  const {
    segments: timelineSegments,
    reorderSegments,
    setSourceRange,
    toCustomAssignments,
  } = useTimelineEditor({
    audioDuration: selectedAudio?.duration_sec ?? 0,
    materials,
    selectedMaterials,
    storageKey,
  });

  // 时间轴第一段素材的视频 URL（用于帧截取预览）
  // 使用后端代理 URL（同源）避免 CORS canvas taint
  const firstTimelineMaterialUrl = useMemo(() => {
    const firstSeg = timelineSegments[0];
    const matId = firstSeg?.materialId ?? selectedMaterials[0];
    if (!matId) return null;
    const mat = materials.find((m) => m.id === matId);
    if (!mat) return null;
    return `/api/materials/stream/${mat.id}`;
  }, [materials, timelineSegments, selectedMaterials]);

  const materialPosterUrl = useVideoFrameCapture(showStylePreview ? firstTimelineMaterialUrl : null);

  useEffect(() => {
    if (isAuthLoading || !userId) return;
    let active = true;

    const prefetchAccounts = async () => {
      try {
        const { data: res } = await api.get<ApiResponse<{ accounts: PublishAccount[] }>>(
          "/api/publish/accounts"
        );
        if (!active) return;
        const payload = unwrap(res);
        updatePublishPrefetch({ accounts: payload.accounts || [] });
      } catch (error) {
        console.error("预取账号失败:", error);
      }
    };

    void prefetchAccounts();
    return () => {
      active = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isAuthLoading, userId]);

  useEffect(() => {
    if (generatedVideos.length === 0) return;
    const prefetched = generatedVideos.map((video) => ({
      id: video.id,
      name: formatDate(video.created_at) + ` (${video.size_mb.toFixed(1)}MB)`,
      path: video.path.startsWith("/") ? video.path.slice(1) : video.path,
    }));
    updatePublishPrefetch({ videos: prefetched });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [generatedVideos]);

  const { isRestored } = useHomePersistence({
    isAuthLoading,
    storageKey,
    text,
    setText,
    videoTitle,
    setVideoTitle,
    videoSecondaryTitle,
    setVideoSecondaryTitle,
    ttsMode,
    setTtsMode,
    voice,
    setVoice,
    textLang,
    setTextLang,
    selectedMaterials,
    setSelectedMaterials,
    selectedSubtitleStyleId,
    setSelectedSubtitleStyleId,
    selectedTitleStyleId,
    setSelectedTitleStyleId,
    selectedSecondaryTitleStyleId,
    setSelectedSecondaryTitleStyleId,
    subtitleFontSize,
    setSubtitleFontSize,
    titleFontSize,
    setTitleFontSize,
    secondaryTitleFontSize,
    setSecondaryTitleFontSize,
    setSubtitleSizeLocked,
    setTitleSizeLocked,
    setSecondaryTitleSizeLocked,
    titleTopMargin,
    setTitleTopMargin,
    secondaryTitleTopMargin,
    setSecondaryTitleTopMargin,
    titleDisplayMode,
    setTitleDisplayMode,
    subtitleBottomMargin,
    setSubtitleBottomMargin,
    outputAspectRatio,
    setOutputAspectRatio,
    lipsyncModelMode,
    setLipsyncModelMode,
    selectedBgmId,
    setSelectedBgmId,
    bgmVolume,
    setBgmVolume,
    enableBgm,
    setEnableBgm,
    selectedVideoId,
    setSelectedVideoId,
    selectedRefAudio,
    selectedAudioId,
    setSelectedAudioId,
    speed,
    setSpeed,
    emotion,
    setEmotion,
  });

  const { savedScripts, saveScript, deleteScript: deleteSavedScript } = useSavedScripts(storageKey);

  const handleSaveScript = () => {
    if (!text.trim()) return;
    saveScript(text);
    toast.success("文案已保存");
  };

  const syncTitleToPublish = (value: string) => {
    if (typeof window !== "undefined") {
      localStorage.setItem(`vigent_${storageKey}_publish_title`, value);
    }
  };

  const titleInput = useTitleInput({
    value: videoTitle,
    onChange: setVideoTitle,
    onCommit: syncTitleToPublish,
  });

  const secondaryTitleInput = useTitleInput({
    value: videoSecondaryTitle,
    onChange: setVideoSecondaryTitle,
    maxLength: SECONDARY_TITLE_MAX_LENGTH,
  });

  // 加载素材列表和历史视频
  useEffect(() => {
    if (isAuthLoading) return;
    void Promise.allSettled([
      fetchMaterials(),
      fetchGeneratedVideos(),
      fetchRefAudios(),
      fetchGeneratedAudios(),
      refreshSubtitleStyles(),
      refreshTitleStyles(),
      fetchBgmList(),
    ]);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isAuthLoading]);

  // 监听任务完成，自动刷新视频列表并选中最新
  useEffect(() => {
    if (prevIsGenerating.current && !isGenerating) {
      if (currentTask?.status === "completed") {
        void fetchGeneratedVideos("__latest__");
      } else {
        void fetchGeneratedVideos();
      }
    }
    prevIsGenerating.current = isGenerating;
  }, [isGenerating, currentTask, fetchGeneratedVideos]);

  useEffect(() => {
    const firstSelected = selectedMaterials[0];
    const material = materials.find((item) => item.id === firstSelected);
    if (!material?.path) {
      setMaterialDimensions(null);
      return;
    }
    const url = resolveMediaUrl(material.path);
    if (!url) {
      setMaterialDimensions(null);
      return;
    }

    let isActive = true;
    const video = document.createElement("video");
    video.preload = "metadata";
    video.src = url;
    video.load();

    const handleLoaded = () => {
      if (!isActive) return;
      if (video.videoWidth && video.videoHeight) {
        setMaterialDimensions({ width: video.videoWidth, height: video.videoHeight });
      } else {
        setMaterialDimensions(null);
      }
    };

    const handleError = () => {
      if (!isActive) return;
      setMaterialDimensions(null);
    };

    video.addEventListener("loadedmetadata", handleLoaded);
    video.addEventListener("error", handleError);

    return () => {
      isActive = false;
      video.removeEventListener("loadedmetadata", handleLoaded);
      video.removeEventListener("error", handleError);
    };
  }, [materials, selectedMaterials]);


  useEffect(() => {
    if (subtitleSizeLocked || subtitleStyles.length === 0) return;
    const active = subtitleStyles.find((s) => s.id === selectedSubtitleStyleId)
      || subtitleStyles.find((s) => s.is_default)
      || subtitleStyles[0];
    if (active?.font_size) {
      setSubtitleFontSize(active.font_size);
    }
  }, [subtitleStyles, selectedSubtitleStyleId, subtitleSizeLocked]);

  useEffect(() => {
    if (titleSizeLocked || titleStyles.length === 0) return;
    const active = titleStyles.find((s) => s.id === selectedTitleStyleId)
      || titleStyles.find((s) => s.is_default)
      || titleStyles[0];
    if (active?.font_size) {
      setTitleFontSize(active.font_size);
    }
  }, [titleStyles, selectedTitleStyleId, titleSizeLocked]);

  useEffect(() => {
    if (secondaryTitleSizeLocked || titleStyles.length === 0) return;
    const active = titleStyles.find((s) => s.id === selectedSecondaryTitleStyleId)
      || titleStyles.find((s) => s.is_default)
      || titleStyles[0];
    if (active?.font_size) {
      setSecondaryTitleFontSize(active.font_size);
    }
  }, [titleStyles, selectedSecondaryTitleStyleId, secondaryTitleSizeLocked]);

  // 移除重复的 BGM 持久化恢复逻辑 (已统一移动到 useHomePersistence 中)
  // useEffect(() => { ... })

  // 时间门控：页面加载后 1 秒内禁止所有列表自动滚动效果
  // 防止持久化恢复 + 异步数据加载触发 scrollIntoView 导致移动端页面跳动
  const scrollEffectsEnabled = useRef(false);
  useEffect(() => {
    const timer = setTimeout(() => {
      scrollEffectsEnabled.current = true;
    }, 1000);
    return () => clearTimeout(timer);
  }, []);

  // BGM 列表滚动
  useEffect(() => {
    if (!selectedBgmId || !scrollEffectsEnabled.current) return;
    const container = bgmListContainerRef.current;
    const target = bgmItemRefs.current[selectedBgmId];
    if (container && target) {
      scrollContainerToItem(container, target);
    }
  }, [selectedBgmId, bgmList]);

  // 素材列表滚动
  useEffect(() => {
    const firstSelected = selectedMaterials[0];
    if (!firstSelected || !scrollEffectsEnabled.current) return;
    const target = materialItemRefs.current[firstSelected];
    if (target) {
      target.scrollIntoView({ block: "nearest", behavior: "smooth" });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedMaterials.length]);

  // 【修复】历史视频默认选中逻辑
  // 当持久化恢复完成，且列表加载完毕，如果没选中任何视频，默认选中第一个
  useEffect(() => {
    if (isRestored && generatedVideos.length > 0 && !selectedVideoId) {
      const firstId = generatedVideos[0].id;
      setSelectedVideoId(firstId);
      setGeneratedVideo(resolveMediaUrl(generatedVideos[0].path));
    }
  }, [isRestored, generatedVideos, selectedVideoId, setSelectedVideoId, setGeneratedVideo]);

  // 【修复】BGM 默认选中逻辑
  useEffect(() => {
    if (isRestored && bgmList.length > 0 && !selectedBgmId && enableBgm) {
      setSelectedBgmId(bgmList[0].id);
    }
  }, [isRestored, bgmList, selectedBgmId, enableBgm, setSelectedBgmId]);

  // 视频列表滚动
  useEffect(() => {
    if (!selectedVideoId || !scrollEffectsEnabled.current) return;
    const target = videoItemRefs.current[selectedVideoId];
    if (target) {
      target.scrollIntoView({ block: "nearest", behavior: "smooth" });
    }
  }, [selectedVideoId, generatedVideos]);

  // 自动选择参考音频 (恢复上次选择 或 默认最新的)
  useEffect(() => {
    // 只有在数据加载完成且尚未选择时才执行
    if (refAudios.length > 0 && !selectedRefAudio && isRestored) {
      const savedId = localStorage.getItem(`vigent_${storageKey}_refAudioId`);
      let targetAudio = null;

      if (savedId) {
        targetAudio = refAudios.find((a) => a.id === savedId);
      }

      // 如果没找到保存的，或者没有保存，则默认选第一个(最新的)
      if (!targetAudio) {
        targetAudio = refAudios[0];
      }

      setSelectedRefAudio(targetAudio);
      setRefText(targetAudio.ref_text);
    }
  }, [refAudios, selectedRefAudio, isRestored, storageKey, setSelectedRefAudio, setRefText]);

  useEffect(() => {
    if (!selectedRefAudio || !isRestored) return;
    localStorage.setItem(`vigent_${storageKey}_refAudioId`, selectedRefAudio.id);
  }, [selectedRefAudio, storageKey, isRestored]);

  useEffect(() => {
    if (!selectedRefAudio) return;
    setRefText(selectedRefAudio.ref_text);
  }, [selectedRefAudio]);

  // 开始录音
  const startRecording = async () => {
    try {
      setRecordedBlob(null);
      setRecordingTime(0);

      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
      const chunks: BlobPart[] = [];

      mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
      mediaRecorder.onstop = () => {
        const blob = new Blob(chunks, { type: "audio/webm" });
        setRecordedBlob(blob);
        stream.getTracks().forEach((track) => track.stop());
      };

      mediaRecorder.start();
      setIsRecording(true);
      mediaRecorderRef.current = mediaRecorder;

      // 计时器
      recordingIntervalRef.current = setInterval(() => {
        setRecordingTime((prev) => prev + 1);
      }, 1000);
    } catch (err) {
      toast.error("无法访问麦克风，请检查权限设置");
      console.error(err);
    }
  };

  // 停止录音
  const stopRecording = () => {
    mediaRecorderRef.current?.stop();
    setIsRecording(false);
    if (recordingIntervalRef.current) {
      clearInterval(recordingIntervalRef.current);
      recordingIntervalRef.current = null;
    }
  };

  // 使用录音（上传到后端，使用固定参考文字）
  const useRecording = async () => {
    if (!recordedBlob) return;

    // 回归：使用固定文件名，依靠后端自动重命名 (recording(1).webm)
    const filename = "recording.webm";

    const file = new File([recordedBlob], filename, { type: "audio/webm" });
    await uploadRefAudio(file);
    setRecordedBlob(null);
    setRecordingTime(0);
  };

  const discardRecording = () => {
    setRecordedBlob(null);
    setRecordingTime(0);
  };

  // 格式化录音时长
  const formatRecordingTime = (seconds: number) => {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins}:${secs.toString().padStart(2, "0")}`;
  };

  // AI 生成标题和标签
  const handleGenerateMeta = async () => {
    if (!text.trim()) {
      toast.error("请先输入口播文案");
      return;
    }

    setIsGeneratingMeta(true);
    try {
      const { data: res } = await api.post<ApiResponse<{ title?: string; secondary_title?: string; tags?: string[] }>>(
        "/api/ai/generate-meta",
        { text: text.trim() }
      );
      const payload = unwrap(res);

      // 更新首页标题
      const nextTitle = clampTitle(payload.title || "");
      titleInput.commitValue(nextTitle);

      // 更新副标题
      const nextSecondaryTitle = clampSecondaryTitle(payload.secondary_title || "");
      secondaryTitleInput.commitValue(nextSecondaryTitle);

      // 同步到发布页 localStorage
      localStorage.setItem(`vigent_${storageKey}_publish_tags`, JSON.stringify(payload.tags || []));
    } catch (err: unknown) {
      console.error("AI generate meta failed:", err);
      const axiosErr = err as { response?: { data?: { message?: string } }; message?: string };
      const errorMsg = axiosErr.response?.data?.message || axiosErr.message || String(err);
      toast.error(`AI 生成失败: ${errorMsg}`);
    } finally {
      setIsGeneratingMeta(false);
    }
  };

  // AI 多语言翻译
  const handleTranslate = async (targetLang: string) => {
    if (!text.trim()) {
      toast.error("请先输入口播文案");
      return;
    }

    // 首次翻译时保存原文
    if (originalText === null) {
      setOriginalText(text);
    }

    setIsTranslating(true);
    try {
      const { data: res } = await api.post<ApiResponse<{ translated_text: string }>>(
        "/api/ai/translate",
        { text: text.trim(), target_lang: targetLang }
      );
      const payload = unwrap(res);
      setText(payload.translated_text || "");

      // 根据翻译目标语言更新 textLang 并自动切换声音
      const locale = LANG_TO_LOCALE[targetLang] || "zh-CN";
      setTextLang(locale);
      if (ttsMode === "edgetts") {
        const langVoices = VOICES[locale] || VOICES["zh-CN"];
        setVoice(langVoices[0].id);
      }
    } catch (err: unknown) {
      console.error("AI translate failed:", err);
      const axiosErr = err as { response?: { data?: { message?: string } }; message?: string };
      const errorMsg = axiosErr.response?.data?.message || axiosErr.message || String(err);
      toast.error(`AI 翻译失败: ${errorMsg}`);
    } finally {
      setIsTranslating(false);
    }
  };

  const handleRestoreOriginal = () => {
    if (originalText !== null) {
      setText(originalText);
      setOriginalText(null);
      setTextLang("zh-CN");
      if (ttsMode === "edgetts") {
        setVoice(VOICES["zh-CN"][0].id);
      }
    }
  };

  // 生成配音
  const handleGenerateAudio = async () => {
    if (!text.trim()) {
      toast.error("请先输入文案");
      return;
    }
    if (ttsMode === "voiceclone" && !selectedRefAudio) {
      toast.error("请选择参考音频");
      return;
    }

    const emotionToInstruct: Record<string, string> = {
      normal: "",
      happy: "You are a helpful assistant. 请非常开心地说一句话。<|endofprompt|>",
      sad: "You are a helpful assistant. 请非常伤心地说一句话。<|endofprompt|>",
      angry: "You are a helpful assistant. 请非常生气地说一句话。<|endofprompt|>",
    };

    const params = {
      text: text.trim(),
      tts_mode: ttsMode,
      voice: ttsMode === "edgetts" ? voice : undefined,
      ref_audio_id: ttsMode === "voiceclone" ? selectedRefAudio!.id : undefined,
      ref_text: ttsMode === "voiceclone" ? refText : undefined,
      language: textLang,
      speed: ttsMode === "voiceclone" ? speed : undefined,
      instruct_text: ttsMode === "voiceclone" ? emotionToInstruct[emotion] || "" : undefined,
    };
    await generateAudio(params);
  };

  // 生成视频
  const handleGenerate = async () => {
    if (selectedMaterials.length === 0 || !text.trim()) {
      toast.error("请先选择素材并填写文案");
      return;
    }

    if (!selectedAudio) {
      toast.error("请先生成并选中配音");
      return;
    }

    if (enableBgm && !selectedBgmId) {
      toast.error("请选择背景音乐");
      return;
    }

    setGeneratedVideo(null);

    try {
      // 查找选中的素材对象以获取路径
      const firstMaterialObj = materials.find((m) => m.id === selectedMaterials[0]);
      if (!firstMaterialObj) {
        toast.error("素材数据异常");
        return;
      }

      // 构建请求参数 - 使用预生成配音
      const payload: Record<string, unknown> = {
        material_path: firstMaterialObj.path,
        text: selectedAudio.text || text,
        generated_audio_id: selectedAudio.id,
        language: selectedAudio.language || textLang,
        lipsync_model: lipsyncModelMode,
        title: videoTitle.trim() || undefined,
        enable_subtitles: true,
        output_aspect_ratio: outputAspectRatio,
      };

      // 多素材
      if (selectedMaterials.length > 1) {
        const timelineOrderedIds = timelineSegments
          .map((seg) => seg.materialId)
          .filter((id, index, arr) => arr.indexOf(id) === index);
        const orderedMaterialIds = [
          ...timelineOrderedIds.filter((id) => selectedMaterials.includes(id)),
          ...selectedMaterials.filter((id) => !timelineOrderedIds.includes(id)),
        ];

        const materialPaths = orderedMaterialIds
          .map((id) => materials.find((x) => x.id === id)?.path)
          .filter((path): path is string => !!path);

        if (materialPaths.length === 0) {
          toast.error("多素材解析失败，请刷新素材后重试");
          return;
        }

        payload.material_paths = materialPaths;
        payload.material_path = materialPaths[0];

        // 发送自定义时间轴分配
        const assignments = toCustomAssignments();
        if (assignments.length > 0) {
          const assignmentPaths = assignments
            .map((a) => a.material_path)
            .filter((path): path is string => !!path);

          if (assignmentPaths.length === assignments.length) {
            // 以时间轴可见段为准：超出时间轴的素材不会参与本次生成
            payload.material_paths = assignmentPaths;
            payload.material_path = assignmentPaths[0];
          }
          payload.custom_assignments = assignments;
        } else {
          console.warn(
            "[Timeline] custom_assignments 为空，回退后端自动分配",
            { materials: materialPaths.length }
          );
        }
      }

      // 单素材 + 截取范围
      const singleSeg = timelineSegments[0];
      if (
        selectedMaterials.length === 1
        && singleSeg
        && (singleSeg.sourceStart > 0 || singleSeg.sourceEnd > 0)
      ) {
        payload.custom_assignments = toCustomAssignments();
      }

      if (selectedSubtitleStyleId) {
        payload.subtitle_style_id = selectedSubtitleStyleId;
      }

      if (subtitleFontSize) {
        payload.subtitle_font_size = Math.round(subtitleFontSize);
      }

      if (videoTitle.trim() && selectedTitleStyleId) {
        payload.title_style_id = selectedTitleStyleId;
      }

      if (videoTitle.trim() && titleFontSize) {
        payload.title_font_size = Math.round(titleFontSize);
      }

      if (videoTitle.trim() || videoSecondaryTitle.trim()) {
        payload.title_display_mode = titleDisplayMode;
        if (titleDisplayMode === "short") {
          payload.title_duration = DEFAULT_SHORT_TITLE_DURATION;
        }
      }

      if (videoTitle.trim()) {
        payload.title_top_margin = Math.round(titleTopMargin);
      }

      if (videoSecondaryTitle.trim()) {
        payload.secondary_title = videoSecondaryTitle.trim();
        if (selectedSecondaryTitleStyleId) {
          payload.secondary_title_style_id = selectedSecondaryTitleStyleId;
        }
        if (secondaryTitleFontSize) {
          payload.secondary_title_font_size = Math.round(secondaryTitleFontSize);
        }
        payload.secondary_title_top_margin = Math.round(secondaryTitleTopMargin);
      }

      payload.subtitle_bottom_margin = Math.round(subtitleBottomMargin);

      if (enableBgm && selectedBgmId) {
        payload.bgm_id = selectedBgmId;
        payload.bgm_volume = 0.2;
      }

      // 创建生成任务
      const { data: res } = await api.post<ApiResponse<{ task_id: string }>>(
        "/api/videos/generate",
        payload
      );

      const taskId = unwrap(res).task_id;

      // 保存任务ID到 localStorage，以便页面切换后恢复
      localStorage.setItem(`vigent_${storageKey}_current_task`, taskId);

      // 使用全局 TaskContext 开始任务
      startTask(taskId);
    } catch (error) {
      console.error("生成失败:", error);
    }
  };

  const handleSelectRefAudio = (audio: RefAudio) => {
    setSelectedRefAudio(audio);
    setRefText(audio.ref_text);
  };

  const handlePreviewMaterial = (path: string) => {
    setPreviewMaterial(resolveMediaUrl(path));
  };

  const handleSelectVideo = (video: GeneratedVideo) => {
    setSelectedVideoId(video.id);
    setGeneratedVideo(resolveMediaUrl(video.path));
  };

  const registerMaterialRef = (id: string, el: HTMLDivElement | null) => {
    materialItemRefs.current[id] = el;
  };

  const registerBgmItemRef = (id: string, el: HTMLDivElement | null) => {
    bgmItemRefs.current[id] = el;
  };

  const registerVideoRef = (id: string, el: HTMLDivElement | null) => {
    videoItemRefs.current[id] = el;
  };

  return {
    apiBase,
    registerMaterialRef,
    previewMaterial,
    setPreviewMaterial,
    materials,
    fetchError,
    isFetching,
    lastMaterialCount,
    isUploading,
    uploadProgress,
    uploadError,
    setUploadError,
    fetchMaterials,
    deleteMaterial,
    handleUpload,
    selectedMaterials,
    toggleMaterial,
    handlePreviewMaterial,
    editingMaterialId,
    editMaterialName,
    setEditMaterialName,
    startMaterialEditing,
    saveMaterialEditing,
    cancelMaterialEditing,
    text,
    setText,
    extractModalOpen,
    setExtractModalOpen,
    rewriteModalOpen,
    setRewriteModalOpen,
    handleGenerateMeta,
    isGeneratingMeta,
    handleTranslate,
    isTranslating,
    originalText,
    handleRestoreOriginal,
    savedScripts,
    handleSaveScript,
    deleteSavedScript,
    showStylePreview,
    setShowStylePreview,
    videoTitle,
    titleInput,
    titleStyles,
    selectedTitleStyleId,
    setSelectedTitleStyleId,
    titleFontSize,
    setTitleFontSize,
    setTitleSizeLocked,
    videoSecondaryTitle,
    secondaryTitleInput,
    selectedSecondaryTitleStyleId,
    setSelectedSecondaryTitleStyleId,
    secondaryTitleFontSize,
    setSecondaryTitleFontSize,
    setSecondaryTitleSizeLocked,
    secondaryTitleTopMargin,
    setSecondaryTitleTopMargin,
    subtitleStyles,
    selectedSubtitleStyleId,
    setSelectedSubtitleStyleId,
    subtitleFontSize,
    setSubtitleFontSize,
    setSubtitleSizeLocked,
    titleTopMargin,
    setTitleTopMargin,
    titleDisplayMode,
    setTitleDisplayMode,
    subtitleBottomMargin,
    setSubtitleBottomMargin,
    outputAspectRatio,
    setOutputAspectRatio,
    lipsyncModelMode,
    setLipsyncModelMode,
    resolveAssetUrl,
    getFontFormat,
    buildTextShadow,
    materialDimensions,
    materialPosterUrl,
    ttsMode,
    setTtsMode,
    voices: VOICES[textLang] || VOICES["zh-CN"],
    voice,
    setVoice,
    textLang,
    refAudios,
    selectedRefAudio,
    handleSelectRefAudio,
    isUploadingRef,
    uploadRefError,
    setUploadRefError,
    uploadRefAudio,
    fetchRefAudios,
    playingAudioId,
    togglePlayPreview,
    editingAudioId,
    editName,
    setEditName,
    startEditing,
    saveEditing,
    cancelEditing,
    deleteRefAudio,
    retranscribeRefAudio,
    retranscribingId,
    recordedBlob,
    isRecording,
    recordingTime,
    startRecording,
    stopRecording,
    useRecording,
    discardRecording,
    formatRecordingTime,
    bgmList,
    bgmLoading,
    bgmError,
    enableBgm,
    setEnableBgm,
    fetchBgmList,
    selectedBgmId,
    setSelectedBgmId,
    playingBgmId,
    toggleBgmPreview,
    bgmVolume,
    setBgmVolume,
    bgmListContainerRef,
    registerBgmItemRef,
    currentTask,
    isGenerating,
    handleGenerate,
    generatedVideo,
    generatedVideos,
    selectedVideoId,
    handleSelectVideo,
    deleteVideo,
    fetchGeneratedVideos,
    registerVideoRef,
    formatDate,
    generatedAudios,
    selectedAudio,
    selectedAudioId,
    isGeneratingAudio,
    audioTask,
    fetchGeneratedAudios,
    handleGenerateAudio,
    deleteAudio,
    renameAudio,
    selectAudio,
    speed,
    setSpeed,
    emotion,
    setEmotion,
    timelineSegments,
    reorderSegments,
    setSourceRange,
    clipTrimmerOpen,
    setClipTrimmerOpen,
    clipTrimmerSegmentId,
    setClipTrimmerSegmentId,
  };
};
