import type { Torrent } from './shared';
import { STATUS } from './shared';

// Display formatting for the Transmission panels. One implementation each, because a torrent list shows the
// same quantity in a column, a tooltip and a detail row, and three near-identical formatters drift.

const SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];

/** Binary (1024-based) sizes, matching what Transmission's own clients show. */
export function formatSize(bytes: number | undefined | null): string {
  if (bytes == null || !Number.isFinite(bytes)) return '—';
  if (bytes <= 0) return '0 B';
  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), SIZE_UNITS.length - 1);
  const value = bytes / 1024 ** i;
  return `${value.toFixed(i === 0 ? 0 : value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${SIZE_UNITS[i]}`;
}

/** Zero renders blank, not "0 B/s" — a list where every idle row shouts zero is unreadable. */
export function formatSpeed(bytesPerSecond: number | undefined | null): string {
  if (!bytesPerSecond || bytesPerSecond <= 0) return '';
  return `${formatSize(bytesPerSecond)}/s`;
}

/**
 * Transmission's ETA sentinels: -1 means "not available", -2 means "unknown". Both are ordinary states for a
 * stopped or seeding torrent, so neither is an error worth surfacing.
 */
export function formatEta(seconds: number | undefined | null): string {
  if (seconds == null || seconds < 0) return '∞';
  return formatDuration(seconds);
}

export function formatDuration(seconds: number | undefined | null): string {
  if (seconds == null || !Number.isFinite(seconds) || seconds < 0) return '—';
  if (seconds < 60) return `${Math.round(seconds)}s`;

  const d = Math.floor(seconds / 86400);
  const h = Math.floor((seconds % 86400) / 3600);
  const m = Math.floor((seconds % 3600) / 60);

  if (d > 0) return h > 0 ? `${d}d ${h}h` : `${d}d`;
  if (h > 0) return m > 0 ? `${h}h ${m}m` : `${h}h`;
  return `${m}m`;
}

/**
 * Ratio, with the two conventions every torrent client shares: nothing uploaded yet reads as a dash, and
 * anything uploaded against a zero download is infinite rather than a division blow-up.
 */
export function formatRatio(ratio: number | undefined | null): string {
  if (ratio == null) return '—';
  if (ratio < 0) return '∞';
  return ratio.toFixed(2);
}

/** Transmission timestamps are unix seconds, and 0 means "never" rather than 1970. */
export function formatDate(unixSeconds: number | undefined | null): string {
  if (!unixSeconds || unixSeconds <= 0) return '—';
  const d = new Date(unixSeconds * 1000);
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

/**
 * Progress, floored — never rounded. `toFixed` rounds, so 99.97% printed as "100.0%" next to a bar that
 * is still blue, which reads as a stuck torrent rather than a nearly-finished one. Only an actually
 * complete fraction may show a hundred, and it shows it as a flat "100%" so the two can't be confused.
 */
export function formatPercent(fraction: number | undefined | null): string {
  if (fraction == null) return '—';
  if (fraction >= 1) return '100%';
  return `${(Math.floor(fraction * 1000) / 10).toFixed(1)}%`;
}

// ── Status ────────────────────────────────────────────────────────────────────────────────────────

export type StatusTone = 'stopped' | 'downloading' | 'seeding' | 'checking' | 'queued' | 'error';

export function statusTone(torrent: Torrent): StatusTone {
  if (torrent.error !== 0) return 'error';
  switch (torrent.status) {
    case STATUS.Download:
      return 'downloading';
    case STATUS.Seed:
      return 'seeding';
    case STATUS.Check:
    case STATUS.CheckWait:
      return 'checking';
    case STATUS.DownloadWait:
    case STATUS.SeedWait:
      return 'queued';
    default:
      return 'stopped';
  }
}

export function statusLabel(torrent: Torrent): string {
  if (torrent.error !== 0) return 'Error';
  switch (torrent.status) {
    case STATUS.Stopped:
      // A stopped torrent that never finished is paused; one that did is simply done and not seeding.
      return torrent.percentDone >= 1 ? 'Finished' : 'Paused';
    case STATUS.CheckWait:
      return 'Queued to verify';
    case STATUS.Check:
      return 'Verifying';
    case STATUS.DownloadWait:
      return 'Queued to download';
    case STATUS.Download:
      // Transmission reports "downloading" while it is still fetching the metadata of a magnet link, which
      // is a materially different state — there is nothing to download yet.
      return torrent.metadataPercentComplete < 1 ? 'Fetching metadata' : 'Downloading';
    case STATUS.SeedWait:
      return 'Queued to seed';
    case STATUS.Seed:
      return 'Seeding';
    default:
      return 'Unknown';
  }
}

/** Tailwind classes per tone, kept together so the list, the pill and the detail header cannot disagree. */
export const TONE_CLASSES: Record<StatusTone, { text: string; bg: string; dot: string }> = {
  stopped: { text: 'text-muted-foreground', bg: 'bg-muted', dot: 'bg-muted-foreground' },
  downloading: { text: 'text-blue-500', bg: 'bg-blue-500/10', dot: 'bg-blue-500' },
  seeding: { text: 'text-emerald-500', bg: 'bg-emerald-500/10', dot: 'bg-emerald-500' },
  checking: { text: 'text-cyan-500', bg: 'bg-cyan-500/10', dot: 'bg-cyan-500' },
  queued: { text: 'text-amber-500', bg: 'bg-amber-500/10', dot: 'bg-amber-500' },
  error: { text: 'text-destructive', bg: 'bg-destructive/10', dot: 'bg-destructive' },
};

export const PRIORITY_LABELS: Record<number, string> = { [-1]: 'Low', 0: 'Normal', 1: 'High' };

/** Peer flag characters, spelled out for the peers table tooltip (rpc-spec.md §3.2). */
export const PEER_FLAGS: Record<string, string> = {
  O: 'Optimistic unchoke',
  D: 'Downloading from peer',
  d: 'We would download but peer is choking us',
  U: 'Uploading to peer',
  u: 'Peer would download but we are choking',
  K: 'Peer unchoked us but we are not interested',
  '?': 'We unchoked peer but it is not interested',
  E: 'Encrypted connection',
  H: 'Peer discovered through DHT',
  X: 'Peer discovered through PEX',
  I: 'Peer is incoming',
  T: 'Peer is over uTP',
};

export function describePeerFlags(flagStr: string): string {
  return (
    flagStr
      .split('')
      .map((c) => PEER_FLAGS[c])
      .filter(Boolean)
      .join('\n') || flagStr
  );
}
