"use client";

import {
  ArrowPathIcon,
  ChartBarIcon,
  CheckCircleIcon,
  EyeSlashIcon,
  FolderIcon,
  Squares2X2Icon,
  TrashIcon,
  XMarkIcon,
} from "@heroicons/react/24/outline";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { del, get, post } from "../../lib/api";
import ConfirmationDialog from "../components/ConfirmationDialog";
import LoadingCard from "../components/Loading";
import { useNotifications } from "../components/NotificationContext";
import PathAutocomplete from "../components/PathAutocomplete";
import { useAppContext } from "../providers/AppContext";

interface DuplicateGroup {
  id: number;
  dataset: string;
  destination: string;
  hash: string;
  size: number;
  files: string[];
  status: string;
  created_at?: string;
  reviewed_at?: string;
  note?: string;
}

interface IndexStats {
  totalDuplicates: number;
  duplicatesByDataset: Array<{
    dataset: string;
    hash: string;
    file_size: number;
    file_count: number;
    files: string[];
  }>;
}

interface IndexCount {
  count: number;
}

type SortField = "dataset" | "count" | "size" | "created_at";

type DeleteSelection = {
  isOpen: boolean;
  groups: Array<{ id: number; files: string[] }>;
};

const formatBytes = (bytes: number) => {
  if (!bytes) return "0 B";
  const sizes = ["B", "KB", "MB", "GB", "TB"];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
};

function makeFileKey(groupId: number, path: string) {
  return `${groupId}::${encodeURIComponent(path)}`;
}

function parseFileKey(key: string) {
  const [idStr, encodedPath] = key.split("::");
  return { groupId: Number(idStr), path: decodeURIComponent(encodedPath) };
}

export default function DuplicateList() {
  const queryClient = useQueryClient();
  const { datasets, datasetsConfig } = useAppContext();
  const { addNotification } = useNotifications();

  // Modal states
  const [showIndexModal, setShowIndexModal] = useState(false);
  const [showStatsModal, setShowStatsModal] = useState(false);

  // Index management states
  const [selectedDataset, setSelectedDataset] = useState<string>("");
  const [destinationPath, setDestinationPath] = useState<string>("");
  const [defaultDestination, setDefaultDestination] = useState<string>("");
  const [batchSize, setBatchSize] = useState<number>(100);

  const datasetNames = datasets
    ? datasets.map((p: string) => p.split("/").pop()).filter(Boolean)
    : [];

  // Auto-populate destination path from dataset configuration when a dataset is selected
  useEffect(() => {
    if (!selectedDataset || !datasetsConfig) return;

    const cfg = datasetsConfig[selectedDataset];
    if (!cfg) return;

    const tryFindDestination = (obj: any): string | undefined => {
      if (!obj || typeof obj !== "object") return undefined;
      if (typeof obj.destination === "string" && obj.destination.trim()) {
        return obj.destination as string;
      }
      for (const value of Object.values(obj)) {
        if (
          value &&
          typeof value === "object" &&
          "destination" in value &&
          typeof (value as any).destination === "string"
        ) {
          if ((value as any).destination.trim()) return (value as any).destination as string;
        }
      }
      return undefined;
    };

    const destination = tryFindDestination(cfg);
    if (destination) {
      setDefaultDestination(destination);
      if (!destinationPath || destinationPath === defaultDestination) {
        setDestinationPath(destination);
      }
    }
  }, [selectedDataset, datasetsConfig]);

  // Get index count for selected dataset
  const {
    data: indexCount,
    isLoading: isLoadingCount,
    refetch: refetchCount,
  } = useQuery<IndexCount>({
    queryKey: ["index-count", selectedDataset],
    queryFn: async () =>
      selectedDataset
        ? get("/maintenance/index/count", { dataset: selectedDataset })
        : { count: 0 },
    enabled: !!selectedDataset && showIndexModal,
  });

  // Get duplicate stats
  const {
    data: stats,
    isLoading: isLoadingStats,
    refetch: refetchStats,
  } = useQuery<IndexStats>({
    queryKey: ["index-stats"],
    queryFn: async () => get("/maintenance/index/stats"),
    enabled: showStatsModal,
  });

  // Index destination mutation
  const indexMutation = useMutation({
    mutationFn: async ({
      dataset,
      destination,
      reindex,
    }: {
      dataset: string;
      destination: string;
      reindex: boolean;
    }) =>
      post("/maintenance/index/destination", {
        dataset,
        destination,
        reindex,
        batchSize,
      }),
    onSuccess: (data) => {
      setShowIndexModal(false);
      toast.success(
        `✅ Indexed: ${data.indexed}, Skipped: ${data.skipped}, Errors: ${data.errors}`
      );
      addNotification({
        type: "success",
        title: "Indexing Complete",
        message: `Indexed ${data.indexed} files, skipped ${data.skipped}, errors ${data.errors}`,
      });
      refetchCount();
      refetchStats();
      queryClient.invalidateQueries({ queryKey: ["all-files"] });
      queryClient.invalidateQueries({ queryKey: ["files"] });
    },
    onError: (err: any) => {
      console.error(err);
      toast.error("Failed to index destination");
      addNotification({
        type: "error",
        title: "Indexing Failed",
        message: err.message || "Failed to index destination",
      });
    },
  });

  // Clear index mutation
  const clearMutation = useMutation({
    mutationFn: async (dataset: string) => del(`/maintenance/index/${dataset}`),
    onSuccess: (data) => {
      setShowIndexModal(false);
      toast.success(`🗑️ Cleared ${data.cleared} index entries`);
      addNotification({
        type: "success",
        title: "Index Cleared",
        message: `Cleared ${data.cleared} index entries from ${selectedDataset}`,
      });
      refetchCount();
      refetchStats();
      queryClient.invalidateQueries({ queryKey: ["all-files"] });
      queryClient.invalidateQueries({ queryKey: ["files"] });
    },
    onError: (err: any) => {
      console.error(err);
      toast.error("Failed to clear index");
      addNotification({
        type: "error",
        title: "Clear Index Failed",
        message: err.message || "Failed to clear index",
      });
    },
  });

  const handleIndex = (reindex: boolean) => {
    if (!selectedDataset) {
      toast.error("Please select a dataset");
      return;
    }
    if (!destinationPath) {
      toast.error("Please enter a destination path");
      return;
    }

    toast.loading(
      `Starting ${reindex ? "re-index" : "index"} of ${selectedDataset}...`,
      { duration: 2000 }
    );
    addNotification({
      type: "info",
      title: "Indexing Started",
      message: `${reindex ? "Re-indexing" : "Indexing"} ${selectedDataset} at ${destinationPath}`,
    });

    indexMutation.mutate({
      dataset: selectedDataset,
      destination: destinationPath,
      reindex,
    });
  };

  const handleClear = () => {
    if (!selectedDataset) {
      toast.error("Please select a dataset");
      return;
    }

    if (confirm(`Clear all index entries for ${selectedDataset}?`)) {
      clearMutation.mutate(selectedDataset);
    }
  };

  const {
    data: duplicateGroups,
    isLoading,
    error,
    refetch,
  } = useQuery<DuplicateGroup[]>({
    queryKey: ["duplicate-files"],
    queryFn: async () => get("/maintenance/duplicates?status=pending"),
  });

  const [enabledDatasets, setEnabledDatasets] = useState<Set<string>>(
    new Set()
  );
  const [searchTerm, setSearchTerm] = useState("");
  const [sortField, setSortField] = useState<SortField>("count");
  const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
  const [expandedRows, setExpandedRows] = useState<Set<number>>(new Set());
  const [selectedGroups, setSelectedGroups] = useState<Set<number>>(new Set());
  const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
  const [deleteSelection, setDeleteSelection] = useState<DeleteSelection>({
    isOpen: false,
    groups: [],
  });

  // Initialize enabled datasets on first load
  useEffect(() => {
    if (datasets && datasets.length > 0 && enabledDatasets.size === 0) {
      const names = datasets
        .map((p: string) => p.split("/").pop())
        .filter(Boolean) as string[];
      setEnabledDatasets(new Set(names));
    }
  }, [datasets]);

  // Listen for indexing events from WebSocket
  useEffect(() => {
    const handleMaintenanceUpdate = (event: CustomEvent) => {
      const data = event.detail;
      if (data.type === "indexing_progress") {
        // Show progress toast
        toast.loading(
          `Indexing ${data.dataset}: ${data.progress}% (${data.indexed} files)`,
          { id: `indexing-${data.dataset}` }
        );
      } else if (data.type === "indexing_complete") {
        // Dismiss progress toast and show success
        toast.dismiss(`indexing-${data.dataset}`);
        toast.success(
          `Indexing complete: ${data.indexed} indexed, ${data.skipped} skipped, ${data.errors} errors`
        );
        addNotification({
          type: "success",
          title: "Indexing Complete",
          message: `${data.dataset}: ${data.indexed} files indexed`,
        });
        refetchCount();
        refetchStats();
        queryClient.invalidateQueries({ queryKey: ["all-files"] });
        queryClient.invalidateQueries({ queryKey: ["files"] });
      } else if (data.type === "indexing_error") {
        toast.dismiss(`indexing-${data.dataset}`);
        toast.error(`Indexing failed: ${data.error}`);
        addNotification({
          type: "error",
          title: "Indexing Failed",
          message: `${data.dataset}: ${data.error}`,
        });
      }
    };

    window.addEventListener(
      "maintenanceUpdate",
      handleMaintenanceUpdate as EventListener
    );

    return () => {
      window.removeEventListener(
        "maintenanceUpdate",
        handleMaintenanceUpdate as EventListener
      );
    };
  }, [queryClient, refetchCount, refetchStats, addNotification]);

  const [scanController, setScanController] = useState<AbortController | null>(
    null
  );

  const scanMutation = useMutation({
    mutationFn: ({ signal }: { signal?: AbortSignal } = {}) =>
      post("/maintenance/duplicates/scan", { resetExisting: false }, signal),
    onSuccess: () => {
      toast.success("Duplicate scan completed");
      addNotification({
        type: "info",
        title: "Scan Finished",
        message: "Duplicate scan completed. Refreshing results.",
      });
      refetch();
    },
    onError: (err: any) => {
      if (err.message === "Request aborted") return;
      console.error(err);
      toast.error("Failed to start duplicate scan");
    },
    onSettled: () => setScanController(null),
  });

  const markNotDuplicateMutation = useMutation({
    mutationFn: async (ids: number[]) => {
      await Promise.all(
        ids.map((id) =>
          post(`/maintenance/duplicates/${id}/mark`, {
            status: "reviewed",
            note: "not_duplicate",
          })
        )
      );
    },
    onSuccess: () => {
      toast.success("Marked as not duplicate");
      setSelectedGroups(new Set());
      queryClient.invalidateQueries({ queryKey: ["duplicate-files"] });
    },
    onError: (err: any) => {
      console.error(err);
      toast.error("Failed to update duplicates");
    },
  });

  const deleteMutation = useMutation({
    mutationFn: async (groups: Array<{ id: number; files: string[] }>) => {
      if (!groups.length) return;
      await Promise.all(
        groups.map((group) =>
          post(`/maintenance/duplicates/${group.id}/purge`, {
            files: group.files,
          })
        )
      );
    },
    onSuccess: () => {
      toast.success("Selected files deleted");
      setSelectedFiles(new Set());
      setSelectedGroups(new Set());
      setDeleteSelection({ isOpen: false, groups: [] });
      queryClient.invalidateQueries({ queryKey: ["duplicate-files"] });
    },
    onError: (err: any) => {
      console.error(err);
      toast.error("Failed to delete selected files");
    },
  });

  const filteredData = useMemo(() => {
    if (!duplicateGroups) return [] as DuplicateGroup[];

    return duplicateGroups
      .filter((group) => enabledDatasets.has(group.dataset))
      .filter((group) => {
        if (!searchTerm) return true;
        const term = searchTerm.toLowerCase();
        const inFiles = group.files.some((f) => f.toLowerCase().includes(term));
        const inDest = group.destination?.toLowerCase().includes(term);
        return inFiles || inDest || group.hash.toLowerCase().includes(term);
      })
      .sort((a, b) => {
        let aVal: number | string = 0;
        let bVal: number | string = 0;

        switch (sortField) {
          case "dataset":
            aVal = a.dataset;
            bVal = b.dataset;
            break;
          case "count":
            aVal = a.files.length;
            bVal = b.files.length;
            break;
          case "size":
            aVal = a.size;
            bVal = b.size;
            break;
          case "created_at":
            aVal = a.created_at || "";
            bVal = b.created_at || "";
            break;
        }

        if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
        if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
        return 0;
      });
  }, [duplicateGroups, enabledDatasets, searchTerm, sortField, sortDirection]);

  const toggleDataset = (dataset: string) => {
    const next = new Set(enabledDatasets);
    if (next.has(dataset)) next.delete(dataset);
    else next.add(dataset);
    setEnabledDatasets(next);
  };

  const toggleExpanded = (id: number) => {
    const next = new Set(expandedRows);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setExpandedRows(next);
  };

  const toggleGroupSelection = (id: number) => {
    const next = new Set(selectedGroups);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    setSelectedGroups(next);
  };

  const toggleFileSelection = (groupId: number, filePath: string) => {
    const key = makeFileKey(groupId, filePath);
    const next = new Set(selectedFiles);
    if (next.has(key)) next.delete(key);
    else next.add(key);
    setSelectedFiles(next);
  };

  const selectGroupFiles = (group: DuplicateGroup, checked: boolean) => {
    const next = new Set(selectedFiles);
    for (const file of group.files) {
      const key = makeFileKey(group.id, file);
      if (checked) next.add(key);
      else next.delete(key);
    }
    setSelectedFiles(next);
  };

  const handleBatchDelete = () => {
    if (!duplicateGroups) return;
    const grouped = new Map<number, string[]>();

    selectedFiles.forEach((key) => {
      const { groupId, path } = parseFileKey(key);
      const arr = grouped.get(groupId) || [];
      arr.push(path);
      grouped.set(groupId, arr);
    });

    if (grouped.size === 0) return;

    const groups = Array.from(grouped.entries()).map(([id, files]) => ({
      id,
      files,
    }));

    setDeleteSelection({ isOpen: true, groups });
  };

  const confirmDelete = () => {
    deleteMutation.mutate(deleteSelection.groups);
  };

  const markSelectedNotDuplicate = () => {
    if (selectedGroups.size === 0) return;
    markNotDuplicateMutation.mutate(Array.from(selectedGroups));
  };

  if (isLoading && !duplicateGroups) {
    return <LoadingCard message="Loading duplicate files..." />;
  }

  if (error) {
    return (
      <div className="text-center p-8 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
        <h2 className="text-lg font-semibold text-red-800 dark:text-red-200 mb-2">
          Failed to load duplicates
        </h2>
        <p className="text-gray-600 dark:text-gray-400 mb-4">
          There was an error loading duplicate file data.
        </p>
        <button
          onClick={() =>
            queryClient.refetchQueries({ queryKey: ["duplicate-files"] })
          }
          className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700"
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <div className="space-y-6">
      <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
        <div>
          <h3 className="text-lg font-semibold text-gray-900 dark:text-white">
            Duplicate Files
          </h3>
          <p className="text-sm text-gray-600 dark:text-gray-400">
            Review detected duplicates, delete unwanted files, or mark them as
            safe.
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          <div className="relative group">
            <button
              onClick={() => setShowIndexModal(true)}
              className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-purple-100 dark:hover:bg-purple-900/30 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
              title="Manage Index"
            >
              <FolderIcon className="h-5 w-5" />
            </button>
            <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
              Manage Index
            </div>
          </div>

          <div className="relative group">
            <button
              onClick={() => setShowStatsModal(true)}
              className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-amber-100 dark:hover:bg-amber-900/30 hover:text-amber-600 dark:hover:text-amber-400 transition-colors"
              title="Duplicate Statistics"
            >
              <ChartBarIcon className="h-5 w-5" />
            </button>
            <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
              Duplicate Statistics
            </div>
          </div>

          <div className="relative group">
            <button
              onClick={() => {
                addNotification({
                  type: "info",
                  title: "Scan Started",
                  message:
                    "Scanning for duplicates in /Volumes/Shares/.Private",
                });
                const controller = new AbortController();
                setScanController(controller);
                scanMutation.mutate({ signal: controller.signal });
              }}
              disabled={scanMutation.isPending}
              className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-blue-100 dark:hover:bg-blue-900/30 hover:text-blue-600 dark:hover:text-blue-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
              title="Rescan for duplicates"
            >
              <ArrowPathIcon
                className={`h-5 w-5 ${scanMutation.isPending ? "animate-spin" : ""}`}
              />
            </button>
            <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
              {scanMutation.isPending ? "Scanning..." : "Rescan"}
            </div>
          </div>

          {scanController && (
            <div className="relative group">
              <button
                onClick={() => {
                  scanController.abort();
                  setScanController(null);
                }}
                className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
                title="Cancel scan"
              >
                <XMarkIcon className="h-5 w-5" />
              </button>
              <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
                Cancel Scan
              </div>
            </div>
          )}

          <div className="relative group">
            <button
              onClick={markSelectedNotDuplicate}
              disabled={selectedGroups.size === 0}
              className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-emerald-100 dark:hover:bg-emerald-900/30 hover:text-emerald-600 dark:hover:text-emerald-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
              title="Mark selected as not duplicate"
            >
              <CheckCircleIcon className="h-5 w-5" />
            </button>
            <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
              Mark Not Duplicate
            </div>
          </div>

          <div className="relative group">
            <button
              onClick={handleBatchDelete}
              disabled={selectedFiles.size === 0}
              className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
              title="Delete selected files"
            >
              <TrashIcon className="h-5 w-5" />
            </button>
            <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
              Delete Selected Files
            </div>
          </div>
        </div>
      </div>

      <div className="rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-4 shadow-sm space-y-4">
        <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
          <div className="flex items-center gap-3">
            <Squares2X2Icon className="h-5 w-5 text-gray-500" />
            <div>
              <div className="text-sm text-gray-700 dark:text-gray-300">
                Showing {filteredData.length} group
                {filteredData.length === 1 ? "" : "s"}
              </div>
              <div className="text-xs text-gray-500 dark:text-gray-400">
                {selectedFiles.size} file{selectedFiles.size === 1 ? "" : "s"}{" "}
                selected
              </div>
            </div>
          </div>
          <div className="flex flex-wrap gap-3">
            <input
              type="text"
              placeholder="Search destination or file path..."
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              className="w-64 min-w-[200px] rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
            <select
              className="rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
              value={sortField}
              onChange={(e) => setSortField(e.target.value as SortField)}
            >
              <option value="count">Most files</option>
              <option value="size">Largest size</option>
              <option value="dataset">Dataset</option>
              <option value="created_at">Newest</option>
            </select>
            <button
              onClick={() =>
                setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"))
              }
              className="rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700"
            >
              {sortDirection === "asc" ? "Asc" : "Desc"}
            </button>
          </div>
        </div>

        <div className="flex flex-wrap gap-2 items-center">
          <span className="text-sm text-gray-600 dark:text-gray-400">
            Datasets:
          </span>
          {datasets?.map((ds: string) => {
            const name = ds.split("/").pop();
            if (!name) return null;
            return (
              <label
                key={name}
                className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300"
              >
                <input
                  type="checkbox"
                  checked={enabledDatasets.has(name)}
                  onChange={() => toggleDataset(name)}
                  className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
                />
                {name}
              </label>
            );
          })}
        </div>

        <div className="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
          <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
            <thead className="bg-gray-50 dark:bg-gray-800">
              <tr>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Group
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Dataset
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Destination
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Files
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Size
                </th>
                <th className="px-3 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Hash
                </th>
                <th className="px-3 py-3 text-right text-xs font-semibold text-gray-700 dark:text-gray-200">
                  Actions
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-gray-200 dark:divide-gray-800">
              {filteredData.length === 0 && (
                <tr>
                  <td
                    colSpan={7}
                    className="px-3 py-4 text-center text-sm text-gray-500 dark:text-gray-400"
                  >
                    No duplicate groups match the current filters.
                  </td>
                </tr>
              )}
              {filteredData.map((group) => {
                const isExpanded = expandedRows.has(group.id);
                const allSelected = group.files.every((f) =>
                  selectedFiles.has(makeFileKey(group.id, f))
                );
                return (
                  <>
                    <tr
                      key={group.id}
                      className="hover:bg-gray-50 dark:hover:bg-gray-800"
                    >
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-200 whitespace-nowrap">
                        <button
                          onClick={() => toggleExpanded(group.id)}
                          className="mr-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
                        >
                          {isExpanded ? "−" : "+"}
                        </button>
                        <input
                          type="checkbox"
                          checked={selectedGroups.has(group.id)}
                          onChange={() => toggleGroupSelection(group.id)}
                          className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
                        />
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">
                        <span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded text-xs font-medium">
                          {group.dataset}
                        </span>
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-300 max-w-xs">
                        <div className="truncate" title={group.destination}>
                          {group.destination}
                        </div>
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
                        {group.files.length}
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
                        {formatBytes(group.size)}
                      </td>
                      <td className="px-3 py-3 text-sm text-gray-500 dark:text-gray-400">
                        {group.hash.slice(0, 10)}…
                      </td>
                      <td className="px-3 py-3 text-sm text-right whitespace-nowrap">
                        <div className="inline-flex items-center gap-1">
                          <button
                            onClick={() =>
                              markNotDuplicateMutation.mutate([group.id])
                            }
                            className="inline-flex items-center gap-1 rounded-md border border-gray-300 dark:border-gray-700 px-3 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800"
                          >
                            <EyeSlashIcon className="h-4 w-4" /> Ignore
                          </button>
                          <button
                            onClick={() =>
                              selectGroupFiles(group, !allSelected)
                            }
                            className="inline-flex items-center gap-1 rounded-md border border-gray-300 dark:border-gray-700 px-3 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800"
                          >
                            {allSelected ? "Unselect files" : "Select files"}
                          </button>
                        </div>
                      </td>
                    </tr>
                    {isExpanded && (
                      <tr
                        key={`${group.id}-expanded`}
                        className="bg-gray-50 dark:bg-gray-800/60"
                      >
                        <td colSpan={7} className="px-3 py-3">
                          <div className="flex items-center justify-between mb-2">
                            <div className="text-sm font-medium text-gray-800 dark:text-gray-100">
                              Files in group
                            </div>
                            <div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
                              <span>{selectedFiles.size} selected</span>
                              <button
                                onClick={() => selectGroupFiles(group, true)}
                                className="rounded-md border border-gray-300 dark:border-gray-700 px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700"
                              >
                                Select all
                              </button>
                              <button
                                onClick={() => selectGroupFiles(group, false)}
                                className="rounded-md border border-gray-300 dark:border-gray-700 px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700"
                              >
                                Clear
                              </button>
                            </div>
                          </div>
                          <div className="space-y-2 max-h-64 overflow-y-auto pr-1">
                            {group.files.map((file) => {
                              const key = makeFileKey(group.id, file);
                              return (
                                <label
                                  key={key}
                                  className="flex items-center gap-3 rounded-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-800 dark:text-gray-200"
                                >
                                  <input
                                    type="checkbox"
                                    checked={selectedFiles.has(key)}
                                    onChange={() =>
                                      toggleFileSelection(group.id, file)
                                    }
                                    className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
                                  />
                                  <span className="truncate" title={file}>
                                    {file}
                                  </span>
                                </label>
                              );
                            })}
                          </div>
                        </td>
                      </tr>
                    )}
                  </>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      <ConfirmationDialog
        isOpen={deleteSelection.isOpen}
        title="Delete selected files"
        message="These files will be removed from disk. This action cannot be undone."
        confirmText="Delete"
        cancelText="Cancel"
        type="danger"
        isLoading={deleteMutation.isPending}
        onConfirm={confirmDelete}
        onClose={() => setDeleteSelection({ isOpen: false, groups: [] })}
      />

      {/* Index Management Slide-over */}
      {showIndexModal && (
        <div className="fixed inset-0 z-50 overflow-hidden">
          <div className="absolute inset-0 overflow-hidden">
            <div
              className="absolute inset-0 bg-gray-500 bg-opacity-75 dark:bg-gray-900 dark:bg-opacity-75 transition-opacity"
              onClick={() => setShowIndexModal(false)}
            />
            <div className="fixed inset-y-0 right-0 pl-10 max-w-full flex">
              <div className="w-screen max-w-md bg-white dark:bg-gray-800 shadow-xl transform transition-all animate-slide-in-right">
                <div className="h-full flex flex-col overflow-y-scroll">
                  {/* Header */}
                  <div className="bg-white dark:bg-gray-800 px-4 py-6 sm:px-6">
                    <div className="flex items-center justify-between">
                      <h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
                        Index Management
                      </h2>
                      <button
                        onClick={() => setShowIndexModal(false)}
                        className="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 transition-colors"
                      >
                        <XMarkIcon className="h-6 w-6" />
                      </button>
                    </div>
                  </div>

                  {/* Content */}
                  <div className="flex-1 px-4 py-6 sm:px-6 space-y-6">
                    <div>
                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                        Dataset
                      </label>
                      <select
                        value={selectedDataset}
                        onChange={(e) => setSelectedDataset(e.target.value)}
                        className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100"
                      >
                        <option value="">Select a dataset...</option>
                        {datasetNames.map((name) => (
                          <option key={name} value={name}>
                            {name}
                          </option>
                        ))}
                      </select>
                    </div>

                    <div>
                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                        Destination Path
                      </label>
                      <PathAutocomplete
                        value={destinationPath}
                        onChange={setDestinationPath}
                        placeholder="/path/to/destination"
                        defaultPath={defaultDestination}
                        className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100"
                      />
                    </div>

                    <div>
                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                        Batch Size
                      </label>
                      <input
                        type="number"
                        value={batchSize}
                        onChange={(e) => setBatchSize(parseInt(e.target.value))}
                        min="10"
                        max="1000"
                        className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100"
                      />
                      <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                        Number of files to process at once
                      </p>
                    </div>

                    {selectedDataset && !isLoadingCount && (
                      <div className="flex justify-between items-center p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
                        <span className="text-sm font-medium text-gray-700 dark:text-gray-300">
                          Indexed Files
                        </span>
                        <span className="text-2xl font-bold text-blue-600 dark:text-blue-400">
                          {indexCount?.count || 0}
                        </span>
                      </div>
                    )}
                  </div>

                  {/* Footer */}
                  <div className="bg-gray-50 dark:bg-gray-700 px-4 py-4 sm:px-6 space-y-3">
                    <button
                      onClick={() => handleIndex(false)}
                      disabled={indexMutation.isPending}
                      className="w-full inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
                    >
                      <FolderIcon className="h-5 w-5 mr-2" />
                      Index
                    </button>

                    <button
                      onClick={() => handleIndex(true)}
                      disabled={indexMutation.isPending}
                      className="w-full inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
                    >
                      <ArrowPathIcon className="h-5 w-5 mr-2" />
                      Re-index
                    </button>

                    <button
                      onClick={handleClear}
                      disabled={clearMutation.isPending || !selectedDataset}
                      className="w-full inline-flex items-center justify-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
                    >
                      <TrashIcon className="h-5 w-5 mr-2" />
                      Clear Index
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}

      {/* Duplicate Statistics Slide-over */}
      {showStatsModal && (
        <div className="fixed inset-0 z-50 overflow-hidden">
          <div className="absolute inset-0 overflow-hidden">
            <div
              className="absolute inset-0 bg-gray-500 bg-opacity-75 dark:bg-gray-900 dark:bg-opacity-75 transition-opacity"
              onClick={() => setShowStatsModal(false)}
            />
            <div className="fixed inset-y-0 right-0 pl-10 max-w-full flex">
              <div className="w-screen max-w-md bg-white dark:bg-gray-800 shadow-xl transform transition-all animate-slide-in-right">
                <div className="h-full flex flex-col overflow-y-scroll">
                  {/* Header */}
                  <div className="bg-white dark:bg-gray-800 px-4 py-6 sm:px-6">
                    <div className="flex items-center justify-between">
                      <h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100 flex items-center">
                        <ChartBarIcon className="h-5 w-5 mr-2" />
                        Duplicate Statistics
                      </h2>
                      <button
                        onClick={() => setShowStatsModal(false)}
                        className="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 transition-colors"
                      >
                        <XMarkIcon className="h-6 w-6" />
                      </button>
                    </div>
                  </div>

                  {/* Content */}
                  <div className="flex-1 px-4 py-6 sm:px-6">
                    {isLoadingStats ? (
                      <LoadingCard message="Loading duplicate stats..." />
                    ) : stats && stats.totalDuplicates > 0 ? (
                      <div className="space-y-4">
                        <div className="flex justify-between items-center p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
                          <span className="text-sm font-medium text-yellow-800 dark:text-yellow-300">
                            Total Duplicate Groups
                          </span>
                          <span className="text-2xl font-bold text-yellow-600 dark:text-yellow-400">
                            {stats.totalDuplicates}
                          </span>
                        </div>

                        <div className="space-y-3 max-h-96 overflow-y-auto pr-2">
                          {stats.duplicatesByDataset
                            .slice(0, 10)
                            .map((dup, idx) => (
                              <div
                                key={idx}
                                className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600"
                              >
                                <div className="flex justify-between items-start mb-2">
                                  <span className="text-sm font-medium text-gray-900 dark:text-gray-100">
                                    [{dup.dataset}] {dup.file_count} files
                                  </span>
                                  <span className="text-sm text-gray-500 dark:text-gray-400">
                                    {formatBytes(dup.file_size)}
                                  </span>
                                </div>
                                <div className="text-xs text-gray-500 dark:text-gray-400 font-mono mb-2">
                                  Hash: {dup.hash.substring(0, 32)}...
                                </div>
                                <div className="space-y-1">
                                  {dup.files.map((file, fileIdx) => (
                                    <div
                                      key={fileIdx}
                                      className="text-xs text-gray-600 dark:text-gray-400 truncate"
                                      title={file}
                                    >
                                      • {file}
                                    </div>
                                  ))}
                                </div>
                              </div>
                            ))}
                        </div>

                        {stats.duplicatesByDataset.length > 10 && (
                          <p className="text-sm text-gray-500 dark:text-gray-400 text-center">
                            ... and {stats.duplicatesByDataset.length - 10} more
                            duplicate groups
                          </p>
                        )}
                      </div>
                    ) : (
                      <div className="text-center py-8 text-gray-500 dark:text-gray-400">
                        <p>No duplicates found in indexed files</p>
                      </div>
                    )}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
