export function formatBytes(bytes: number, decimals = 1): string {
  if (!+bytes) return '0 B';
  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
}

/**
 * Combine a backup's date and id into a friendly composite identifier,
 * e.g. "05-20-2026-bk_beM6uQh1re".
 */
export function backupSlug(date: string | null | undefined, id: string): string {
  if (!date) return id;
  const d = new Date(date);
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const dd = String(d.getDate()).padStart(2, '0');
  const yyyy = d.getFullYear();
  return `${mm}-${dd}-${yyyy}-${id}`;
}

/**
 * Locale-aware date+time formatter for backup tables/popovers.
 * Drops seconds — minute-level precision is enough for users, and trimming
 * the trailing ":NN" reduces visual noise across the table.
 *
 * Example output (en-US):  "5/22/2026, 1:23 AM"
 */
export function formatDateTime(input: string | null | undefined): string {
  if (!input) return '-';
  const d = new Date(input);
  if (isNaN(d.getTime())) return '-';
  return d.toLocaleString(undefined, {
    year:   'numeric',
    month:  'numeric',
    day:    'numeric',
    hour:   'numeric',
    minute: '2-digit',
  });
}

/**
 * Human-friendly duration. Seconds stay seconds until a minute, then roll up:
 *   45      → "45s"
 *   879     → "14m 39s"
 *   3725    → "1h 2m"
 * Zero / missing → "—".
 */
export function formatDuration(seconds: number | null | undefined): string {
  const s = Math.floor(Number(seconds) || 0);
  if (s <= 0) return '-';
  if (s < 60) return `${s}s`;
  if (s < 3600) {
    const m = Math.floor(s / 60);
    const rem = s % 60;
    return rem ? `${m}m ${rem}s` : `${m}m`;
  }
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  return m ? `${h}h ${m}m` : `${h}h`;
}

/**
 * The destination IDs that ACTUALLY hold a complete copy of this backup —
 * i.e. every part lists the id in its `uploaded_to` array.
 *
 * `backup.destinations[]` records INTENT (where the engine was asked to put
 * copies) — but a destination that was paused or disconnected during the
 * run never received any parts, even though its id is still in there. Using
 * the intent array for UI badges or the restore-source picker shows phantom
 * copies that don't exist on disk; using this helper shows REALITY.
 *
 * Legacy single-zip backups (files[] empty) fall back to the intent array.
 */
export function backupActualDestinations(backup: {
  destinations: string[];
  files?: Array<{ uploaded_to?: string[] }>;
}): string[] {
  const files = backup.files ?? [];
  if (files.length === 0) return backup.destinations;
  return backup.destinations.filter((id) =>
    files.every((f) => (f.uploaded_to ?? []).includes(id)),
  );
}

export function relTime(iso: string): string {
  const d = new Date(iso).getTime();
  const diff = Math.round((Date.now() - d) / 1000);
  if (diff < 60) return `${diff}s ago`;
  if (diff < 3600) return `${Math.round(diff / 60)}m ago`;
  if (diff < 86400) return `${Math.round(diff / 3600)}h ago`;
  return `${Math.round(diff / 86400)}d ago`;
}
