import { memo, useCallback, useMemo, useState } from "react";
import useInterval from "./useInterval";

export type DateTimeCountdownProps =
	| PlaceholderDateTimeCountdownProps
	| HiddenDateTimeCountdownProps
	| ContinueDateTimeCountdownProps;

export interface BaseDateTimeCountdownProps {
	/**
	 * Server-sent Timestamp that was fetched from the backend. **Unix time in seconds**.
	 * @remarks We use a `number` instead of `Date` because `Date`s are objects and would cause unnecessary re-renders. `number`s are easier to memoize and to compare.
	 */
	targetTimestamp: number;
	/**
	 * What to prefix the countdown with when it is not zero.
	 */
	prefix?: string;
	trimLeadingZeros?: boolean;
}

export interface PlaceholderDateTimeCountdownProps
	extends BaseDateTimeCountdownProps {
	onZero: "placeholder";
	placeholder: string;
}

export interface HiddenDateTimeCountdownProps
	extends BaseDateTimeCountdownProps {
	onZero: "hide";
}

export interface ContinueDateTimeCountdownProps
	extends BaseDateTimeCountdownProps {
	onZero: "continue";
}

const nativeDurationFormat =
	// @ts-expect-error: Remove this suppression when TS ships with
	typeof Intl.DurationFormat === "function" // @ts-expect-error: Remove this suppression when TS ships with Intl.DurationFormat
		? new Intl.DurationFormat(["de", "en"], { style: "narrow" })
		: undefined;

const absoluteDateFormatter = new Intl.DateTimeFormat(["de", "en"], {
	dateStyle: "long",
});

function formatTimeRemaining(
	timeLeftSeconds: number,
	trimLeadingZeros: boolean,
): string {
	const days = (timeLeftSeconds / (60 * 60 * 24)) | 0;
	const hours = ((timeLeftSeconds % (60 * 60 * 24)) / (60 * 60)) | 0;
	const minutes = ((timeLeftSeconds % (60 * 60)) / 60) | 0;
	const seconds = (timeLeftSeconds % 60) | 0;

	// Docs:
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat
	const toFormat: Record<string, number | undefined> = {
		days,
		hours,
		minutes,
		seconds,
	};

	if (trimLeadingZeros) {
		toFormat.days = days === 0 ? undefined : days;
		toFormat.hours =
			hours === 0 && toFormat.days === undefined ? undefined : hours;
		toFormat.minutes =
			minutes === 0 && toFormat.hours === undefined ? undefined : minutes;
	}

	if (!nativeDurationFormat) {
		if (trimLeadingZeros) {
			let res = "";
			if (toFormat.days !== undefined) {
				res += `${toFormat.days}d `;
			}
			if (toFormat.hours !== undefined && toFormat.days !== undefined) {
				res += `${toFormat.hours}h `;
			}
			if (
				toFormat.minutes !== undefined &&
				toFormat.hours !== undefined
			) {
				res += `${toFormat.minutes}m `;
			}
			res += `${toFormat.seconds}s`; // seconds are always shown
			return res;
		}

		return `${days}d ${hours}h ${minutes}m ${seconds}s`;
	}

	return nativeDurationFormat.format(toFormat);
}

export default memo(function DateTimeCountdown(props: DateTimeCountdownProps) {
	const now = (Date.now() / 1000) | 0;
	const targetTimestamp = props.targetTimestamp;

	const [secondsRemaining, setSecondsRemaining] = useState<number>(
		targetTimestamp - now,
	);

	const cb = useCallback(() => {
		setSecondsRemaining((targetTimestamp - Date.now() / 1000) | 0);
	}, [targetTimestamp]);

	useInterval(cb, 1000);

	if (secondsRemaining <= 0) {
		switch (props.onZero) {
			case "placeholder":
				return props.placeholder;
			case "hide":
				return undefined;
			case "continue":
				// continue to return below to show the countdown even when it reaches zero
				break;
		}
	}

	const [isoString, userReadable] = useMemo(() => {
		const targetDateTime = new Date(targetTimestamp * 1000);
		return [
			targetDateTime.toISOString(),
			absoluteDateFormatter.format(targetDateTime),
		];
	}, [targetTimestamp]);

	const formatted = (
		<time dateTime={isoString} title={`Countdown bis zum ${userReadable}`}>
			{formatTimeRemaining(secondsRemaining, !!props.trimLeadingZeros)}
		</time>
	);

	return props.prefix ? (
		<>
			{props.prefix}
			{formatted}
		</>
	) : (
		formatted
	);
});
