import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { open } from '@tauri-apps/plugin-dialog';
import { exists } from '@tauri-apps/plugin-fs';
import { getPreviewVideoUrl } from '../../utils/videoPreview';
import { toast } from '../../store/uiStore';
import { useProjectStore, saveMetaToLocalFile } from '../../store';
import { useLocalVideo } from '../../hooks/useLocalVideo';
import { usePointsCheck } from '../../hooks/usePointsCheck';
import { pointsApi } from '../../api/modules/points';
import { usePointStore } from '../../store';
import RechargeModal from '../../components/RechargeModal/RechargeModal';
import { getCurrentProjectId, localProjectApi } from '../../api/modules/localStorage';
import { useEmptyShotMaterials } from './hooks/useEmptyShotMaterials';
import { useVideoGeneration } from './hooks/useVideoGeneration';
import { validateLocalVideo } from './utils/videoValidation';
import AvatarMaterialSelector from './_components/AvatarMaterialSelector';
import ShotTimeline from './_components/ShotTimeline';
import GenerationControls from './_components/GenerationControls';
import '../VideoCreation/VideoGeneration.css';
import type { ScriptShot } from '../../types/project';

export default function VideoGeneration() {
  const segments = useProjectStore((state) => state.segments);
  const composedVideoPath = useProjectStore((state) => state.composedVideoPath);
  const stepDirtyFlags = useProjectStore((state) => state.stepDirtyFlags);
  const isStepDirty = (stepDirtyFlags?.[3] ?? false) && !!composedVideoPath;
  const { videoUrl: composedVideoBlobUrl, isLoading: isComposedVideoLoading } = useLocalVideo(composedVideoPath);
  const projectId = getCurrentProjectId();

  // 本地人物出镜素材
  const [selectedAvatarMaterial, setSelectedAvatarMaterial] = useState<{
    id: string;
    name: string;
    duration: number;
    path: string;
  } | null>(null);

  const showRechargeModal = usePointStore((state) => state.showRechargeModal);
  const setShowRechargeModal = usePointStore((state) => state.setShowRechargeModal);
  const fetchBalance = usePointStore((state) => state.fetchBalance);
  const shots = useMemo(() => segments || [], [segments]);

  // 从后端配置读取视频生成 multiplier（默认 1）
  const [videoMultiplier, setVideoMultiplier] = useState(1);
  const [videoMultiplierLoaded, setVideoMultiplierLoaded] = useState(false);
  const { PointsModal, checkBalance, handleError } = usePointsCheck();

  useEffect(() => {
    pointsApi
      .getRules()
      .then((rules) => {
        const videoRule = rules.find((r) => r.sourceType === 'video');
        if (videoRule?.pointsPerUnit) {
          setVideoMultiplier(videoRule.pointsPerUnit);
        }
        setVideoMultiplierLoaded(true);
      })
      .catch(() => {
        setVideoMultiplierLoaded(true);
      });
  }, []);

  const dubbingAudioDuration = useProjectStore(state => state.dubbingAudioDuration);

  const [activeScene, setActiveScene] = useState<number>(1);
  const activeSceneRef = useRef(activeScene);
  useEffect(() => {
    activeSceneRef.current = activeScene;
  }, [activeScene]);

  // 匹配请求进行中 shotId 集合，防止重复点击
  const [matchingShotIds, setMatchingShotIds] = useState<Set<string>>(new Set());
  // 匹配请求序号，用于丢弃过期的异步返回结果
  const matchRequestIdRef = useRef(0);
  const videoRef = useRef<HTMLVideoElement>(null);
  const composedVideoRef = useRef<HTMLVideoElement>(null);

  // 控制右侧预览区显示完整视频还是镜头素材
  const [isComposedPreview, setIsComposedPreview] = useState(false);

  // 预览视频
  const [previewVideoUrl, setPreviewVideoUrl] = useState<string | null>(null);
  const [isPreviewLoading, setIsPreviewLoading] = useState(false);

  // 页面刷新后恢复组件本地 state（store 字段由 VideoCreation/index.tsx 统一恢复）
  useEffect(() => {
    async function loadMeta() {
      const pid = getCurrentProjectId();
      if (!pid) {
        return;
      }
      try {
        const meta = await localProjectApi.loadMeta(pid);
        if (!meta) {
          return;
        }

        // 验证人物素材文件是否存在，不存在则清理 store 和 meta.json
        if (meta.avatarMaterialPath) {
          try {
            const fileExists = await exists(meta.avatarMaterialPath);
            if (!fileExists) {
              useProjectStore.setState({
                avatarMaterialPath: undefined,
                avatarMaterialName: undefined,
                avatarMaterialDuration: undefined,
              });
              await saveMetaToLocalFile({
                avatarMaterialPath: undefined,
                avatarMaterialName: undefined,
                avatarMaterialDuration: undefined,
              });
            }
          } catch {
            // no-op: 验证素材文件失败不影响主流程
          }
        }

        // 同步恢复人物素材到组件 state
        if (meta.avatarMaterialPath && meta.avatarMaterialName && meta.avatarMaterialDuration) {
          setSelectedAvatarMaterial({
            id: 'local_restored',
            name: meta.avatarMaterialName,
            duration: meta.avatarMaterialDuration,
            path: meta.avatarMaterialPath,
          });
        } else {
          setSelectedAvatarMaterial(null);
        }
      } catch (e) {
        console.error('[VideoGeneration] 加载 meta.json 失败:', e);
      }
    }
    loadMeta();
  }, []);

  // 空镜素材管理
  const {
    materialMatchMap,
    userUploadedMaterials,
    matchSingleMaterial,
    switchMaterial,
    uploadMaterial,
    setMaterialMatchMap,
  } = useEmptyShotMaterials(shots, projectId);

  // 视频生成积分：统一用完整配音音频时长作为唯一口径（与后端预检、实际扣费保持一致）
  const estimatedVideoPoints = useMemo(() => {
    // 优先使用已保存的配音音频实际时长，确保预估 = 实际扣费
    let basePoints = 0;
    if (dubbingAudioDuration && dubbingAudioDuration > 0) {
      basePoints = Math.ceil(dubbingAudioDuration) * videoMultiplier;
    } else {
      // 降级：按分镜 actualDuration 累加（配音后已更新）
      const totalDur = shots.reduce((sum, shot) => {
        if (shot.actualDuration && shot.actualDuration > 0) {
          return sum + shot.actualDuration;
        }
        const durStr = typeof shot.duration === 'string' ? shot.duration : '5s';
        return sum + parseFloat(durStr.replace(/[^0-9.]/g, '') || '0');
      }, 0);
      basePoints = Math.ceil(totalDur) * videoMultiplier;
    }
    // 系统素材额外收费：每个空镜使用的系统素材额外 2 积分
    const systemMaterialCount = shots.filter((s) => {
      if (s.type !== 'empty_shot') {return false;}
      const matched = materialMatchMap[String(s.id)];
      return matched && (matched.url.startsWith('http://') || matched.url.startsWith('https://'));
    }).length;
    return basePoints + systemMaterialCount * 2;
  }, [shots, videoMultiplier, dubbingAudioDuration, materialMatchMap]);

  // 同步 activeScene 与 shots 数据
  useEffect(() => {
    if (shots.length > 0 && !shots.find((s) => Number(s.id) === activeScene)) {
      setActiveScene(Number(shots[0].id));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [shots, userUploadedMaterials]);

  // 页面刷新后恢复空镜素材匹配结果（从 segments.json 恢复）
  useEffect(() => {
    const currentSegments = useProjectStore.getState().segments;
    const restoredMap: Record<string, { url: string; duration: number }> = {};
    for (const shot of currentSegments) {
      if (shot.emptyShotMaterial) {
        restoredMap[String(shot.id)] = shot.emptyShotMaterial;
      }
    }
    if (Object.keys(restoredMap).length > 0) {
      setMaterialMatchMap((prev) => ({ ...prev, ...restoredMap }));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // loading 兜底：视频 URL 设置后最多显示 5 秒 loading，防止 loadeddata 不触发时卡住
  useEffect(() => {
    if (previewVideoUrl) {
      const timer = setTimeout(() => {
        setIsPreviewLoading(false);
      }, 5000);
      return () => clearTimeout(timer);
    }
  }, [previewVideoUrl]);

  /**
   * 预览视频（素材或人物形象）
   */
  const handlePreview = useCallback(async (urlOrPath: string) => {
    setPreviewVideoUrl(null);
    setIsPreviewLoading(true);

    // 统一走转码/缓存流程：本地视频直接转码，网络视频先下载到本地再转码
    try {
      const url = await getPreviewVideoUrl(urlOrPath);
      setPreviewVideoUrl(url);
      setIsPreviewLoading(false);
    } catch (e) {
      console.error('[VideoGeneration] 预览视频失败:', e);
      toast.error('预览失败');
      setIsPreviewLoading(false);
    }
  }, []);

  // 点击卡片后自动预览对应素材/形象
  useEffect(() => {
    const activeShot = shots.find((s) => Number(s.id) === activeScene);
    if (!activeShot) {
      return;
    }

    setPreviewVideoUrl(null);

    if (activeShot.type === 'empty_shot') {
      const matched = materialMatchMap[String(activeShot.id)];
      if (matched) {
        handlePreview(matched.url);
      } else {
        setPreviewVideoUrl(null);
        setIsPreviewLoading(false);
      }
    } else if (selectedAvatarMaterial) {
      handlePreview(selectedAvatarMaterial.path);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeScene, handlePreview]);

  // 视频生成
  const { isComposing, handleGenerate } = useVideoGeneration({
    selectedAvatarMaterial,
    materialMatchMap,
    estimatedVideoPoints,
    videoMultiplier,
    checkBalance,
    handleError,
    onSuccess: () => {
      setIsComposedPreview(true);
    },
  });

  /**
   * 选择本地人物视频
   */
  const handleSelectLocalVideo = async () => {
    try {
      const selected = await open({
        multiple: false,
        filters: [
          {
            name: 'Video',
            extensions: ['mp4', 'mov'],
          },
        ],
      });

      if (!selected || Array.isArray(selected)) {
        return;
      }

      const filePath = selected;
      const fileName = filePath.split(/[/\\]/).pop() || '未知文件';

      const result = await validateLocalVideo(filePath);

      if (!result.valid) {
        toast.error(result.error || '视频验证失败');
        return;
      }

      const material = {
        id: `local_${Date.now()}`,
        name: fileName,
        duration: parseFloat(result.duration.toFixed(1)),
        path: filePath,
      };
      setSelectedAvatarMaterial(material);

      // 保存到 store + meta.json
      useProjectStore.getState().setAvatarMaterial(filePath, fileName, result.duration);
    } catch (e) {
      console.error('[VideoGeneration] 选择文件失败:', e);
      toast.error('选择文件失败');
    }
  };

  // 处理场景点击
  const handleSceneClick = (id: number, _shot: ScriptShot) => {
    setIsComposedPreview(false);
    setActiveScene(id);
  };

  // 处理素材切换
  const handleSwitchMaterial = async (shotId: string) => {
    const result = await switchMaterial(shotId);
    if (result && Number(shotId) === activeScene) {
      handlePreview(result.url);
    }
  };

  // 处理素材上传
  const handleUploadMaterial = async (shotId: string) => {
    await uploadMaterial(shotId);
  };

  // 处理素材匹配（单个镜头触发匹配）
  // 匹配成功后自动切换到对应镜头并预览，但只处理最新请求的结果，
  // 避免用户快速连点多个镜头时，后返回的请求把画面切回之前的镜头。
  const handleMatchMaterial = async (shotId: string) => {
    if (matchingShotIds.has(shotId)) {return;}
    setMatchingShotIds(prev => new Set(prev).add(shotId));

    const requestId = ++matchRequestIdRef.current;

    try {
      const result = await matchSingleMaterial(shotId);
      if (!result) {
        return;
      }
      // 如果已经有更新的请求发出，丢弃当前过期结果
      if (requestId !== matchRequestIdRef.current) {
        return;
      }
      if (Number(shotId) === activeSceneRef.current) {
        // 当前镜头已激活，直接预览（setActiveScene 值不变时不会触发 useEffect）
        handlePreview(result.url);
      } else {
        // 切换到对应镜头，useEffect 会自动加载预览
        setActiveScene(Number(shotId));
        setIsComposedPreview(false);
      }
    } finally {
      setMatchingShotIds(prev => {
        const next = new Set(prev);
        next.delete(shotId);
        return next;
      });
    }
  };

  const activeShot = shots.find((s) => Number(s.id) === activeScene);



  return (
    <>
      {isStepDirty && (
        <div className="step-dirty-banner">
          <span>上游数据已更新，当前视频结果可能不匹配，建议重新生成</span>
        </div>
      )}
      <div className="step-layout video-gen-layout">
      {/* Left Panel: Digital Human + Storyboard */}
      <div
        className="step-panel-left"
        style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
      >
        <AvatarMaterialSelector
          selectedAvatarMaterial={selectedAvatarMaterial}
          onSelectLocalVideo={handleSelectLocalVideo}
        />

        <ShotTimeline
          shots={shots}
          activeScene={activeScene}
          materialMatchMap={materialMatchMap}
          userUploadedMaterials={userUploadedMaterials}
          selectedAvatarMaterial={selectedAvatarMaterial}
          isComposedPreview={isComposedPreview}
          matchingShotIds={matchingShotIds}
          onSceneClick={handleSceneClick}
          onSwitchMaterial={handleSwitchMaterial}
          onUploadMaterial={handleUploadMaterial}
          onMatchMaterial={handleMatchMaterial}
        />

        <GenerationControls
          composedVideoPath={composedVideoPath ?? null}
          isComposing={isComposing}
          videoMultiplierLoaded={videoMultiplierLoaded}
          estimatedVideoPoints={estimatedVideoPoints}
          isPreviewing={isComposedPreview}
          onGenerate={handleGenerate}
          onPreview={() => setIsComposedPreview(true)}
        />
      </div>

      <PointsModal />
      <RechargeModal
        open={showRechargeModal}
        onClose={() => setShowRechargeModal(false)}
        onRechargeSuccess={() => {
          fetchBalance();
        }}
      />

      {/* Right Panel: Video Preview */}
      <div className="step-panel-right video-gen-right">
        <div className="video-preview-area">
          <div className="video-preview-header">视频预览</div>
          <div className="video-preview-wrapper">
            <div className="video-preview-container">
              {isComposedPreview && composedVideoBlobUrl ? (
                <video
                  key={composedVideoBlobUrl}
                  ref={composedVideoRef}
                  src={composedVideoBlobUrl}
                  className="preview-video"
                  controls
                  autoPlay
                  onCanPlay={() => {
                    composedVideoRef.current?.play().catch(() => {});
                  }}
                />
              ) : isComposedPreview && isComposedVideoLoading ? (
                <div className="video-placeholder">
                  <div
                    style={{
                      width: '40px',
                      height: '40px',
                      border: '3px solid rgba(255,255,255,0.2)',
                      borderTop: '3px solid var(--primary)',
                      borderRadius: '50%',
                      animation: 'avatar-spin 1s linear infinite',
                    }}
                  />
                  <p style={{ color: '#fff', fontSize: '14px', marginTop: '12px' }}>视频加载中...</p>
                </div>
              ) : previewVideoUrl ? (
                <>
                  <video
                    key={previewVideoUrl}
                    ref={videoRef}
                    src={previewVideoUrl}
                    className="preview-video"
                    controls
                    autoPlay
                    muted
                    playsInline
                    onCanPlay={() => {
                      videoRef.current?.play().catch(() => {});
                    }}
                    onLoadedData={() => setIsPreviewLoading(false)}
                    onError={(e) => {
                      console.error('[VideoGeneration] 视频加载失败:', e);
                      toast.error('视频加载失败');
                      setIsPreviewLoading(false);
                    }}
                  />
                </>
              ) : isPreviewLoading ? (
                <div className="video-placeholder">
                  <div
                    style={{
                      width: '40px',
                      height: '40px',
                      border: '3px solid rgba(255,255,255,0.2)',
                      borderTop: '3px solid var(--primary)',
                      borderRadius: '50%',
                      animation: 'avatar-spin 1s linear infinite',
                    }}
                  />
                  <p style={{ color: '#fff', fontSize: '14px', marginTop: '12px' }}>加载中...</p>
                </div>
              ) : (
                <div className="video-placeholder">
                  <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1">
                    <path d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
                  </svg>
                  <p>暂无成品预览</p>
                  <p className="placeholder-sub">
                    {activeShot?.type === 'empty_shot' && !materialMatchMap[String(activeShot.id)]
                      ? '未匹配到素材，请换一个'
                      : activeShot?.type !== 'empty_shot' && !selectedAvatarMaterial
                        ? '请先选择人物形象'
                        : '点击左侧卡片预览素材'}
                  </p>
                </div>
              )}
            </div>
            {/* 左右导航箭头 - 放在 container 外面避免被 overflow:hidden 裁切 */}
            {shots.length > 1 && (
              <>
                <button
                  className="nav-arrow nav-arrow-left"
                  onClick={() => {
                    const idx = shots.findIndex((s) => Number(s.id) === activeScene);
                    if (idx > 0) {
                      const prevId = Number(shots[idx - 1].id);
                      setActiveScene(prevId);
                      const s = shots[idx - 1];
                      if (s.type === 'empty_shot') {
                        const matched = materialMatchMap[String(prevId)];
                        if (matched) {
                          handlePreview(matched.url);
                        }
                      } else if (selectedAvatarMaterial) {
                        handlePreview(selectedAvatarMaterial.path);
                      }
                    }
                  }}
                  disabled={shots.findIndex((s) => Number(s.id) === activeScene) <= 0}
                >
                  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="15 18 9 12 15 6" />
                  </svg>
                </button>
                <button
                  className="nav-arrow nav-arrow-right"
                  onClick={() => {
                    const idx = shots.findIndex((s) => Number(s.id) === activeScene);
                    if (idx < shots.length - 1) {
                      const nextId = Number(shots[idx + 1].id);
                      setActiveScene(nextId);
                      const s = shots[idx + 1];
                      if (s.type === 'empty_shot') {
                        const matched = materialMatchMap[String(nextId)];
                        if (matched) {
                          handlePreview(matched.url);
                        }
                      } else if (selectedAvatarMaterial) {
                        handlePreview(selectedAvatarMaterial.path);
                      }
                    }
                  }}
                  disabled={shots.findIndex((s) => Number(s.id) === activeScene) >= shots.length - 1}
                >
                  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="9 18 15 12 9 6" />
                  </svg>
                </button>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
    </>
  );
}
