/** Unix seconds → localized date-time. `locale`/`timeZone` exist for deterministic tests. */
export function formatTime(unixSeconds: number, locale?: string, timeZone?: string): string {
	return new Intl.DateTimeFormat(locale, {
		dateStyle: "medium",
		timeStyle: "medium",
		timeZone,
	}).format(new Date(unixSeconds * 1000));
}

/**
 * Unix seconds → the time of day alone, "14:05". For a stamp the reader places
 * against now — a pause that ends shortly, the last row that was dropped —
 * where the date would be noise on every reading but one.
 */
export function formatClock(unixSeconds: number, locale?: string, timeZone?: string): string {
	return new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit", timeZone }).format(
		new Date(unixSeconds * 1000),
	);
}

const BYTE_UNITS = ["KiB", "MiB", "GiB", "TiB"] as const;

export function formatBytes(bytes: number): string {
	if (bytes < 1024) return `${bytes} B`;
	let value = bytes;
	let unit: string = BYTE_UNITS[0];
	for (const next of BYTE_UNITS) {
		unit = next;
		value /= 1024;
		if (value < 1024) break;
	}
	return `${value.toFixed(1)} ${unit}`;
}

const AGE_UNITS = [
	{ seconds: 86400, suffix: "d" },
	{ seconds: 3600, suffix: "h" },
	{ seconds: 60, suffix: "m" },
] as const;

/**
 * Seconds of elapsed time → a coarse "3h". Truncating and single-unit on
 * purpose, for a span the caller labels itself, as in "active for 3h". A
 * negative span reads "0s": clock skew is not a duration.
 */
export function formatDuration(seconds: number): string {
	for (const unit of AGE_UNITS) {
		if (seconds >= unit.seconds) return `${Math.floor(seconds / unit.seconds)}${unit.suffix}`;
	}
	return `${Math.max(0, Math.floor(seconds))}s`;
}

/** Microseconds → milliseconds with one decimal, e.g. 1234 → "1.2 ms". */
export function formatMicros(micros: number): string {
	return `${(micros / 1000).toFixed(1)} ms`;
}
