<script lang="ts" module>
	const getTimeZonePart = (
		timeZone: string,
		timeZoneName: Intl.DateTimeFormatOptions['timeZoneName']
	) => {
		return new Intl.DateTimeFormat('en', {
			timeZone,
			timeZoneName
		})
			.formatToParts()
			.find((part) => part.type === 'timeZoneName')?.value;
	};

	// wbr takes a string and adds a <wbr> tag after each forward slash
	const wbr = (str: string): string => {
		return str.replace(/\//g, '/<wbr />');
	};

	/**
	 * Details about a timezone.
	 */
	export type TimezoneDetail = {
		timeZone: string;
		offset?: string;
		long?: string;
		short?: string;
	};

	/**
	 * A sorted list of timezones with details.
	 */
	export const sortedTimeZones: TimezoneDetail[] = Intl.supportedValuesOf('timeZone')
		.map((timeZone) => {
			// get short offset (e.g. GMT+1) for the timezone
			const offset = getTimeZonePart(timeZone, 'shortOffset');
			// if (!offset) return null;

			// get long representation (e.g. Pacific Standard Time) for the timezone
			const long = getTimeZonePart(timeZone, 'long');

			// get short representation (e.g. PST) for the timezone
			const short = getTimeZonePart(timeZone, 'short');

			return {
				timeZone,
				offset,
				long,
				short
			};
		})
		.filter((timeZone) => timeZone !== null)
		.sort((a, b) => {
			return a.timeZone.localeCompare(b.timeZone);
		});

	/**
	 * A map of timezone names to details.
	 */
	export const timezoneMap: Record<string, TimezoneDetail> = sortedTimeZones.reduce(
		(acc, timeZone) => {
			acc[timeZone.timeZone] = timeZone;
			return acc;
		},
		{} as Record<string, TimezoneDetail>
	);
</script>

<script lang="ts">
	import type { ClassValue } from 'svelte/elements';
	import Combobox, { type ComboboxOption } from './Combobox.svelte';

	interface Props {
		label?: string;
		name?: string;
		value?: string;
		invalidMessage?: string;
		required?: boolean;
		class?: ClassValue | null | undefined;
	}

	let {
		label,
		name,
		value = $bindable(''),
		invalidMessage = 'Please select a timezone',
		required,
		class: classValue
	}: Props = $props();

	const options: ComboboxOption[] = sortedTimeZones.map((timeZone) => {
		const infotext = [...new Set([timeZone.short, timeZone.offset])]
			.filter((item) => item !== undefined)
			.join(' · ');

		return {
			value: timeZone.timeZone,
			label: `${timeZone.timeZone.replaceAll('_', ' ')}`,
			preview: timeZone.long,
			infotext: infotext,
			render: timezoneLabel
		};
	});
	const optionsMap = options.reduce(
		(acc, option) => {
			acc[option.value] = option;
			return acc;
		},
		{} as Record<string, ComboboxOption>
	);

	let timezone = $state<ComboboxOption | undefined>(
		optionsMap[Intl.DateTimeFormat().resolvedOptions().timeZone]
	);
</script>

<Combobox
	{label}
	{name}
	{invalidMessage}
	{required}
	bind:value={timezone}
	{options}
	pickerWidth="match"
	placeholder="Select a timezone"
	class={classValue}
/>

{#snippet timezoneLabel(item: ComboboxOption)}
	<!-- eslint-disable svelte/no-at-html-tags -->
	{@html wbr(item.label ?? 'Missing label')}
{/snippet}
