import type { ReactNode } from 'react';
import type { Torrent } from './shared';
import type { SortKey } from './useTorrentFilters';
import { Lock } from 'lucide-react';
import {
  formatDate,
  formatDuration,
  formatEta,
  formatPercent,
  formatRatio,
  formatSize,
  formatSpeed,
  PRIORITY_LABELS,
  statusLabel,
  statusTone,
  TONE_CLASSES,
} from './format';

// Column catalogue for the torrent table. Mirrors the reference client's set, minus the columns that only
// exist there because its canvas renderer needed them split (uploadedDownloaded, the separate seeds cell).
//
// Widths are fixed px rather than fractions: the table scrolls horizontally and is virtualised vertically,
// so a row has to be laid out without measuring, and a percentage-width grid inside a horizontal scroller
// collapses to the viewport instead of the content.

export type ColumnKey =
  | 'name'
  | 'status'
  | 'percentDone'
  | 'totalSize'
  | 'sizeWhenDone'
  | 'leftUntilDone'
  | 'haveValid'
  | 'downloadedEver'
  | 'uploadedEver'
  | 'rateDownload'
  | 'rateUpload'
  | 'eta'
  | 'uploadRatio'
  | 'peersSendingToUs'
  | 'peersGettingFromUs'
  | 'addedDate'
  | 'doneDate'
  | 'activityDate'
  | 'secondsSeeding'
  | 'downloadDir'
  | 'officerTrackerHost'
  | 'officerTrackerStatus'
  | 'bandwidthPriority'
  | 'queuePosition'
  | 'labels'
  | 'isPrivate'
  | 'group'
  | 'file-count'
  | 'pieceCount'
  | 'id';

export type ColumnDef = {
  key: ColumnKey;
  label: string;
  width: number;
  align?: 'right' | 'center';
  /** Absent when the column has no meaningful order (nothing here, currently — kept for honesty). */
  sortKey?: SortKey;
  render: (t: Torrent) => ReactNode;
};

const num = (v: number | undefined) => (v == null ? '—' : v.toLocaleString());
/** A count of zero is noise in a dense table — the absence of peers is already visible as a blank. */
const positive = (v: number | undefined) => (v ? v.toLocaleString() : '');

const ProgressCell = ({ torrent }: { torrent: Torrent }) => {
  const tone = TONE_CLASSES[statusTone(torrent)];
  // Verifying reports its own progress in recheckProgress; percentDone stays where it was, so a recheck of
  // a complete torrent would otherwise render a full bar while the daemon is 3% into the hash check.
  const isChecking = torrent.recheckProgress != null && torrent.recheckProgress > 0 && torrent.percentDone < 1;
  const fraction = isChecking ? torrent.recheckProgress! : torrent.percentDone;

  return (
    <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 ${tone.dot}`} style={{ width: `${Math.min(100, fraction * 100)}%` }} />
      </div>
      <span className="w-11 shrink-0 text-right tabular-nums text-muted-foreground">{formatPercent(fraction)}</span>
    </div>
  );
};

export const ALL_COLUMNS: ColumnDef[] = [
  {
    key: 'name',
    label: 'Name',
    width: 340,
    sortKey: 'name',
    render: (t) => (
      <span className="flex min-w-0 items-center gap-1.5">
        {t.isPrivate && <Lock className="h-3 w-3 shrink-0 text-amber-500" aria-label="Private torrent" />}
        <span className="truncate" title={t.name}>
          {t.name}
        </span>
      </span>
    ),
  },
  {
    key: 'status',
    label: 'Status',
    width: 130,
    sortKey: 'status',
    render: (t) => {
      const tone = TONE_CLASSES[statusTone(t)];
      return (
        <span className={`inline-flex items-center gap-1.5 ${tone.text}`} title={t.errorString || undefined}>
          <span className={`h-1.5 w-1.5 shrink-0 rounded-full ${tone.dot}`} />
          <span className="truncate">{statusLabel(t)}</span>
        </span>
      );
    },
  },
  {
    key: 'percentDone',
    label: 'Progress',
    width: 150,
    sortKey: 'percentDone',
    render: (t) => <ProgressCell torrent={t} />,
  },
  {
    key: 'totalSize',
    label: 'Size',
    width: 90,
    align: 'right',
    sortKey: 'totalSize',
    render: (t) => formatSize(t.totalSize),
  },
  {
    key: 'sizeWhenDone',
    label: 'Size when done',
    width: 120,
    align: 'right',
    render: (t) => formatSize(t.sizeWhenDone),
  },
  { key: 'leftUntilDone', label: 'Remaining', width: 100, align: 'right', render: (t) => formatSize(t.leftUntilDone) },
  { key: 'haveValid', label: 'Have', width: 90, align: 'right', render: (t) => formatSize(t.haveValid) },
  {
    key: 'downloadedEver',
    label: 'Downloaded',
    width: 105,
    align: 'right',
    sortKey: 'downloadedEver',
    render: (t) => formatSize(t.downloadedEver),
  },
  {
    key: 'uploadedEver',
    label: 'Uploaded',
    width: 100,
    align: 'right',
    sortKey: 'uploadedEver',
    render: (t) => formatSize(t.uploadedEver),
  },
  {
    key: 'rateDownload',
    label: 'Down',
    width: 95,
    align: 'right',
    sortKey: 'rateDownload',
    render: (t) => <span className="text-blue-500">{formatSpeed(t.rateDownload)}</span>,
  },
  {
    key: 'rateUpload',
    label: 'Up',
    width: 95,
    align: 'right',
    sortKey: 'rateUpload',
    render: (t) => <span className="text-emerald-500">{formatSpeed(t.rateUpload)}</span>,
  },
  {
    key: 'eta',
    label: 'ETA',
    width: 85,
    align: 'right',
    sortKey: 'eta',
    // ETA is meaningless once a torrent is done — Transmission keeps returning the sentinel and the column
    // would read "∞" down the whole seeding half of the list.
    render: (t) => (t.percentDone >= 1 ? '' : formatEta(t.eta)),
  },
  {
    key: 'uploadRatio',
    label: 'Ratio',
    width: 75,
    align: 'right',
    sortKey: 'uploadRatio',
    render: (t) => formatRatio(t.uploadRatio),
  },
  {
    key: 'peersSendingToUs',
    label: 'Seeds',
    width: 70,
    align: 'right',
    sortKey: 'peersSendingToUs',
    render: (t) => positive(t.peersSendingToUs),
  },
  {
    key: 'peersGettingFromUs',
    label: 'Peers',
    width: 70,
    align: 'right',
    sortKey: 'peersGettingFromUs',
    render: (t) => positive(t.peersGettingFromUs),
  },
  { key: 'addedDate', label: 'Added', width: 145, sortKey: 'addedDate', render: (t) => formatDate(t.addedDate) },
  { key: 'doneDate', label: 'Completed', width: 145, sortKey: 'doneDate', render: (t) => formatDate(t.doneDate) },
  {
    key: 'activityDate',
    label: 'Last active',
    width: 145,
    sortKey: 'activityDate',
    render: (t) => formatDate(t.activityDate),
  },
  {
    key: 'secondsSeeding',
    label: 'Seeding time',
    width: 110,
    align: 'right',
    sortKey: 'secondsSeeding',
    render: (t) => formatDuration(t.secondsSeeding),
  },
  {
    key: 'downloadDir',
    label: 'Location',
    width: 220,
    sortKey: 'downloadDir',
    render: (t) => (
      <span className="truncate" title={t.downloadDir}>
        {t.downloadDir}
      </span>
    ),
  },
  {
    key: 'officerTrackerHost',
    label: 'Tracker',
    width: 150,
    sortKey: 'officerTrackerHost',
    render: (t) => t.officerTrackerHost || '—',
  },
  {
    key: 'officerTrackerStatus',
    label: 'Tracker status',
    width: 190,
    render: (t) => (
      <span className={t.officerTrackerErrors > 0 ? 'text-amber-500' : undefined} title={t.officerTrackerStatus}>
        <span className="truncate">{t.officerTrackerStatus || '—'}</span>
      </span>
    ),
  },
  {
    key: 'bandwidthPriority',
    label: 'Priority',
    width: 85,
    render: (t) => PRIORITY_LABELS[t.bandwidthPriority] ?? '—',
  },
  {
    key: 'queuePosition',
    label: 'Queue',
    width: 70,
    align: 'right',
    sortKey: 'queuePosition',
    render: (t) => num(t.queuePosition),
  },
  {
    key: 'labels',
    label: 'Labels',
    width: 160,
    render: (t) =>
      t.labels.length ? (
        <span className="flex gap-1 overflow-hidden">
          {t.labels.map((l) => (
            <span key={l} className="shrink-0 rounded bg-muted px-1.5 py-px text-[10px] leading-4">
              {l}
            </span>
          ))}
        </span>
      ) : (
        ''
      ),
  },
  { key: 'isPrivate', label: 'Private', width: 70, align: 'center', render: (t) => (t.isPrivate ? 'Yes' : 'No') },
  { key: 'group', label: 'Group', width: 100, render: (t) => t.group || '' },
  { key: 'file-count', label: 'Files', width: 70, align: 'right', render: (t) => num(t['file-count']) },
  { key: 'pieceCount', label: 'Pieces', width: 80, align: 'right', render: (t) => num(t.pieceCount) },
  { key: 'id', label: 'ID', width: 60, align: 'right', sortKey: 'id', render: (t) => String(t.id) },
];

export const COLUMN_BY_KEY = new Map(ALL_COLUMNS.map((c) => [c.key, c]));

/** What a fresh install shows. Everything else is one right-click on the header away. */
export const DEFAULT_VISIBLE_COLUMNS: ColumnKey[] = [
  'name',
  'status',
  'percentDone',
  'totalSize',
  'rateDownload',
  'rateUpload',
  'eta',
  'uploadRatio',
  'peersSendingToUs',
  'peersGettingFromUs',
  'addedDate',
];

/** The name column is the only thing identifying a row, so it is not hideable. */
export const PINNED_COLUMN: ColumnKey = 'name';
