export type Kind = "idle" | "working" | "verify" | "done" | "attention"; // Worker Job.state -> a literal, legible label + severity kind, collapsed to three // honest phases: Searching -> Downloading -> Finishing. The record-label metaphor // stays in the chrome; these labels never obscure what's happening. const STATE_MAP: Record = { requested: { label: "Queued", kind: "idle" }, matching: { label: "Searching", kind: "working" }, matched: { label: "Searching", kind: "working" }, downloading: { label: "Downloading", kind: "working" }, tagging: { label: "Finishing", kind: "verify" }, imported: { label: "Pressed", kind: "done" }, needs_attention: { label: "Needs attention", kind: "attention" }, }; /** Compact elapsed label from an ISO/Date string to `now` (ms since epoch). */ export function timeAgo(value: string | Date | null | undefined, now: number): string { if (!value) return ""; const t = new Date(value).getTime(); if (Number.isNaN(t)) return ""; const s = Math.max(0, Math.floor((now - t) / 1000)); if (s < 60) return "just now"; const m = Math.floor(s / 60); if (m < 60) return `${m}m`; const h = Math.floor(m / 60); if (h < 24) return `${h}h`; return `${Math.floor(h / 24)}d`; } /** Compact download ETA like "~4m left", "~45s left", "~1h 5m left". Null/≤0 → no label. */ export function etaLabel(seconds: number | null | undefined): string | null { if (seconds == null || seconds <= 0) return null; if (seconds < 60) return `~${seconds}s left`; const m = Math.round(seconds / 60); if (m < 60) return `~${m}m left`; const h = Math.floor(m / 60); return `~${h}h ${m % 60}m left`; } export function describeJob(state: string, stage: string): { label: string; kind: Kind } { void stage; // stage no longer drives the label; kept for call-site compatibility return ( STATE_MAP[state] ?? { label: state ? state[0].toUpperCase() + state.slice(1) : "Unknown", kind: "idle", } ); }