import type { Torrent } from '../shared';
import { useMemo, useState } from 'react';
import { ChevronRight, File, Folder, Pencil } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { PRIORITY } from '../shared';
import { formatPercent, formatSize, PRIORITY_LABELS } from '../format';
import { useTorrentMutations } from '../useTransmissionData';
import { RenameDialog } from '../dialogs/RenameDialog';

// Files, as a tree, because a torrent of a TV season is thirty files in three folders and a flat list of
// thirty identical-looking paths is unusable.
//
// Transmission addresses files by their INDEX in the `files` array, not by path — so every node carries the
// indices beneath it, and a folder operation is just the same RPC with more indices.

type FileNode = {
  key: string;
  name: string;
  /** Leaf files only; a directory's own index list is the union of its descendants'. */
  index?: number;
  children: FileNode[];
  indices: number[];
  size: number;
  completed: number;
};

function buildTree(torrent: Torrent): FileNode {
  const root: FileNode = { key: '', name: '', children: [], indices: [], size: 0, completed: 0 };
  const files = torrent.files ?? [];

  files.forEach((file, index) => {
    const segments = file.name.split('/').filter(Boolean);
    let node = root;
    segments.forEach((segment, depth) => {
      const isLeaf = depth === segments.length - 1;
      const key = segments.slice(0, depth + 1).join('/');
      let child = node.children.find((c) => c.key === key);
      if (!child) {
        child = { key, name: segment, children: [], indices: [], size: 0, completed: 0 };
        node.children.push(child);
      }
      if (isLeaf) child.index = index;
      node = child;
    });
  });

  // Roll sizes and indices up in one post-order pass, so a folder row can show its own progress bar.
  const roll = (node: FileNode): FileNode => {
    if (node.index != null) {
      const file = files[node.index]!;
      node.size = file.length;
      node.completed = file.bytesCompleted;
      node.indices = [node.index];
      return node;
    }
    node.children.forEach(roll);
    node.size = node.children.reduce((s, c) => s + c.size, 0);
    node.completed = node.children.reduce((s, c) => s + c.completed, 0);
    node.indices = node.children.flatMap((c) => c.indices);
    // Folders before files, then alphabetical — the ordering every file manager uses.
    node.children.sort((a, b) => Number(!!a.index) - Number(!!b.index) || a.name.localeCompare(b.name));
    return node;
  };

  return roll(root);
}

type FilesTabProps = { torrent: Torrent };

export const FilesTab = ({ torrent }: FilesTabProps) => {
  const { set } = useTorrentMutations();
  const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
  const [renaming, setRenaming] = useState<string | null>(null);

  const tree = useMemo(() => buildTree(torrent), [torrent.files, torrent.id]);
  const stats = torrent.fileStats ?? [];

  if (!torrent.files?.length) {
    return (
      <p className="p-6 text-center text-sm text-muted-foreground">
        {torrent.metadataPercentComplete < 1
          ? 'Still fetching metadata — the file list appears once the magnet resolves.'
          : 'This torrent has no files.'}
      </p>
    );
  }

  const wantedCount = stats.filter((s) => s.wanted).length;
  const wantedBytes = torrent.files.reduce((sum, f, i) => sum + (stats[i]?.wanted === false ? 0 : f.length), 0);

  const setWanted = (indices: number[], wanted: boolean) =>
    set.mutate({ ids: [torrent.id], [wanted ? 'files-wanted' : 'files-unwanted']: indices });

  const setPriority = (indices: number[], priority: number) => {
    const field =
      priority === PRIORITY.High ? 'priority-high' : priority === PRIORITY.Low ? 'priority-low' : 'priority-normal';
    set.mutate({ ids: [torrent.id], [field]: indices });
  };

  const toggleCollapsed = (key: string) =>
    setCollapsed((prev) => {
      const next = new Set(prev);
      if (next.has(key)) next.delete(key);
      else next.add(key);
      return next;
    });

  const renderNode = (node: FileNode, depth: number): React.ReactNode[] => {
    const isDir = node.index == null;
    const isCollapsed = collapsed.has(node.key);
    const nodeStats = node.indices.map((i) => stats[i]).filter(Boolean);
    const allWanted = nodeStats.length > 0 && nodeStats.every((s) => s!.wanted);
    const someWanted = nodeStats.some((s) => s!.wanted);
    const priorities = new Set(nodeStats.map((s) => s!.priority));
    const priority = priorities.size === 1 ? [...priorities][0]! : null;
    const fraction = node.size > 0 ? node.completed / node.size : 0;

    const row = (
      <div
        key={node.key}
        className="grid grid-cols-[minmax(0,1fr)_90px_120px_90px_28px] items-center gap-2 border-b border-border/40 px-3 py-1.5 text-xs hover:bg-muted/40"
      >
        <div className="flex min-w-0 items-center gap-1.5" style={{ paddingLeft: depth * 14 }}>
          {isDir ? (
            <button type="button" onClick={() => toggleCollapsed(node.key)} className="shrink-0">
              <ChevronRight className={`h-3.5 w-3.5 transition-transform ${isCollapsed ? '' : 'rotate-90'}`} />
            </button>
          ) : (
            <span className="w-3.5 shrink-0" />
          )}
          <Checkbox
            checked={allWanted ? true : someWanted ? 'indeterminate' : false}
            onCheckedChange={() => setWanted(node.indices, !allWanted)}
            className="shrink-0"
          />
          {isDir ? (
            <Folder className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
          ) : (
            <File className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
          )}
          <span className={`truncate ${someWanted ? '' : 'text-muted-foreground line-through'}`} title={node.key}>
            {node.name}
          </span>
        </div>

        <span className="text-right tabular-nums text-muted-foreground">{formatSize(node.size)}</span>

        <div className="flex items-center gap-2">
          <div className="h-1.5 min-w-0 flex-1 overflow-hidden rounded-full bg-muted">
            <div className="h-full rounded-full bg-blue-500" style={{ width: `${fraction * 100}%` }} />
          </div>
          <span className="w-10 shrink-0 text-right tabular-nums text-muted-foreground">{formatPercent(fraction)}</span>
        </div>

        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <button type="button" className="text-left text-muted-foreground hover:text-foreground">
              {priority == null ? 'mixed' : PRIORITY_LABELS[priority]}
            </button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuItem onSelect={() => setPriority(node.indices, PRIORITY.High)}>High</DropdownMenuItem>
            <DropdownMenuItem onSelect={() => setPriority(node.indices, PRIORITY.Normal)}>Normal</DropdownMenuItem>
            <DropdownMenuItem onSelect={() => setPriority(node.indices, PRIORITY.Low)}>Low</DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>

        <button
          type="button"
          onClick={() => setRenaming(node.key)}
          className="text-muted-foreground hover:text-foreground"
          aria-label={`Rename ${node.name}`}
        >
          <Pencil className="h-3 w-3" />
        </button>
      </div>
    );

    if (isDir && !isCollapsed) {
      return [row, ...node.children.flatMap((child) => renderNode(child, depth + 1))];
    }
    return [row];
  };

  return (
    <div className="flex h-full flex-col">
      <div className="flex items-center gap-3 border-b border-border px-3 py-2 text-xs text-muted-foreground">
        <span>
          {wantedCount} of {torrent.files.length} files · {formatSize(wantedBytes)} wanted
        </span>
        <span className="flex-1" />
        <Button
          size="sm"
          variant="ghost"
          onClick={() =>
            setWanted(
              torrent.files!.map((_, i) => i),
              true,
            )
          }
        >
          Select all
        </Button>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button size="sm" variant="ghost">
              Set all priorities
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            {[PRIORITY.High, PRIORITY.Normal, PRIORITY.Low].map((p) => (
              <DropdownMenuItem
                key={p}
                onSelect={() =>
                  setPriority(
                    torrent.files!.map((_, i) => i),
                    p,
                  )
                }
              >
                {PRIORITY_LABELS[p]}
              </DropdownMenuItem>
            ))}
          </DropdownMenuContent>
        </DropdownMenu>
      </div>

      <div className="min-h-0 flex-1 overflow-auto">{tree.children.flatMap((child) => renderNode(child, 0))}</div>

      <RenameDialog
        open={renaming != null}
        onOpenChange={(open) => !open && setRenaming(null)}
        torrent={torrent}
        path={renaming ?? ''}
      />
    </div>
  );
};
