'use client';

import { Check, ChevronDown, Download, Trash2, X } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { createPortal } from 'react-dom';

import { downloadDB, CompletedTask } from '@/lib/download-db';
import {
  buildIndexedDBVideoCacheKey,
  getIndexedDBVideoManifestByEpisode,
  getIndexedDBVideoSegments,
  deleteIndexedDBVideoCacheByEpisode,
} from '@/lib/indexeddb-video-cache';

import { ConfirmDialog } from './ConfirmDialog';

interface DownloadManagementPanelProps {
  isOpen: boolean;
  onClose: () => void;
}

interface VideoDownloadGroup {
  key: string;
  source: string;
  videoId: string;
  title: string;
  tasks: CompletedTask[];
  totalSize?: number;
  lastCompletedAt: number;
  downloadModes: CompletedTask['downloadMode'][];
}

export function DownloadManagementPanel({
  isOpen,
  onClose,
}: DownloadManagementPanelProps) {
  const [completedTasks, setCompletedTasks] = useState<CompletedTask[]>([]);
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const [expandedGroupKeys, setExpandedGroupKeys] = useState<Set<string>>(
    new Set()
  );
  const [isDeleting, setIsDeleting] = useState(false);
  const [isExporting, setIsExporting] = useState(false);
  const [mounted, setMounted] = useState(false);
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  useEffect(() => {
    if (isOpen) {
      loadCompletedTasks();
    }
  }, [isOpen]);

  const loadCompletedTasks = async () => {
    try {
      const tasks = await downloadDB.getCompletedTasks();
      setCompletedTasks(tasks);
    } catch (error) {
      console.error('加载已完成任务失败:', error);
    }
  };

  const handleSelectAll = () => {
    if (selectedIds.size === completedTasks.length) {
      setSelectedIds(new Set());
    } else {
      setSelectedIds(new Set(completedTasks.map((t) => t.id)));
    }
  };

  const handleToggleSelect = (id: string) => {
    const newSet = new Set(selectedIds);
    if (newSet.has(id)) {
      newSet.delete(id);
    } else {
      newSet.add(id);
    }
    setSelectedIds(newSet);
  };

  const handleToggleGroupSelect = (group: VideoDownloadGroup) => {
    const newSet = new Set(selectedIds);
    const allSelected = group.tasks.every((task) => newSet.has(task.id));

    if (allSelected) {
      group.tasks.forEach((task) => newSet.delete(task.id));
    } else {
      group.tasks.forEach((task) => newSet.add(task.id));
    }

    setSelectedIds(newSet);
  };

  const handleToggleGroupExpand = (groupKey: string) => {
    const newSet = new Set(expandedGroupKeys);
    if (newSet.has(groupKey)) {
      newSet.delete(groupKey);
    } else {
      newSet.add(groupKey);
    }
    setExpandedGroupKeys(newSet);
  };

  const handleDelete = async () => {
    if (selectedIds.size === 0) return;

    setShowConfirmDialog(true);
  };

  const handleConfirmDelete = async () => {
    setShowConfirmDialog(false);
    setIsDeleting(true);
    try {
      // 获取要删除的任务
      const tasksToDelete = completedTasks.filter((t) => selectedIds.has(t.id));

      //禁止SzeMeng76抄袭狗抄袭
      // 删除文件系统中的文件
      for (const task of tasksToDelete) {
        if (task.downloadMode === 'filesystem') {
          try {
            // 从 IndexedDB 读取目录句柄
            const dbName = 'MoonTVPlus';
            const storeName = 'dirHandles';

            const dirHandle = await new Promise<
              FileSystemDirectoryHandle | undefined
            >((resolve) => {
              const request = indexedDB.open(dbName, 2); // 使用版本 2

              request.onsuccess = (event) => {
                const db = (event.target as IDBOpenDBRequest).result;

                if (!db.objectStoreNames.contains(storeName)) {
                  db.close();
                  resolve(undefined);
                  return;
                }

                const transaction = db.transaction([storeName], 'readonly');
                const store = transaction.objectStore(storeName);
                const getRequest = store.get('downloadDir');

                getRequest.onsuccess = () => {
                  const handle = getRequest.result as
                    | FileSystemDirectoryHandle
                    | undefined;
                  db.close();
                  resolve(handle);
                };

                getRequest.onerror = () => {
                  db.close();
                  resolve(undefined);
                };
              };

              request.onerror = () => {
                resolve(undefined);
              };
            });

            if (dirHandle) {
              // 请求写权限
              const permission = await (dirHandle as any).requestPermission({
                mode: 'readwrite',
              });
              if (permission !== 'granted') {
                console.error('未获得写权限，无法删除文件');
                continue;
              }

              // 删除目录
              try {
                const sourceDirHandle = await dirHandle.getDirectoryHandle(
                  task.source,
                  { create: false }
                );
                const videoIdDirHandle =
                  await sourceDirHandle.getDirectoryHandle(task.videoId, {
                    create: false,
                  });
                await videoIdDirHandle.removeEntry(
                  `ep${task.episodeIndex + 1}`,
                  { recursive: true }
                );
                console.log(
                  '已删除文件:',
                  task.source,
                  task.videoId,
                  `ep${task.episodeIndex + 1}`
                );
              } catch (deleteError) {
                console.error('删除目录失败:', deleteError);
                // 如果目录不存在，也算成功
                if ((deleteError as Error).name !== 'NotFoundError') {
                  throw deleteError;
                }
              }
            }
          } catch (error) {
            console.error('删除文件失败:', task.title, error);
          }
        } else if (task.downloadMode === 'indexeddb') {
          try {
            await deleteIndexedDBVideoCacheByEpisode(
              task.source,
              task.videoId,
              task.episodeIndex
            );
            console.log(
              '已删除 IndexedDB 视频缓存:',
              task.source,
              task.videoId,
              task.episodeIndex
            );
          } catch (error) {
            console.error('删除 IndexedDB 视频缓存失败:', task.title, error);
          }
        }
      }

      // 从数据库删除记录
      await downloadDB.deleteCompletedTasks(Array.from(selectedIds));
      await loadCompletedTasks();
      setSelectedIds(new Set());
    } catch (error) {
      console.error('删除任务失败:', error);
      alert('删除失败，请重试');
    } finally {
      setIsDeleting(false);
    }
  };

  const sanitizeFilename = (name: string) => {
    return (
      name
        .replace(/[\\/:*?"<>|]/g, '_')
        .replace(/\s+/g, ' ')
        .trim()
        .slice(0, 120) || 'download'
    );
  };

  const triggerBlobDownload = (blob: Blob, filename: string) => {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    window.setTimeout(() => URL.revokeObjectURL(url), 1000);
  };

  const getTaskExportFilename = (
    task: CompletedTask,
    usedFilenames: Set<string>
  ) => {
    const episodeLabel = `第${task.episodeIndex + 1}集`;
    const baseTitle = task.videoTitle || getGroupTitle([task]);
    const episodeTitle = task.episodeTitle
      ? `${episodeLabel}_${task.episodeTitle}`
      : episodeLabel;
    const baseFilename = sanitizeFilename(`${baseTitle}_${episodeTitle}`);
    let filename = `${baseFilename}.ts`;
    let duplicateIndex = 2;

    while (usedFilenames.has(filename)) {
      filename = `${baseFilename}_${duplicateIndex}.ts`;
      duplicateIndex += 1;
    }

    usedFilenames.add(filename);
    return filename;
  };

  const getStoredDownloadDirHandle = async (): Promise<
    FileSystemDirectoryHandle | undefined
  > => {
    const dbName = 'MoonTVPlus';
    const storeName = 'dirHandles';

    return new Promise<FileSystemDirectoryHandle | undefined>((resolve) => {
      const request = indexedDB.open(dbName, 2);

      request.onsuccess = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;

        if (!db.objectStoreNames.contains(storeName)) {
          db.close();
          resolve(undefined);
          return;
        }

        const transaction = db.transaction([storeName], 'readonly');
        const store = transaction.objectStore(storeName);
        const getRequest = store.get('downloadDir');

        getRequest.onsuccess = () => {
          const handle = getRequest.result as
            | FileSystemDirectoryHandle
            | undefined;
          db.close();
          resolve(handle);
        };

        getRequest.onerror = () => {
          db.close();
          resolve(undefined);
        };
      };

      request.onerror = () => resolve(undefined);
    });
  };

  const readFilesystemTaskAsBlobParts = async (
    task: CompletedTask,
    dirHandle: FileSystemDirectoryHandle
  ): Promise<BlobPart[]> => {
    const sourceDirHandle = await dirHandle.getDirectoryHandle(task.source, {
      create: false,
    });
    const videoIdDirHandle = await sourceDirHandle.getDirectoryHandle(
      task.videoId,
      { create: false }
    );
    const epDirHandle = await videoIdDirHandle.getDirectoryHandle(
      `ep${task.episodeIndex + 1}`,
      { create: false }
    );

    const segmentFiles: Array<{ name: string; file: File }> = [];
    for await (const entry of epDirHandle.values()) {
      if (entry.kind === 'file' && /^segment_\d+\.ts$/i.test(entry.name)) {
        const fileHandle = entry as FileSystemFileHandle;
        segmentFiles.push({
          name: entry.name,
          file: await fileHandle.getFile(),
        });
      }
    }

    segmentFiles.sort((a, b) =>
      a.name.localeCompare(b.name, undefined, { numeric: true })
    );

    if (segmentFiles.length === 0) {
      throw new Error('未找到可导出的分片文件');
    }

    return segmentFiles.map(({ file }) => file);
  };

  const readIndexedDBTaskAsBlobParts = async (
    task: CompletedTask
  ): Promise<BlobPart[]> => {
    const manifest = await getIndexedDBVideoManifestByEpisode(
      task.source,
      task.videoId,
      task.episodeIndex
    );
    const cacheKey =
      manifest?.cacheKey ||
      buildIndexedDBVideoCacheKey(task.source, task.videoId, task.episodeIndex);
    const segments = await getIndexedDBVideoSegments(cacheKey);

    if (segments.length === 0) {
      throw new Error('未找到可导出的 IndexedDB 分片');
    }

    return segments.map((segment) => segment.data);
  };

  const handleExport = async () => {
    if (selectedIds.size === 0 || isExporting) return;

    setIsExporting(true);
    try {
      const tasksToExport = completedTasks
        .filter((task) => selectedIds.has(task.id))
        .sort((a, b) => {
          const groupCompare = `${a.source}::${a.videoId}`.localeCompare(
            `${b.source}::${b.videoId}`
          );
          if (groupCompare !== 0) return groupCompare;
          if (a.episodeIndex !== b.episodeIndex)
            return a.episodeIndex - b.episodeIndex;
          return a.completedAt - b.completedAt;
        });

      const unsupportedTasks = tasksToExport.filter(
        (task) => task.downloadMode === 'browser'
      );
      if (unsupportedTasks.length > 0) {
        alert(
          '浏览器下载模式的文件未保存在本地缓存中，无法从下载管理导出。请仅选择 File System API 或 IndexedDB 缓存记录。'
        );
        return;
      }

      let dirHandle: FileSystemDirectoryHandle | undefined;
      if (tasksToExport.some((task) => task.downloadMode === 'filesystem')) {
        dirHandle = await getStoredDownloadDirHandle();
        if (!dirHandle) {
          alert('无法读取下载目录授权，请先在下载设置中重新选择保存目录。');
          return;
        }

        const permission = await (dirHandle as any).requestPermission({
          mode: 'read',
        });
        if (permission !== 'granted') {
          alert('未获得下载目录读取权限，无法导出。');
          return;
        }
      }

      const usedFilenames = new Set<string>();
      let exportedCount = 0;
      const failedTitles: string[] = [];

      for (const task of tasksToExport) {
        try {
          const parts =
            task.downloadMode === 'filesystem'
              ? await readFilesystemTaskAsBlobParts(task, dirHandle!)
              : await readIndexedDBTaskAsBlobParts(task);
          const blob = new Blob(parts, { type: 'video/MP2T' });
          triggerBlobDownload(blob, getTaskExportFilename(task, usedFilenames));
          exportedCount += 1;
        } catch (error) {
          console.error('导出任务失败:', task.title, error);
          failedTitles.push(`第 ${task.episodeIndex + 1} 集`);
        }
      }

      if (exportedCount === 0) {
        alert(`导出失败：${failedTitles.join('、') || '未找到可导出的内容'}`);
        return;
      }

      if (failedTitles.length > 0) {
        alert(`已导出可读取的内容，但以下条目失败：${failedTitles.join('、')}`);
      }
    } catch (error) {
      console.error('导出失败:', error);
      alert(
        `导出失败：${error instanceof Error ? error.message : String(error)}`
      );
    } finally {
      setIsExporting(false);
    }
  };

  const formatDate = (timestamp: number) => {
    const date = new Date(timestamp);
    return date.toLocaleString('zh-CN', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
    });
  };

  const formatFileSize = (bytes?: number) => {
    if (!bytes) return '未知';
    if (bytes < 1024) return bytes + ' B';
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
    if (bytes < 1024 * 1024 * 1024)
      return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
    return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
  };

  const getDownloadModeLabel = (mode: CompletedTask['downloadMode']) => {
    switch (mode) {
      case 'filesystem':
        return 'File System API';
      case 'indexeddb':
        return 'IndexedDB 缓存';
      case 'browser':
      default:
        return '浏览器下载';
    }
  };

  const getGroupTitle = (tasks: CompletedTask[]) => {
    const taskWithVideoTitle = tasks.find((task) => task.videoTitle);
    if (taskWithVideoTitle?.videoTitle) return taskWithVideoTitle.videoTitle;

    const firstTitle = tasks[0]?.title || '未知视频';
    return (
      firstTitle
        .replace(/[_\s-]*第\s*\d+\s*集.*$/u, '')
        .replace(/[_\s-]*EP?\s*\d+.*$/iu, '')
        .trim() || firstTitle
    );
  };

  const videoGroups = useMemo<VideoDownloadGroup[]>(() => {
    const groupMap = new Map<string, CompletedTask[]>();

    for (const task of completedTasks) {
      const key = `${task.source}::${task.videoId}`;
      const groupTasks = groupMap.get(key) || [];
      groupTasks.push(task);
      groupMap.set(key, groupTasks);
    }

    return Array.from(groupMap.entries())
      .map(([key, tasks]) => {
        const sortedTasks = [...tasks].sort((a, b) => {
          if (a.episodeIndex !== b.episodeIndex) {
            return a.episodeIndex - b.episodeIndex;
          }
          return b.completedAt - a.completedAt;
        });
        const totalSize = sortedTasks.reduce(
          (sum, task) => sum + (task.fileSize || 0),
          0
        );
        const modeSet = new Set<CompletedTask['downloadMode']>(
          sortedTasks.map((task) => task.downloadMode)
        );

        return {
          key,
          source: sortedTasks[0].source,
          videoId: sortedTasks[0].videoId,
          title: getGroupTitle(sortedTasks),
          tasks: sortedTasks,
          totalSize: totalSize > 0 ? totalSize : undefined,
          lastCompletedAt: Math.max(
            ...sortedTasks.map((task) => task.completedAt)
          ),
          downloadModes: Array.from(modeSet),
        };
      })
      .sort((a, b) => b.lastCompletedAt - a.lastCompletedAt);
  }, [completedTasks]);

  if (!mounted || !isOpen) return null;

  return (
    <>
      {createPortal(
        <div className='fixed inset-0 z-[9999] flex items-end justify-center p-0 sm:items-center sm:p-4'>
          <div className='absolute inset-0 bg-black/50' onClick={onClose} />
          <div className='relative flex h-[92dvh] max-h-[92dvh] w-full max-w-4xl flex-col rounded-t-2xl bg-white shadow-xl dark:bg-gray-900 sm:h-auto sm:max-h-[90vh] sm:rounded-lg'>
            {/* Header */}
            <div className='flex items-center justify-between border-b border-gray-200 p-3 dark:border-gray-700 sm:p-4'>
              <h2 className='text-lg font-semibold text-gray-800 dark:text-gray-200 sm:text-xl'>
                下载文件管理
              </h2>
              <button
                onClick={onClose}
                className='rounded p-2 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800'
                aria-label='关闭下载文件管理'
              >
                <X className='w-5 h-5 text-gray-600 dark:text-gray-400' />
              </button>
            </div>

            {/* Toolbar */}
            <div className='flex items-center justify-between gap-3 border-b border-gray-200 p-3 dark:border-gray-700 sm:p-4'>
              <div className='min-w-0 flex-1 flex-col gap-2 sm:flex sm:flex-row sm:items-center sm:gap-4'>
                <label className='flex items-center gap-2 cursor-pointer'>
                  <input
                    type='checkbox'
                    checked={
                      selectedIds.size === completedTasks.length &&
                      completedTasks.length > 0
                    }
                    onChange={handleSelectAll}
                    className='w-4 h-4'
                  />
                  <span className='text-sm text-gray-700 dark:text-gray-300'>
                    全选
                  </span>
                </label>
                <span className='text-sm text-gray-500 dark:text-gray-400'>
                  已选择 {selectedIds.size} / {completedTasks.length} 集
                  {videoGroups.length > 0 &&
                    `，共 ${videoGroups.length} 个视频`}
                </span>
              </div>
              <div className='ml-auto flex flex-shrink-0 items-center justify-end gap-2'>
                <button
                  onClick={handleExport}
                  aria-label={isExporting ? '导出中' : '导出选中'}
                  disabled={selectedIds.size === 0 || isExporting || isDeleting}
                  className='flex h-10 w-10 items-center justify-center rounded bg-green-500 text-white transition-colors hover:bg-green-600 disabled:cursor-not-allowed disabled:opacity-50 sm:h-auto sm:w-auto sm:gap-2 sm:px-4 sm:py-2 sm:text-sm'
                >
                  <Download className='h-4 w-4' />
                  <span className='hidden sm:inline'>
                    {isExporting ? '导出中...' : '导出选中'}
                  </span>
                </button>
                <button
                  onClick={handleDelete}
                  aria-label={isDeleting ? '删除中' : '删除选中'}
                  disabled={selectedIds.size === 0 || isDeleting || isExporting}
                  className='flex h-10 w-10 items-center justify-center rounded bg-red-500 text-white transition-colors hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50 sm:h-auto sm:w-auto sm:gap-2 sm:px-4 sm:py-2 sm:text-sm'
                >
                  <Trash2 className='h-4 w-4' />
                  <span className='hidden sm:inline'>
                    {isDeleting ? '删除中...' : '删除选中'}
                  </span>
                </button>
              </div>
            </div>

            {/* Content */}
            <div className='flex-1 overflow-y-auto p-3 sm:p-4'>
              {completedTasks.length === 0 ? (
                <div className='text-center py-12 text-gray-500 dark:text-gray-400'>
                  暂无下载记录
                </div>
              ) : (
                <div className='space-y-3'>
                  {videoGroups.map((group) => {
                    const isExpanded = expandedGroupKeys.has(group.key);
                    const selectedCount = group.tasks.filter((task) =>
                      selectedIds.has(task.id)
                    ).length;
                    const isGroupSelected =
                      selectedCount === group.tasks.length;
                    const isGroupPartiallySelected =
                      selectedCount > 0 && !isGroupSelected;

                    return (
                      <div
                        key={group.key}
                        className={`border rounded-lg overflow-hidden transition-colors ${
                          isGroupSelected || isGroupPartiallySelected
                            ? 'border-green-500 bg-green-50/70 dark:bg-green-900/10'
                            : 'border-gray-200 dark:border-gray-700'
                        }`}
                      >
                        <div
                          className='flex cursor-pointer items-start gap-2 p-3 hover:bg-gray-50 dark:hover:bg-gray-800/80 sm:gap-3 sm:p-4'
                          onClick={() => handleToggleGroupExpand(group.key)}
                        >
                          <button
                            type='button'
                            onClick={(event) => {
                              event.stopPropagation();
                              handleToggleGroupSelect(group);
                            }}
                            className={`flex h-8 w-8 flex-shrink-0 items-center justify-center rounded border-2 transition-colors sm:mt-1 sm:h-5 sm:w-5 ${
                              isGroupSelected
                                ? 'border-green-500 bg-green-500'
                                : isGroupPartiallySelected
                                ? 'border-green-500 bg-green-100 dark:bg-green-900/40'
                                : 'border-gray-300 dark:border-gray-600'
                            }`}
                            aria-label={`选择 ${group.title}`}
                          >
                            {isGroupSelected ? (
                              <Check className='h-4 w-4 text-white sm:h-3 sm:w-3' />
                            ) : isGroupPartiallySelected ? (
                              <span className='h-0.5 w-3 rounded bg-green-500 sm:w-2.5' />
                            ) : null}
                          </button>

                          <div className='min-w-0 flex-1'>
                            <div className='flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-3'>
                              <div className='min-w-0 flex-1'>
                                <h3 className='line-clamp-2 text-sm font-semibold text-gray-800 dark:text-gray-200 sm:truncate'>
                                  {group.title}
                                </h3>
                                <div className='mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-gray-500 dark:text-gray-400'>
                                  <span>来源: {group.source}</span>
                                  <span>•</span>
                                  <span>{group.tasks.length} 集</span>
                                  <span>•</span>
                                  <span>
                                    {group.downloadModes
                                      .map(getDownloadModeLabel)
                                      .join(' / ')}
                                  </span>
                                </div>
                              </div>
                              <div className='flex w-full flex-row-reverse items-center justify-between gap-3 text-left sm:w-auto sm:flex-row sm:items-start sm:text-right'>
                                <div className='min-w-0 sm:min-w-[150px]'>
                                  <div className='text-xs text-gray-500 dark:text-gray-400'>
                                    最近完成：
                                    {formatDate(group.lastCompletedAt)}
                                  </div>
                                  <div className='mt-1 text-xs text-gray-500 dark:text-gray-400'>
                                    总大小：{formatFileSize(group.totalSize)}
                                  </div>
                                  {selectedCount > 0 && (
                                    <div className='mt-1 text-xs text-green-600 dark:text-green-400'>
                                      已选 {selectedCount} 集
                                    </div>
                                  )}
                                </div>
                                <ChevronDown
                                  className={`h-5 w-5 flex-shrink-0 text-gray-400 transition-transform sm:mt-1 ${
                                    isExpanded ? 'rotate-180' : ''
                                  }`}
                                />
                              </div>
                            </div>
                          </div>
                        </div>

                        {isExpanded && (
                          <div className='border-t border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900'>
                            {group.tasks.map((task) => (
                              <div
                                key={task.id}
                                className={`flex cursor-pointer items-start gap-2 px-3 py-3.5 transition-colors sm:gap-3 sm:px-4 sm:py-3 ${
                                  selectedIds.has(task.id)
                                    ? 'bg-green-50 dark:bg-green-900/20'
                                    : 'hover:bg-gray-50 dark:hover:bg-gray-800'
                                }`}
                                onClick={() => handleToggleSelect(task.id)}
                              >
                                <div className='flex-shrink-0 sm:mt-1'>
                                  <div
                                    className={`flex h-8 w-8 items-center justify-center rounded border-2 sm:h-5 sm:w-5 ${
                                      selectedIds.has(task.id)
                                        ? 'border-green-500 bg-green-500'
                                        : 'border-gray-300 dark:border-gray-600'
                                    }`}
                                  >
                                    {selectedIds.has(task.id) && (
                                      <Check className='h-4 w-4 text-white sm:h-3 sm:w-3' />
                                    )}
                                  </div>
                                </div>
                                <div className='min-w-0 flex-1'>
                                  <div className='flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between'>
                                    <div className='min-w-0 flex-1'>
                                      <h4 className='line-clamp-2 text-sm font-medium text-gray-800 dark:text-gray-200 sm:truncate'>
                                        第 {task.episodeIndex + 1} 集
                                        {task.episodeTitle
                                          ? `：${task.episodeTitle}`
                                          : ''}
                                      </h4>
                                      <p className='mt-1 truncate text-xs text-gray-500 dark:text-gray-400'>
                                        {task.title}
                                      </p>
                                    </div>
                                    <div className='flex-shrink-0 text-left sm:text-right'>
                                      <div className='text-xs text-gray-500 dark:text-gray-400'>
                                        {formatDate(task.completedAt)}
                                      </div>
                                      <div className='mt-1 text-xs text-gray-500 dark:text-gray-400'>
                                        {formatFileSize(task.fileSize)}
                                      </div>
                                    </div>
                                  </div>
                                  <div className='mt-2 flex flex-wrap items-center gap-2 text-xs text-gray-500 dark:text-gray-400'>
                                    <span>第 {task.episodeIndex + 1} 集</span>
                                    <span>•</span>
                                    <span>
                                      {getDownloadModeLabel(task.downloadMode)}
                                    </span>
                                  </div>
                                </div>
                              </div>
                            ))}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          </div>
        </div>,
        document.body
      )}

      <ConfirmDialog
        isOpen={showConfirmDialog}
        title='确认删除'
        message={`确定要删除选中的 ${selectedIds.size} 个下载记录吗？\n\n注意：File System API 文件会从磁盘删除，IndexedDB 缓存会从浏览器独立视频缓存库删除。`}
        confirmText='删除'
        cancelText='取消'
        variant='danger'
        onConfirm={handleConfirmDelete}
        onCancel={() => setShowConfirmDialog(false)}
      />
    </>
  );
}
