import { memo } from "react";

export type UnitDisplay = NonNullable<Intl.NumberFormatOptions["unitDisplay"]>;

export interface DurationProps {
	seconds: number;
	display: UnitDisplay;
	maxDigits?: number;
}

export default memo(Duration);

type Unit =
	| "year"
	| "month"
	| "week"
	| "day"
	| "hour"
	| "minute"
	| "second"
	| "millisecond";

/**
 * Constants will be folded by the bundler
 *
 * Values taken from:
 * https://tc39.es/proposal-unified-intl-numberformat/section6/locales-currencies-tz_proposed_out.html#sec-issanctionedsimpleunitidentifier
 *
 * TODO: Maybe use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat one day
 */
const durationFormatters = {
	year: 1000 * 60 * 60 * 24 * 365,
	month: 1000 * 60 * 60 * 24 * 30,
	week: 1000 * 60 * 60 * 24 * 7,
	day: 1000 * 60 * 60 * 24,
	hour: 1000 * 60 * 60,
	minute: 1000 * 60,
	second: 1000,
	millisecond: 1,
} satisfies Record<Unit, number>;

function Duration({ seconds, display, maxDigits }: DurationProps) {
	// <time> can also be used for durations:
	// https://stackoverflow.com/a/31591343
	return (
		<time dateTime={`${seconds}s`}>
			{formatDuration(seconds, display, maxDigits ?? 0)}
		</time>
	);
}

function formatDuration(
	seconds: number,
	unitDisplay: UnitDisplay,
	maxDigits: number,
) {
	const ms = seconds * 1000;

	for (const [unit, thresholdStr] of Object.entries(durationFormatters)) {
		const threshold = Number(thresholdStr);
		if (ms >= threshold) {
			return getFormatter(unit as Unit, unitDisplay, maxDigits).format(
				ms / threshold,
			);
		}
	}
	return getFormatter("second", unitDisplay, maxDigits).format(ms);
}

const formatterCache: Partial<
	Record<
		UnitDisplay,
		Partial<Record<Unit, Record<number, Intl.NumberFormat>>>
	>
> = {};

function getFormatter(
	unit: Unit,
	unitDisplay: UnitDisplay,
	maxDigits: number,
): Intl.NumberFormat {
	// biome-ignore lint/suspicious/noAssignInExpressions: Ok here
	const fu = (formatterCache[unitDisplay] ??= {});
	// biome-ignore lint/suspicious/noAssignInExpressions: Ok here
	const fun = (fu[unit] ??= {});
	// biome-ignore lint/suspicious/noAssignInExpressions: Ok here
	return (fun[maxDigits] ??= new Intl.NumberFormat("de-DE", {
		style: "unit",
		unit,
		unitDisplay,
		maximumFractionDigits: maxDigits,
	}));
}
