/** Display formatting. Numbers are shown compactly; time as broadcast timecode. */
export function compact(n) {
const value = Number(n) || 0;
if (value < 1000) return String(value);
if (value < 1_000_000) {
const k = value / 1000;
return `${k < 10 ? k.toFixed(1).replace(/\.0$/, '') : Math.round(k)}K`;
}
const m = value / 1_000_000;
return `${m < 10 ? m.toFixed(1).replace(/\.0$/, '') : Math.round(m)}M`;
}
export const full = (n) => (Number(n) || 0).toLocaleString();
/** 74 -> "1:14", 3675 -> "1:01:15" */
export function timecode(seconds) {
const total = Math.max(0, Math.round(Number(seconds) || 0));
const s = total % 60;
const m = Math.floor(total / 60) % 60;
const hrs = Math.floor(total / 3600);
const pad = (x) => String(x).padStart(2, '0');
return hrs ? `${hrs}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
}
const UNITS = [
['year', 31536000],
['month', 2592000],
['week', 604800],
['day', 86400],
['hour', 3600],
['minute', 60],
];
/** SQLite stores UTC without a zone marker; normalise before parsing. */
export function parseDate(value) {
if (value instanceof Date) return value;
const text = String(value ?? '').trim();
const iso = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(text)
? `${text.replace(' ', 'T')}Z`
: text;
const date = new Date(iso);
return Number.isNaN(date.getTime()) ? new Date() : date;
}
export function timeAgo(value) {
const seconds = Math.max(0, (Date.now() - parseDate(value).getTime()) / 1000);
if (seconds < 45) return 'just now';
for (const [unit, size] of UNITS) {
if (seconds >= size) {
const n = Math.floor(seconds / size);
return `${n} ${unit}${n === 1 ? '' : 's'} ago`;
}
}
return 'just now';
}
export const longDate = (value) =>
parseDate(value).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
export function fileSize(bytes) {
const n = Number(bytes) || 0;
if (n < 1024) return `${n} B`;
if (n < 1024 ** 2) return `${(n / 1024).toFixed(0)} KB`;
if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`;
return `${(n / 1024 ** 3).toFixed(2)} GB`;
}
export const initials = (name = '') =>
name.trim().split(/\s+/).slice(0, 2).map((w) => w[0] ?? '').join('') || '?';
export const plural = (n, word, suffix = 's') => `${compact(n)} ${word}${n === 1 ? '' : suffix}`;