/**
 * 配音库页面 — V21 设计系统
 *
 * 任务 3.11：删除 Mock 数据，使用 useQuery 对接后端真实 API
 *
 * Tab 1：预置音色 — fetchPresetVoices()
 * Tab 2：我的克隆 — getVoiceClonesWithTotal()
 * 统计：fetchVoices({ limit: 1 }) 获取 preset_count / clone_count
 */
import React, {
  useMemo,
  useState,
  useRef,
  useEffect,
  useCallback,
} from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
  SoundOutlined,
  PlayCircleOutlined,
  PauseCircleOutlined,
  SearchOutlined,
  PlusOutlined,
  RobotOutlined,
  UploadOutlined,
  AudioOutlined,
  HeartOutlined,
  UserOutlined,
  DeleteOutlined,
  ReloadOutlined,
  CloseCircleOutlined,
} from "@ant-design/icons";
import { Button, Input, Select, Tooltip } from "@/components/ui";
import PageHead from "@/components/layout/PageHead";
import {
  fetchPresetVoices,
  fetchVoices,
  type PresetVoiceItem,
} from "@/api/voices";
import {
  getVoiceClonesWithTotal,
  deleteVoiceClone,
  retryVoiceClone,
  toVoiceClone,
  type VoiceClone,
} from "@/api/voiceClone";
import CloneModal from "@/components/voice/CloneModal";
import "./voices.css";

/* ============================================================
 * 类型
 * ============================================================ */
type VoiceGender = "male" | "female" | "child" | "elderly";
type VoiceLanguage = "zh" | "en" | "ja" | "ko";
type TabKey = "preset" | "cloned";

/** 前端展示用的预置音色（从 PresetVoiceItem 映射） */
interface PresetVoiceDisplay {
  id: string;
  name: string;
  gender: VoiceGender;
  language: VoiceLanguage;
  duration: number;
  tags: string[];
  description: string;
  voiceId: string;
  previewUrl: string;
  starred: boolean;
}

/** 前端展示用的克隆音色（从 VoiceClone 映射） */
interface ClonedVoiceDisplay {
  id: string;
  name: string;
  description: string;
  sourceName: string;
  status: "ready" | "processing" | "failed";
  createdAt: string;
  duration: number;
  tags: string[];
  voiceId: string;
  language: string;
  gender: string;
  errorMessage: string | null;
  sampleUrl?: string;
}

/* ============================================================
 * 映射函数
 * ============================================================ */

const mapPresetToDisplay = (item: PresetVoiceItem): PresetVoiceDisplay => ({
  id: item.voice_id,
  name: item.name,
  gender: (item.gender as VoiceGender) || "female",
  language: (item.language as VoiceLanguage) || "zh",
  duration: 0,
  tags: item.tags,
  description: item.description,
  voiceId: item.voice_id,
  previewUrl: item.preview_url || "",
  starred: false,
});

const mapCloneToDisplay = (clone: VoiceClone): ClonedVoiceDisplay => ({
  id: clone.id,
  name: clone.name,
  description: clone.description || "",
  sourceName: clone.sample_url || "未知来源",
  status: clone.status,
  createdAt: new Date(clone.created_at).toLocaleDateString("zh-CN"),
  duration: clone.duration_seconds,
  tags: [],
  voiceId: clone.id,
  language: clone.language || "",
  gender: clone.gender || "",
  errorMessage: clone.error_message || null,
  sampleUrl: clone.sample_url || undefined,
});

/* ============================================================
 * 工具函数
 * ============================================================ */
const genderLabel = (g: VoiceGender) => {
  const map: Record<VoiceGender, string> = {
    male: "男声",
    female: "女声",
    child: "童声",
    elderly: "老年",
  };
  return map[g];
};

const languageLabel = (l: VoiceLanguage) => {
  const map: Record<VoiceLanguage, string> = {
    zh: "中文",
    en: "英文",
    ja: "日文",
    ko: "韩文",
  };
  return map[l];
};

const formatTime = (seconds: number): string => {
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
};

const genderClass = (g: VoiceGender) => `xx-voice-gender--${g}`;

/* ============================================================
 * VoiceCard 组件（预置音色 + 克隆音色统一）
 * ============================================================ */
interface VoiceCardProps {
  id: string;
  name: string;
  subtitle: string;
  tags: string[];
  duration: number;
  gender: VoiceGender;
  isPlaying: boolean;
  isSelected: boolean;
  currentTime: number;
  starred?: boolean;
  status?: "ready" | "processing" | "failed";
  onPlay: () => void;
  onPause: () => void;
  onSeek: (time: number) => void;
  onSelect?: () => void;
  onToggleStar?: () => void;
}

const VoiceCard: React.FC<VoiceCardProps> = ({
  id: _id,
  name,
  subtitle,
  tags,
  duration,
  gender,
  isPlaying,
  isSelected,
  currentTime,
  starred,
  status = "ready",
  onPlay,
  onPause,
  onSeek,
  onSelect,
  onToggleStar,
}) => {
  const progressRef = useRef<HTMLDivElement>(null);

  const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!progressRef.current || status !== "ready") return;
    const rect = progressRef.current.getBoundingClientRect();
    const percent = (e.clientX - rect.left) / rect.width;
    onSeek(percent * duration);
  };

  const progress = duration > 0 ? (currentTime / duration) * 100 : 0;

  return (
    <div
      className={`xx-voice-card ${genderClass(gender)}${isSelected ? " selected" : ""}${isPlaying ? " playing" : ""}`}
      onClick={onSelect}
    >
      <div className="xx-voice-avatar">
        <SoundOutlined />
      </div>

      <div className="xx-voice-info">
        <div className="xx-voice-name-row">
          <h4 className="xx-voice-name" title={name}>
            {name}
          </h4>
          {starred !== undefined && (
            <button
              className={`xx-voice-star${starred ? " active" : ""}`}
              onClick={(e) => {
                e.stopPropagation();
                onToggleStar?.();
              }}
              title={starred ? "取消收藏" : "收藏"}
            >
              <HeartOutlined />
            </button>
          )}
        </div>
        <div className="xx-voice-subtitle">{subtitle}</div>
        <div className="xx-voice-tags">
          {tags.slice(0, 3).map((tag) => (
            <span key={tag} className="xx-voice-tag">
              {tag}
            </span>
          ))}
        </div>
      </div>

      {status === "processing" && (
        <div className="xx-voice-status xx-voice-status--processing">
          <span className="xx-voice-status-dot" />
          处理中...
        </div>
      )}
      {status === "failed" && (
        <div className="xx-voice-status xx-voice-status--failed">克隆失败</div>
      )}

      {status === "ready" && <div className="xx-voice-wave" />}

      {status === "ready" && (
        <div className="xx-voice-controls">
          <button
            className="xx-voice-play-btn"
            onClick={(e) => {
              e.stopPropagation();
              isPlaying ? onPause() : onPlay();
            }}
            title={isPlaying ? "暂停" : "试听"}
          >
            {isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
          </button>
          <div
            ref={progressRef}
            className="xx-voice-progress"
            onClick={handleProgressClick}
          >
            <div
              className="xx-voice-progress-bar"
              style={{ width: `${progress}%` }}
            />
          </div>
          <span className="xx-voice-time">
            {isPlaying ? formatTime(currentTime) : formatTime(duration)}
          </span>
        </div>
      )}
    </div>
  );
};

/* ============================================================
 * 克隆音色卡片组件（任务 3.12）
 * ============================================================ */

/** 状态配置 */
const CLONE_STATUS_CONFIG: Record<
  ClonedVoiceDisplay["status"],
  { label: string; className: string }
> = {
  ready: { label: "可用", className: "xx-clone-status--ready" },
  processing: { label: "处理中", className: "xx-clone-status--processing" },
  failed: { label: "失败", className: "xx-clone-status--failed" },
};

/** Toast 类型 */
interface Toast {
  id: number;
  message: string;
  type: "success" | "error";
}

let toastIdSeq = 0;

/** 克隆音色卡片 */
interface CloneVoiceCardProps {
  voice: ClonedVoiceDisplay;
  isPlaying: boolean;
  currentTime: number;
  onPlay: () => void;
  onPause: () => void;
  onUse: () => void;
  onDelete: () => void;
  onRetry: () => void;
  onShowDetail: () => void;
}

const CloneVoiceCard: React.FC<CloneVoiceCardProps> = ({
  voice,
  isPlaying,
  currentTime,
  onPlay,
  onPause,
  onUse,
  onDelete,
  onRetry,
  onShowDetail,
}) => {
  const statusCfg = CLONE_STATUS_CONFIG[voice.status];
  const isFailed = voice.status === "failed";
  const isProcessing = voice.status === "processing";
  const genderText =
    voice.gender === "male"
      ? "男声"
      : voice.gender === "female"
        ? "女声"
        : voice.gender;

  return (
    <div
      className={`xx-clone-card${isPlaying ? " playing" : ""}${isFailed ? " failed" : ""}`}
      onClick={isFailed ? undefined : onShowDetail}
    >
      {/* 右上角操作按钮 */}
      <div className="xx-clone-card-actions">
        <Tooltip title="删除">
          <button
            type="button"
            className="xx-clone-action-btn xx-clone-action-btn--danger"
            onClick={(e) => {
              e.stopPropagation();
              onDelete();
            }}
          >
            <DeleteOutlined />
          </button>
        </Tooltip>
        {isFailed && (
          <Tooltip title="重试">
            <button
              type="button"
              className="xx-clone-action-btn"
              onClick={(e) => {
                e.stopPropagation();
                onRetry();
              }}
            >
              <ReloadOutlined />
            </button>
          </Tooltip>
        )}
      </div>

      {/* 头部：头像 + 名称 + 状态 */}
      <div className="xx-clone-card-header">
        <div
          className={`xx-clone-avatar${isProcessing ? " xx-clone-avatar--processing" : ""}`}
        >
          <SoundOutlined />
        </div>
        <div className="xx-clone-header-info">
          <h4 className="xx-clone-name" title={voice.name}>
            {voice.name}
          </h4>
          <span className={`xx-clone-status ${statusCfg.className}`}>
            <span className="xx-clone-status-dot" />
            {statusCfg.label}
          </span>
        </div>
      </div>

      {/* 描述 */}
      {voice.description && (
        <p className="xx-clone-desc">{voice.description}</p>
      )}

      {/* 元信息 */}
      <div className="xx-clone-meta">
        {(voice.gender || voice.language) && (
          <span className="xx-clone-meta-item">
            <UserOutlined />
            {genderText}
            {voice.language ? ` · ${voice.language}` : ""}
          </span>
        )}
        <span className="xx-clone-meta-item">{voice.createdAt}</span>
      </div>

      {/* 错误信息 */}
      {isFailed && voice.errorMessage && (
        <div className="xx-clone-error">
          <CloseCircleOutlined />
          <span>{voice.errorMessage}</span>
        </div>
      )}

      {/* 底部操作区 */}
      <div className="xx-clone-footer">
        {voice.status === "ready" && (
          <>
            <button
              type="button"
              className="xx-clone-play-btn"
              onClick={(e) => {
                e.stopPropagation();
                isPlaying ? onPause() : onPlay();
              }}
              title={isPlaying ? "暂停" : "试听"}
            >
              {isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
            </button>
            <div className="xx-clone-progress">
              <div
                className="xx-clone-progress-bar"
                style={{
                  width: isPlaying
                    ? `${Math.min((currentTime / Math.max(voice.duration, 1)) * 100, 100)}%`
                    : "0%",
                }}
              />
            </div>
            <button
              type="button"
              className="xx-clone-use-btn"
              onClick={(e) => {
                e.stopPropagation();
                onUse();
              }}
            >
              使用
            </button>
          </>
        )}
        {isProcessing && (
          <div className="xx-clone-processing-hint">
            <ReloadOutlined spin />
            克隆处理中，请稍候...
          </div>
        )}
        {isFailed && (
          <button
            type="button"
            className="xx-clone-retry-btn"
            onClick={(e) => {
              e.stopPropagation();
              onRetry();
            }}
          >
            <ReloadOutlined />
            重试克隆
          </button>
        )}
      </div>
    </div>
  );
};

/** 克隆音色详情弹窗 */
const CloneDetailModal: React.FC<{
  voice: ClonedVoiceDisplay;
  onClose: () => void;
  onUse: () => void;
  onDelete: () => void;
  onRetry: () => void;
}> = ({ voice, onClose, onUse, onDelete, onRetry }) => {
  const statusCfg = CLONE_STATUS_CONFIG[voice.status];
  const genderText =
    voice.gender === "male"
      ? "男声"
      : voice.gender === "female"
        ? "女声"
        : voice.gender || "未知";
  const langText = voice.language || "未知";

  return (
    <div className="xx-clone-detail-overlay" onClick={onClose}>
      <div className="xx-clone-detail" onClick={(e) => e.stopPropagation()}>
        <button
          type="button"
          className="xx-clone-detail-close"
          onClick={onClose}
        >
          <CloseCircleOutlined />
        </button>

        <div className="xx-clone-detail-header">
          <div className="xx-clone-avatar">
            <SoundOutlined />
          </div>
          <div>
            <h3 className="xx-clone-detail-name">{voice.name}</h3>
            <span className={`xx-clone-status ${statusCfg.className}`}>
              <span className="xx-clone-status-dot" />
              {statusCfg.label}
            </span>
          </div>
        </div>

        {voice.description && (
          <p className="xx-clone-detail-desc">{voice.description}</p>
        )}

        <div className="xx-clone-detail-info">
          <div className="xx-clone-detail-row">
            <span className="xx-clone-detail-label">性别</span>
            <span>{genderText}</span>
          </div>
          <div className="xx-clone-detail-row">
            <span className="xx-clone-detail-label">语言</span>
            <span>{langText}</span>
          </div>
          <div className="xx-clone-detail-row">
            <span className="xx-clone-detail-label">来源</span>
            <span>{voice.sourceName}</span>
          </div>
          <div className="xx-clone-detail-row">
            <span className="xx-clone-detail-label">创建时间</span>
            <span>{voice.createdAt}</span>
          </div>
          {voice.errorMessage && (
            <div
              className="xx-clone-detail-row"
              style={{ color: "var(--error-color, #ef4444)" }}
            >
              <span className="xx-clone-detail-label">错误</span>
              <span>{voice.errorMessage}</span>
            </div>
          )}
        </div>

        <div className="xx-clone-detail-actions">
          {voice.status === "failed" && (
            <Button
              buttonType="ghost"
              buttonSize="sm"
              icon={<ReloadOutlined />}
              onClick={onRetry}
            >
              重试
            </Button>
          )}
          <Button
            buttonType="ghost"
            buttonSize="sm"
            icon={<DeleteOutlined />}
            onClick={onDelete}
          >
            删除
          </Button>
          {voice.status === "ready" && (
            <Button buttonType="primary" buttonSize="sm" onClick={onUse}>
              使用此音色
            </Button>
          )}
        </div>
      </div>
    </div>
  );
};

/** 骨架屏 */
const CloneCardSkeleton: React.FC = () => (
  <div className="xx-voice-card xx-skeleton-clone">
    <div className="xx-skeleton-clone-avatar" />
    <div className="xx-skeleton-clone-info">
      <div className="xx-skeleton-clone-line xx-skeleton-clone-line--name" />
      <div className="xx-skeleton-clone-line xx-skeleton-clone-line--status" />
      <div className="xx-skeleton-clone-line xx-skeleton-clone-line--desc" />
      <div className="xx-skeleton-clone-line xx-skeleton-clone-line--meta" />
      <div className="xx-skeleton-clone-line xx-skeleton-clone-line--footer" />
    </div>
  </div>
);

/* ============================================================
 * 主组件
 * ============================================================ */
const VoiceLibrary: React.FC = () => {
  const [activeTab, setActiveTab] = useState<TabKey>("preset");
  const [searchText, setSearchText] = useState("");
  const [filterGender, setFilterGender] = useState<string>("all");
  const [filterLang, setFilterLang] = useState<string>("all");

  const [playingId, setPlayingId] = useState<string | null>(null);
  const [currentTime, setCurrentTime] = useState(0);
  const intervalRef = useRef<number | null>(null);

  const queryClient = useQueryClient();
  const [detailVoice, setDetailVoice] = useState<ClonedVoiceDisplay | null>(
    null,
  );
  const [toasts, setToasts] = useState<Toast[]>([]);
  const [cloneModalOpen, setCloneModalOpen] = useState(false);

  const showToast = useCallback((message: string, type: Toast["type"]) => {
    const id = ++toastIdSeq;
    setToasts((prev) => [...prev, { id, message, type }]);
    setTimeout(() => {
      setToasts((prev) => prev.filter((t) => t.id !== id));
    }, 3000);
  }, []);

  /** 删除克隆音色 */
  const deleteMutation = useMutation({
    mutationFn: deleteVoiceClone,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["voice-clones"] });
      showToast("音色已删除", "success");
    },
    onError: () => {
      showToast("删除失败", "error");
    },
  });

  /** 重试克隆 */
  const retryMutation = useMutation({
    mutationFn: retryVoiceClone,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["voice-clones"] });
      showToast("已重新提交克隆", "success");
    },
    onError: () => {
      showToast("重试失败", "error");
    },
  });

  /* ── 数据查询（任务 3.11：替换 Mock） ──────────────── */

  /** 预置音色列表 */
  const { data: presetData, isLoading: presetLoading } = useQuery({
    queryKey: ["preset-voices"],
    queryFn: fetchPresetVoices,
  });

  /** 克隆音色列表 */
  const { data: cloneData, isLoading: cloneLoading } = useQuery({
    queryKey: ["voice-clones"],
    queryFn: () => getVoiceClonesWithTotal({ limit: 50 }),
  });

  /** 统一统计（preset_count / clone_count） */
  const { data: unifiedStats } = useQuery({
    queryKey: ["voices-unified"],
    queryFn: () => fetchVoices({ limit: 1 }),
  });

  const presetVoices = useMemo(
    () => (presetData?.items ?? []).map(mapPresetToDisplay),
    [presetData],
  );
  const clonedVoices = useMemo(
    () =>
      (cloneData?.items ?? []).map((p) => mapCloneToDisplay(toVoiceClone(p))),
    [cloneData],
  );
  const presetCount = unifiedStats?.preset_count ?? presetData?.total ?? 0;
  const cloneCount = unifiedStats?.clone_count ?? cloneData?.total ?? 0;

  const filteredPreset = useMemo(() => {
    let list = presetVoices;
    if (filterGender !== "all") {
      list = list.filter((v) => v.gender === filterGender);
    }
    if (filterLang !== "all") {
      list = list.filter((v) => v.language === filterLang);
    }
    if (searchText.trim()) {
      const q = searchText.trim().toLowerCase();
      list = list.filter(
        (v) =>
          v.name.toLowerCase().includes(q) ||
          v.description.toLowerCase().includes(q) ||
          v.tags.some((tag) => tag.toLowerCase().includes(q)),
      );
    }
    return list;
  }, [presetVoices, filterGender, filterLang, searchText]);

  const handlePlay = useCallback(
    (voiceId: string, duration: number) => {
      if (playingId === voiceId) return;
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
      setPlayingId(voiceId);
      setCurrentTime(0);
      intervalRef.current = window.setInterval(() => {
        setCurrentTime((prev) => {
          if (prev >= duration) {
            if (intervalRef.current) {
              clearInterval(intervalRef.current);
              intervalRef.current = null;
            }
            setPlayingId(null);
            return 0;
          }
          return prev + 0.1;
        });
      }, 100);
    },
    [playingId],
  );

  const handlePause = useCallback(() => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
    setPlayingId(null);
  }, []);

  const handleSeek = useCallback(
    (voiceId: string, time: number, duration: number) => {
      setCurrentTime(time);
      if (playingId !== voiceId) {
        handlePlay(voiceId, duration);
      }
    },
    [playingId, handlePlay],
  );

  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, []);

  const handleToggleStar = (_voiceId: string) => {
    // TODO: 后端暂未提供收藏接口
  };

  /** 克隆音色操作 handlers */
  const handleCloneDelete = (voice: ClonedVoiceDisplay) => {
    if (window.confirm(`确定删除音色「${voice.name}」吗？`)) {
      deleteMutation.mutate(voice.id);
      if (detailVoice?.id === voice.id) setDetailVoice(null);
    }
  };

  const handleCloneRetry = (voice: ClonedVoiceDisplay) => {
    retryMutation.mutate(voice.id);
  };

  const handleCloneUse = (_voice: ClonedVoiceDisplay) => {
    showToast("已选择音色", "success");
  };

  const handleShowDetail = (voice: ClonedVoiceDisplay) => {
    setDetailVoice(voice);
  };

  const handleClonePlayPause = (voiceId: string, duration: number) => {
    if (playingId === voiceId) {
      handlePause();
    } else {
      handlePlay(voiceId, duration);
    }
  };

  /** 克隆音色成功回调 */
  const handleCloneSuccess = (_voice: VoiceClone) => {
    queryClient.invalidateQueries({ queryKey: ["voice-clones"] });
    showToast("克隆已提交，正在生成中", "success");
  };

  const pageActions = (
    <div className="xx-voices-actions">
      <Button buttonType="ghost" buttonSize="sm" icon={<UploadOutlined />}>
        上传音频
      </Button>
      <Button
        buttonType="ghost"
        buttonSize="sm"
        icon={<AudioOutlined />}
        onClick={() => setCloneModalOpen(true)}
      >
        克隆音色
      </Button>
      <Button buttonType="primary" buttonSize="sm" icon={<RobotOutlined />}>
        AI 配音
      </Button>
    </div>
  );

  return (
    <div className="xx-voices-page">
      <PageHead
        title="配音库"
        description="预置音色与克隆音色管理，支持 CosyVoice 高质量 TTS"
        actions={pageActions}
      />

      <div className="xx-voices-tabs">
        <button
          className={`xx-voices-tab${activeTab === "preset" ? " active" : ""}`}
          onClick={() => {
            setActiveTab("preset");
            handlePause();
          }}
        >
          <AudioOutlined />
          预置音色
          <span className="xx-voices-tab-count">{presetCount}</span>
        </button>
        <button
          className={`xx-voices-tab${activeTab === "cloned" ? " active" : ""}`}
          onClick={() => {
            setActiveTab("cloned");
            handlePause();
          }}
        >
          <UserOutlined />
          我的克隆
          <span className="xx-voices-tab-count">{cloneCount}</span>
        </button>
      </div>

      {activeTab === "preset" && (
        <div className="xx-voices-tab-content">
          <div className="xx-voices-filters">
            <Input
              placeholder="搜索音色名称、标签..."
              prefix={<SearchOutlined />}
              value={searchText}
              onChange={(e) => setSearchText(e.target.value)}
              allowClear
              style={{ width: 260 }}
            />
            <Select
              value={filterGender}
              onChange={setFilterGender}
              style={{ width: 130 }}
              options={[
                { value: "all", label: "全部音色" },
                { value: "male", label: "男声" },
                { value: "female", label: "女声" },
                { value: "child", label: "童声" },
                { value: "elderly", label: "老年" },
              ]}
            />
            <Select
              value={filterLang}
              onChange={setFilterLang}
              style={{ width: 130 }}
              options={[
                { value: "all", label: "全部语言" },
                { value: "zh", label: "中文" },
                { value: "en", label: "英文" },
                { value: "ja", label: "日文" },
                { value: "ko", label: "韩文" },
              ]}
            />
          </div>

          {presetLoading && (
            <div className="xx-voices-empty">
              <div className="xx-voices-empty-icon">
                <SoundOutlined />
              </div>
              <p>加载预置音色中...</p>
            </div>
          )}

          {!presetLoading && filteredPreset.length > 0 && (
            <div className="xx-voice-grid">
              {filteredPreset.map((voice) => (
                <VoiceCard
                  key={voice.id}
                  id={voice.id}
                  name={voice.name}
                  subtitle={`${genderLabel(voice.gender)} · ${languageLabel(voice.language)} · ${voice.description}`}
                  tags={voice.tags}
                  duration={voice.duration}
                  gender={voice.gender}
                  isPlaying={playingId === voice.id}
                  isSelected={false}
                  currentTime={playingId === voice.id ? currentTime : 0}
                  starred={voice.starred}
                  onPlay={() => handlePlay(voice.id, voice.duration)}
                  onPause={handlePause}
                  onSeek={(time) => handleSeek(voice.id, time, voice.duration)}
                  onToggleStar={() => handleToggleStar(voice.voiceId)}
                />
              ))}
            </div>
          )}

          {!presetLoading && filteredPreset.length === 0 && (
            <div className="xx-voices-empty">
              <div className="xx-voices-empty-icon">
                <SoundOutlined />
              </div>
              <p>未找到匹配的音色</p>
              <Button
                buttonType="ghost"
                buttonSize="sm"
                onClick={() => {
                  setSearchText("");
                  setFilterGender("all");
                  setFilterLang("all");
                }}
              >
                清除筛选条件
              </Button>
            </div>
          )}
        </div>
      )}

      {activeTab === "cloned" && (
        <div className="xx-voices-tab-content">
          {/* 骨架屏加载 */}
          {cloneLoading && (
            <div className="xx-voice-grid">
              {Array.from({ length: 6 }).map((_, i) => (
                <CloneCardSkeleton key={i} />
              ))}
            </div>
          )}

          {/* 卡片列表 */}
          {!cloneLoading && clonedVoices.length > 0 && (
            <div className="xx-voice-grid">
              {clonedVoices.map((voice) => (
                <CloneVoiceCard
                  key={voice.id}
                  voice={voice}
                  isPlaying={playingId === voice.id}
                  currentTime={playingId === voice.id ? currentTime : 0}
                  onPlay={() => handleClonePlayPause(voice.id, voice.duration)}
                  onPause={handlePause}
                  onUse={() => handleCloneUse(voice)}
                  onDelete={() => handleCloneDelete(voice)}
                  onRetry={() => handleCloneRetry(voice)}
                  onShowDetail={() => handleShowDetail(voice)}
                />
              ))}
            </div>
          )}

          {/* 空状态 */}
          {!cloneLoading && clonedVoices.length === 0 && (
            <div className="xx-voices-empty">
              <div className="xx-voices-empty-icon">
                <UserOutlined />
              </div>
              <h3>暂无克隆音色</h3>
              <p>上传音频素材即可克隆专属音色</p>
              <Button
                buttonType="primary"
                buttonSize="md"
                onClick={() => setCloneModalOpen(true)}
              >
                <PlusOutlined /> 去克隆音色
              </Button>
            </div>
          )}

          {/* 处理中提示 */}
          {!cloneLoading &&
            clonedVoices.some((v) => v.status === "processing") && (
              <div className="xx-clone-processing-hint">
                <RobotOutlined />
                <span>部分音色正在克隆处理中，完成后将自动出现在列表中。</span>
              </div>
            )}
        </div>
      )}

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

      {/* 详情弹窗 */}
      {detailVoice && (
        <CloneDetailModal
          voice={detailVoice}
          onClose={() => setDetailVoice(null)}
          onDelete={() => handleCloneDelete(detailVoice)}
          onRetry={() => handleCloneRetry(detailVoice)}
          onUse={() => handleCloneUse(detailVoice)}
        />
      )}

      {/* Toast 提示 */}
      {toasts.length > 0 && (
        <div className="vc-toast-container">
          {toasts.map((t) => (
            <div key={t.id} className={`vc-toast vc-toast--${t.type}`}>
              {t.type === "success" ? "✅" : "❌"} {t.message}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

export default VoiceLibrary;
