import { useState, useEffect, useRef } from 'react';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
import { open, save } from '@tauri-apps/plugin-dialog';
import { exists } from '@tauri-apps/plugin-fs';
import { compositeApi } from '../../api/modules/videoComposite';
import { downloadFile } from '../../api/modules/videoCompose';
import { bgmMusicApi, type BgmMusicItem } from '../../api/modules/bgmMusic';
import { pointsApi } from '../../api/modules/points';
import { useProjectStore, saveMetaToLocalFile } from '../../store';
import { getCurrentProjectId } from '../../api/modules/localStorage';
import { usePointStore, useAuthStore } from '../../store';
import { useProgressStore } from '../../store/progressStore';

import { useLocalVideo } from '../../hooks/useLocalVideo';
import { toast } from '../../store/uiStore';
import ConfirmModal from '../../components/Modal/ConfirmModal';
import Modal from '../../components/Modal/Modal';
import RechargeModal from '../../components/RechargeModal/RechargeModal';

import './VideoCreation.css';
import './VideoGeneration.css';

const CATEGORY_LABELS: Record<string, string> = {
  knowledge: '知识科普',
  showcase: '案例展示',
  promotion: '促销活动',
  lifestyle: '家居生活',
  tech: '智能家居',
};

export default function VideoCompose() {
  const segments = useProjectStore(state => state.segments);
  const coverPath = useProjectStore(state => state.coverPath);
  const burnedVideoPath = useProjectStore(state => state.burnedVideoPath);
  const coverConfig = useProjectStore(state => state.coverConfig);
  const coverMainTitle = coverConfig?.mainTitle;
  const coverSubTitle = coverConfig?.subtitle;
  const finalVideoPath = useProjectStore(state => state.finalVideoPath);
  const setFinalVideoPath = useProjectStore(state => state.setFinalVideoPath);
  const setExportedAt = useProjectStore(state => state.setExportedAt);
  const stepDirtyFlags = useProjectStore(state => state.stepDirtyFlags);
  const isStepDirty = (stepDirtyFlags?.[6] ?? false) && !!finalVideoPath;


  const [compositing, setCompositing] = useState(false);
  const [done, setDone] = useState(!!finalVideoPath);
  const progress = useProgressStore(state => state.progress);
  const [resultPath, setResultPath] = useState(finalVideoPath || '');

  // BGM
  const [bgmList, setBgmList] = useState<BgmMusicItem[]>([]);
  const [bgmCategory, setBgmCategory] = useState<string>('all');
  const [previewId, setPreviewId] = useState<number | null>(null);
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const bgmMusicId = useProjectStore(state => state.bgmMusicId);
  const bgmMusicPath = useProjectStore(state => state.bgmMusicPath);
  const bgmMusicTitle = useProjectStore(state => state.bgmMusicTitle);
  const bgmVolume = useProjectStore(state => state.bgmVolume);
  const setBgmMusic = useProjectStore(state => state.setBgmMusic);
  const setBgmVolume = useProjectStore(state => state.setBgmVolume);
  const [, setBgmLoading] = useState(false);
  const [bgmModalOpen, setBgmModalOpen] = useState(false);


  // 打开弹窗时根据当前选中的 BGM 分类自动切换标签
  useEffect(() => {
    if (bgmModalOpen && bgmMusicId !== undefined && bgmMusicId !== null && bgmMusicId !== -1) {
      const current = bgmList.find(b => b.id === bgmMusicId);
      if (current?.category) {
        setBgmCategory(current.category);
      } else {
        setBgmCategory('all');
      }
    }
  }, [bgmModalOpen, bgmList, bgmMusicId]);

  // 弹窗关闭时停止试听
  useEffect(() => {
    if (!bgmModalOpen) {
      audioRef.current?.pause();
      if (audioRef.current) {
        audioRef.current.onended = null;
      }
      audioRef.current = null;
      setPreviewId(null);
    }
  }, [bgmModalOpen]);

  // 组件卸载时清理音频
  useEffect(() => {
    return () => {
      if (audioRef.current) {
        audioRef.current.pause();
        audioRef.current.onended = null;
        audioRef.current = null;
      }
    };
  }, []);

  const handlePreview = (item: BgmMusicItem) => {
    if (previewId === item.id) {
      audioRef.current?.pause();
      if (audioRef.current) {
        audioRef.current.onended = null;
      }
      audioRef.current = null;
      setPreviewId(null);
      return;
    }
    audioRef.current?.pause();
    if (audioRef.current) {
      audioRef.current.onended = null;
    }
    const audioSrc = item.url;
    if (!audioSrc) {
      toast.warning('该音乐暂无可用的试听链接');
      return;
    }
    const audio = new Audio(audioSrc);
    audio.play().catch(() => {});
    audio.onended = () => setPreviewId(null);
    audioRef.current = audio;
    setPreviewId(item.id);
  };

  const filteredBgmList = bgmCategory === 'all'
    ? bgmList
    : bgmList.filter(item => item.category === bgmCategory);

  // 积分不足弹窗
  const [showPointsModal, setShowPointsModal] = useState(false);
  const showRechargeModal = usePointStore((state) => state.showRechargeModal);
  const setShowRechargeModal = usePointStore((state) => state.setShowRechargeModal);
  const fetchBalance = usePointStore((state) => state.fetchBalance);

  // 同步 Store 中的 finalVideoPath 变化（如切换项目时）
  useEffect(() => {
    setDone(!!finalVideoPath);
    setResultPath(finalVideoPath || '');
  }, [finalVideoPath]);

  // 加载 BGM 列表
  useEffect(() => {
    let canceled = false;
    setBgmLoading(true);
    bgmMusicApi.list().then(list => {
      if (!canceled) {
        setBgmList(list);
      }
    }).catch(err => {
      console.error('加载 BGM 列表失败:', err);
    }).finally(() => {
      if (!canceled) {
        setBgmLoading(false);
      }
    });
    return () => { canceled = true; };
  }, []);

  // 本地上传 BGM
  const handleLocalBgmUpload = async () => {
    const selected = await open({
      multiple: false,
      filters: [{ name: '音频文件', extensions: ['mp3', 'wav', 'm4a', 'aac', 'ogg', 'flac'] }],
    });
    if (selected && typeof selected === 'string') {
      const fileName = selected.split(/[\\/]/).pop() || '本地音频';
      setBgmMusic(-1, fileName, selected);
      setBgmModalOpen(false);
    }
  };

  const shots = segments || [];
  const hasResult = done && !!resultPath;
  const { videoUrl: previewUrl } = useLocalVideo(hasResult ? resultPath : undefined);

  // 计算统计信息
  const totalDurationSeconds =
    segments?.reduce((acc, s) => acc + parseFloat(String(s.duration).replace(/[^0-9.]/g, '') || '0'), 0) || 0;


  // 准备实际用于合成的视频路径列表
  // 单视频全局压制模式：优先使用全局 burnedVideoPath
  const videoPaths = burnedVideoPath
    ? [burnedVideoPath]
    : segments
        ?.map(s => s.burnedVideoPath || s.videoPath)
        .filter((p): p is string => !!p) || [];

  useEffect(() => {
    let canceled = false;
    let unlisten: (() => void) | undefined;
    if (compositing) {
      listen('ffmpeg-progress', event => {
        const timeStr = event.payload as string; // e.g. 00:00:05.12
        const parts = timeStr.split(':').map(parseFloat);
        if (parts.length === 3 && totalDurationSeconds > 0) {
          const elapsed = parts[0] * 3600 + parts[1] * 60 + parts[2];
          const p = Math.min(99, (elapsed / totalDurationSeconds) * 100);
          useProgressStore.getState().setProgress(Math.round(p));
          useProgressStore.getState().update(`正在合成视频... ${Math.round(p)}%`);
        }
      })
        .then(u => {
          if (canceled) {
            u();
          } else {
            unlisten = u;
          }
        })
        .catch(err => console.error('Failed to listen to ffmpeg progress:', err));
    }
    return () => {
      canceled = true;
      if (unlisten) {
        unlisten();
      }
    };
  }, [compositing, totalDurationSeconds]);

  const handleStart = async () => {
    if (videoPaths.length === 0) {
      return;
    }

    // 前置积分检查：压制成片
    const composePoints = usePointStore.getState().getRule('compose')?.points || 5;
    await fetchBalance();
    const currentBalance = usePointStore.getState().balance;
    if (currentBalance < composePoints) {
      setShowPointsModal(true);
      return;
    }
    setShowPointsModal(false);

    setCompositing(true);
    setDone(false);
    setResultPath('');
    useProgressStore.getState().show('压制成片');
    useProgressStore.getState().setProgress(0);

    try {
      // 1. 获取成品保存路径（直接到 products 目录，避免二次复制）
      useProgressStore.getState().update('正在准备合成...');
      const safeTitle = (coverMainTitle || '')
        .trim()
        .replace(/[/:*?"<>|]/g, '')
        .slice(0, 50);
      const timestamp = Date.now();
      const filename = safeTitle ? `${safeTitle}_${timestamp}.mp4` : `final_${timestamp}.mp4`;
      const outputRes = await invoke<{ code: number; data?: string; message: string }>('get_product_save_path', {
        projectId: getCurrentProjectId(),
        filename,
      });
      if (outputRes.code !== 200 || !outputRes.data) {
        throw new Error(outputRes.message);
      }
      const outputPath = outputRes.data;

      // 2. 准备封面路径
      const resolvedCoverPath = coverPath;

      // 3. 调用 Rust 合成，直接输出到 products 目录
      const result = await compositeApi.synthesis({
        video_paths: videoPaths,
        cover_path: resolvedCoverPath,
        output_path: outputPath,
      });

      // 4. 如果选择了 BGM，混合背景音乐
      if (bgmMusicPath) {
        useProgressStore.getState().update('正在混合背景音乐...');
        let finalBgmPath = bgmMusicPath;

        // BGM 为 URL 时，先下载到本地缓存
        if (bgmMusicPath.startsWith('http')) {
          const cacheDir = await invoke<string>('get_bgm_cache_dir');
          const ext = bgmMusicPath.split('.').pop() || 'mp3';
          const cachedPath = `${cacheDir}/bgm_${bgmMusicId}.${ext}`;
          const fileExists = await exists(cachedPath);
          if (!fileExists) {
            useProgressStore.getState().update('正在下载背景音乐...');
            await downloadFile(bgmMusicPath, cachedPath);
          }
          finalBgmPath = cachedPath;
        }

        const mixRes = await invoke<{ code: number; data?: string; message: string }>('mix_bgm_to_video', {
          args: {
            videoPath: result,
            bgmPath: finalBgmPath,
            outputPath: result,
            videoVolume: 1.0,
            bgmVolume: (bgmVolume ?? 0.25),
          },
        });
        if (mixRes.code !== 200) {
          toast.warning('BGM 混合失败，使用无 BGM 版本: ' + mixRes.message);
        }
      }

      setResultPath(result);
      setFinalVideoPath(result);
      setExportedAt(Date.now());
      useProgressStore.getState().setProgress(100);

      // 4. 扣费：压制成片
      const composePoints = usePointStore.getState().getRule('compose')?.points || 5;
      try {
        await pointsApi.consumePoints({
          points: composePoints,
          sourceType: 'compose',
          sourceId: `compose_${useAuthStore.getState().user?.id || 'unknown'}_${Date.now()}`,
          description: '压制成片',
        });
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        // 回滚状态：未成功扣费视为合成未完成
        setResultPath('');
        setFinalVideoPath(undefined);
        setExportedAt(undefined);
        useProgressStore.getState().hide();
        setCompositing(false);
        if (msg.includes('402') || msg.includes('积分不足')) {
          setShowPointsModal(true);
          return;
        }
        toast.error('积分扣费失败，请重试');
        return;
      }

      // 5. 持久化最终路径和时长到 meta.json（无需二次复制）
      await saveMetaToLocalFile({
        finalVideoPath: result,
        finalVideoDuration: totalDurationSeconds,
        exportedAt: Date.now(),
        coverPath,
      });

      useProjectStore.getState().clearStepDirty(6);
      setDone(true);
      useProgressStore.getState().success('压制成片完成', composePoints);
    } catch (error: unknown) {
      console.error('压制成片失败:', error);
      useProgressStore.getState().error('压制成片失败，请重试');
    } finally {
      setCompositing(false);
    }
  };

  return (
    <>
      {isStepDirty && (
        <div className="step-dirty-banner">
          <span>上游数据已更新，当前成片可能与最新内容不匹配，建议重新合成</span>
        </div>
      )}
      <div className="step-layout video-gen-layout">
      {/* Left Panel: Shot List */}
      <div
        className="step-panel-left"
        style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
      >
        {/* 镜头列表 - 复用视频生成页面的样式 */}
        <div
          className="panel-section"
          style={{
            flex: 1,
            minHeight: 0,
            display: 'flex',
            flexDirection: 'column',
            overflow: 'hidden',
          }}
        >
          <label className="panel-label">合成信息</label>
          {/* BGM 选择 */}
          <div style={{ marginBottom: 'var(--spacing-md)' }}>
            <label style={{ fontSize: 'var(--font-sm)', color: 'var(--text-secondary)', marginBottom: 'var(--spacing-xs)', display: 'block' }}>
              背景音乐
            </label>
            {bgmMusicId !== undefined && bgmMusicId !== null ? (
              <div
                style={{
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'space-between',
                  padding: 'var(--spacing-sm) var(--spacing-md)',
                  border: '1px solid var(--border-color)',
                  borderRadius: '8px',
                  background: 'var(--bg-secondary)',
                  cursor: 'pointer',
                }}
                onClick={() => setBgmModalOpen(true)}
              >
                <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)' }}>
                  <div
                    style={{
                      width: '36px',
                      height: '36px',
                      borderRadius: '50%',
                      background: 'var(--primary-light)',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      color: 'var(--primary)',
                    }}
                  >
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
                      <path d="M9 18V5l12-2v13" />
                      <circle cx="6" cy="18" r="3" />
                      <circle cx="18" cy="16" r="3" />
                    </svg>
                  </div>
                  <div>
                    <div style={{ fontSize: 'var(--font-sm)', fontWeight: 500 }}>{bgmMusicTitle || '背景音乐'}</div>
                    <div style={{ fontSize: 'var(--font-xs)', color: 'var(--text-secondary)' }}>
                      音量 {Math.round((bgmVolume ?? 0.25) * 100)}%
                    </div>
                  </div>
                </div>
                <div style={{ display: 'flex', gap: 8 }}>
                  <button className="btn btn-ghost btn-sm" onClick={e => { e.stopPropagation(); setBgmModalOpen(true); }}>
                    更换
                  </button>
                  <button
                    className="btn btn-ghost btn-sm"
                    style={{ color: 'var(--text-secondary)' }}
                    onClick={e => {
                      e.stopPropagation();
                      setBgmMusic(undefined, undefined, undefined);
                    }}
                  >
                    取消
                  </button>
                </div>
              </div>
            ) : (
              <div
                style={{
                  padding: 'var(--spacing-md)',
                  border: '1px dashed var(--border-color)',
                  borderRadius: '8px',
                  textAlign: 'center',
                  color: 'var(--text-secondary)',
                  fontSize: 'var(--font-sm)',
                  cursor: 'pointer',
                }}
                onClick={() => setBgmModalOpen(true)}
              >
                <p>未选择背景音乐</p>
                <p style={{ marginTop: '4px', fontSize: 'var(--font-xs)', opacity: 0.7 }}>
                  点击选择系统音乐或上传本地音频
                </p>
              </div>
            )}
            {bgmMusicId && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)', marginTop: 'var(--spacing-xs)' }}>
                <span style={{ fontSize: 'var(--font-xs)', color: 'var(--text-secondary)', whiteSpace: 'nowrap' }}>
                  BGM 音量
                </span>
                <input
                  type="range"
                  className="slider-input"
                  min="0"
                  max="1"
                  step="0.05"
                  value={bgmVolume ?? 0.25}
                  onChange={e => setBgmVolume(parseFloat(e.target.value))}
                  disabled={compositing}
                  style={{ flex: 1, '--slider-percent': `${((bgmVolume ?? 0.25) / 1) * 100}%` } as React.CSSProperties}
                />
                <span style={{ fontSize: 'var(--font-xs)', color: 'var(--text-secondary)', minWidth: '36px', textAlign: 'right' }}>
                  {Math.round(((bgmVolume ?? 0.25)) * 100)}%
                </span>
              </div>
            )}
          </div>
          <div className="script-editor" style={{ flex: 1, overflowY: 'auto', paddingRight: '4px' }}>
            {shots.length === 0 ? (
              <div
                style={{
                  display: 'flex',
                  flexDirection: 'column',
                  alignItems: 'center',
                  justifyContent: 'center',
                  height: '100%',
                  color: 'var(--text-secondary)',
                  gap: 'var(--spacing-md)',
                  opacity: 0.6,
                }}
              >
                <svg
                  width="48"
                  height="48"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="1.5"
                >
                  <path d="M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z" />
                </svg>
                <span>暂无分镜数据，请先生成脚本</span>
              </div>
            ) : (
              <>
                {/* 封面信息卡片 */}
                {coverPath && (
                  <div
                    className="scene-card-compact"
                  >
                    <div className="scene-card-header">
                      <span className="scene-id-text">封面</span>
                    </div>
                    <div className="scene-card-body">
                      <p className="scene-card-desc">
                        <span className="scene-field-label-pill">主标题</span>
                        {coverMainTitle || '未设置'}
                      </p>
                      <p className="scene-card-voiceover">
                        <span className="scene-field-label-pill">副标题</span>
                        {coverSubTitle || '未设置'}
                      </p>
                    </div>
                  </div>
                )}
                {/* 镜头列表 */}
                {shots.map(shot => {
                  const path = shot.burnedVideoPath || shot.videoPath;
                  const hasVideo = !!path;
                  return (
                    <div
                      key={shot.id}
                      className="scene-card-compact"
                    >
                      <div className="scene-card-header">
                        <span className="scene-id-text">镜头 {shot.id}</span>
                        <span
                          className={`scene-type-badge ${shot.type === 'empty_shot' ? 'empty' : 'regular'}`}
                        >
                          {shot.type === 'empty_shot' ? '空镜' : '分镜'}
                        </span>
                        <span className="scene-duration-badge">{shot.duration}</span>
                        <div
                          className={`scene-status-dot ${hasVideo ? 'completed' : ''}`}
                          title={hasVideo ? '视频已就绪' : '等待生成'}
                        />
                      </div>
                      <div className="scene-card-body">
                        <p className="scene-card-desc">
                          <span className="scene-field-label-pill">画面</span>
                          {shot.scene || '未设置画面描述'}
                        </p>
                        <p className="scene-card-voiceover">
                          <span className="scene-field-label-pill">配音文本</span>
                          {shot.voiceover || '未设置配音文本'}
                        </p>
                      </div>
                    </div>
                  );
                })}
              </>
            )}
          </div>
        </div>

        {/* 底部按钮 */}
        <div style={{ paddingBottom: 'var(--spacing-md)' }}>
          {/* 前置条件提示 */}
          {(!coverPath || !burnedVideoPath) && (
            <div
              style={{
                marginBottom: 'var(--spacing-md)',
                padding: 'var(--spacing-sm) var(--spacing-md)',
                backgroundColor: 'rgba(255, 193, 7, 0.1)',
                border: '1px solid rgba(255, 193, 7, 0.3)',
                borderRadius: 'var(--radius-md)',
                color: '#ffc107',
                fontSize: 'var(--font-sm)',
              }}
            >
              {!burnedVideoPath && (
                <p>⚠️ 缺少字幕烧录后的视频，请先回到第4步完成「字幕烧录」</p>
              )}
              {!coverPath && (
                <p>⚠️ 缺少封面图，请先回到第5步完成「封面设计」</p>
              )}
            </div>
          )}
          {!done ? (
            <button
              className="btn btn-primary"
              style={{ width: '100%', marginTop: 0, flexShrink: 0 }}
              onClick={handleStart}
              disabled={compositing || !coverPath || !burnedVideoPath}
            >
              合成视频（{usePointStore.getState().getRule('compose')?.points || 5} 积分）
            </button>
          ) : (
            <div style={{ display: 'flex', gap: 'var(--spacing-md)', width: '100%' }}>
              <button
                className="btn btn-primary"
                style={{ flex: 1 }}
                onClick={handleStart}
                disabled={compositing || !coverPath || !burnedVideoPath}
              >
                重新合成视频（{usePointStore.getState().getRule('compose')?.points || 5} 积分）
              </button>
              <button
                className="btn btn-secondary"
                style={{ flex: 1 }}
                onClick={async () => {
                  if (!resultPath) {return;}
                  try {
                    const filename = resultPath.split(/[\\/]/).pop() || 'video.mp4';
                    const targetPath = await save({
                      defaultPath: filename,
                      filters: [{ name: '视频', extensions: ['mp4'] }],
                    });
                    if (!targetPath) {return;}
                    const res = await invoke<{ code: number; data?: string; message: string }>('export_product', {
                      sourcePath: resultPath,
                      targetPath,
                    });
                    if (res.code === 200) {
                      toast.success('导出成功');
                    } else {
                      toast.error(res.message || '导出失败');
                    }
                  } catch {
                    toast.error('导出失败');
                  }
                }}
              >
                导出视频
              </button>
            </div>
          )}
        </div>
      </div>

      {/* Right Panel: Final 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">
            {done && resultPath ? (
              <video
                key={resultPath}
                src={previewUrl}
                className="preview-video"
                controls
                autoPlay
              />
            ) : compositing ? (
              <div className="video-placeholder">
                <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                  <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">{progress !== undefined ? `${Math.round(progress)}% 完成` : '准备中...'}</p>
              </div>
            ) : (
              <div className="video-placeholder">
                <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                  <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">合成完成后在此显示最终视频</p>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>

      {/* 积分不足弹窗 */}
      <ConfirmModal
        open={showPointsModal}
        type="warning"
        title="积分不足"
        description="请先充值积分后再进行尝试"
        confirmText="立即充值"
        cancelText="稍后再说"
        confirmButtonType="danger"
        onConfirm={() => { setShowPointsModal(false); setShowRechargeModal(true); }}
        onCancel={() => setShowPointsModal(false)}
        onClose={() => setShowPointsModal(false)}
      />
      <RechargeModal
        open={showRechargeModal}
        onClose={() => setShowRechargeModal(false)}
        onRechargeSuccess={() => { fetchBalance(); setShowPointsModal(false); }}
      />

      {/* BGM 选择弹窗 */}
      <Modal
        open={bgmModalOpen}
        onClose={() => setBgmModalOpen(false)}
        title="选择背景音乐"
        width="480px"
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {/* 分类标签 */}
          <div className="modal-bgm-tabs">
            <button
              className={`modal-bgm-tab ${bgmCategory === 'all' ? 'active' : ''}`}
              onClick={() => setBgmCategory('all')}
            >
              全部
            </button>
            {Object.entries(CATEGORY_LABELS).map(([key, label]) => (
              <button
                key={key}
                className={`modal-bgm-tab ${bgmCategory === key ? 'active' : ''}`}
                onClick={() => setBgmCategory(key)}
              >
                {label}
              </button>
            ))}
          </div>

          {/* 当前选中：本地上传 */}
          {bgmMusicId === -1 && bgmMusicTitle && (
            <div className="modal-bgm-local-hint">
              <span>当前：{bgmMusicTitle}（本地上传）</span>
              <button
                className="modal-bgm-local-clear"
                onClick={() => {
                  setBgmMusic(undefined, undefined, undefined);
                  audioRef.current?.pause();
                  setPreviewId(null);
                }}
              >
                清除
              </button>
            </div>
          )}

          {/* BGM 列表 */}
          <div className="modal-bgm-list">
            {filteredBgmList.map((item) => (
              <div
                key={item.id}
                className={`modal-bgm-row ${bgmMusicId === item.id ? 'active' : ''}`}
              >
                <button
                  className={`modal-bgm-preview-btn ${previewId === item.id ? 'playing' : ''}`}
                  onClick={() => handlePreview(item)}
                  title={previewId === item.id ? '暂停试听' : '试听'}
                >
                  {previewId === item.id ? (
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                      <rect x="6" y="4" width="4" height="16" rx="1" />
                      <rect x="14" y="4" width="4" height="16" rx="1" />
                    </svg>
                  ) : (
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M8 5v14l11-7z" />
                    </svg>
                  )}
                </button>
                <div
                  className="modal-bgm-info"
                  onClick={() => {
                    setBgmMusic(item.id, item.title, item.url);
                    setBgmModalOpen(false);
                  }}
                >
                  <div className="modal-bgm-row-title">{item.title}</div>
                  <div className="modal-bgm-row-meta">
                    {CATEGORY_LABELS[item.category] || item.category}
                    {item.duration ? ` · ${Math.floor(item.duration / 60)}:${String(Math.floor(item.duration % 60)).padStart(2, '0')}` : ''}
                  </div>
                </div>
                {bgmMusicId === item.id && (
                  <div className="modal-bgm-check">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
                      <polyline points="20 6 9 17 4 12" />
                    </svg>
                  </div>
                )}
              </div>
            ))}
            {filteredBgmList.length === 0 && (
              <div className="modal-bgm-empty">该分类暂无背景音乐</div>
            )}
          </div>

          {/* 底部操作 */}
          <div className="modal-bgm-footer">
            <button
              className="modal-bgm-upload-btn"
              onClick={() => { void handleLocalBgmUpload(); }}
            >
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" />
                <polyline points="17,8 12,3 7,8" />
                <line x1="12" y1="3" x2="12" y2="15" />
              </svg>
              本地上传
            </button>

            <button className="btn btn-secondary btn-sm" onClick={() => setBgmModalOpen(false)}>取消</button>
          </div>
        </div>
      </Modal>
    </div>
    </>
  );
}
