<script lang="ts">
	import Label from './Label.svelte';
	import { Country, type ICountry } from 'country-state-city';
	import ExpandableCombobox, { type ComboboxItem } from './Combobox.svelte';
	import StyledRawInput from './StyledRawInput.svelte';
	import { AsYouType, type PhoneNumber, type CountryCode } from 'libphonenumber-js';

	let {
		name,
		value = $bindable<PhoneNumber | undefined>(undefined),
		number = $bindable(''), // consider making this private, it should only be used internally
		country = $bindable<ICountry | undefined>(), // consider making this private, it should only be used internally
		defaultISO,
		required = false,
		label,
		placeholder = 'Phone number',
		invalidMessage
	}: {
		name: string;
		value?: PhoneNumber;
		number?: string;
		country?: ICountry;
		defaultISO?: string;
		required?: boolean;
		label?: string;
		placeholder?: string;
		invalidMessage?: string;
	} = $props();

	let selectedCountryItem = $state<ComboboxItem | undefined>();
	let countriesOpen = $state<boolean>(false);
	let countriesValid = $state<boolean>(true);
	let numberValid = $state<boolean>(true);

	const countries = Country.getAllCountries();
	const countrycodeMap = countries.reduce(
		(acc, country) => {
			acc[country.isoCode] = country;
			return acc;
		},
		{} as Record<string, ICountry>
	);
	const options: ComboboxItem[] = countries.map((country) => ({
		value: country.isoCode,
		label: `${country.name} (+${country.phonecode})`,
		preview: `+${country.phonecode}`,
		icon: renderIcon
	}));

	let phonecode: string = $derived.by(() => {
		if (country) {
			return countrycodeMap[country.isoCode]?.phonecode ?? '';
		}
		return '';
	});

	// setCountryByISO sets the country to match a given ISO code
	export const setCountryByISO = (iso: string) => {
		if (iso in countrycodeMap) {
			countriesOpen = false;
			country = countrycodeMap[iso];
			selectedCountryItem = options.find((item) => item.value === country?.isoCode);
		}
	};

	// set the default country based on the provided ISO code
	if (defaultISO) {
		setCountryByISO(defaultISO);
	}

	// if country is still blank (probably invalid defaultISO), set it to the first country
	if (!country) {
		country = countries[0];

		if (defaultISO)
			console.warn(`PhoneInput: Invalid defaultISO "${defaultISO}", defaulting to ${country.name}`);
	}

	selectedCountryItem = options.find((item) => item.value === country?.isoCode);

	let formatterCountryCode: CountryCode | undefined = $derived.by(() => {
		if (country) return country.isoCode as CountryCode;
		return undefined;
	});
</script>

{#snippet renderIcon(item: ComboboxItem)}
	{#if countrycodeMap[item.value]?.flag}
		{countrycodeMap[item.value].flag}
	{/if}
{/snippet}

<div>
	{#if label}
		<Label for={name}>{label}</Label>
	{/if}

	<!-- Hidden input stores international E.164 formatted number -->
	<input type="hidden" {name} value={value?.number ?? ''} />

	<div class="flex gap-2">
		<div
			class={['transition-width', countriesOpen && 'w-full!']}
			style="width: {phonecode.length * 1.5 + 12.5}ch;"
		>
			<ExpandableCombobox
				name="{name}_country"
				items={options}
				bind:value={selectedCountryItem}
				bind:open={countriesOpen}
				{required}
				onchange={(e) => {
					country = countrycodeMap[e.value.value];
				}}
				onvalidate={(e) => {
					countriesValid = e.detail.valid;
				}}
			/>
		</div>

		<div class="w-full">
			<StyledRawInput
				type="tel"
				{placeholder}
				name="{name}_number"
				bind:value={number}
				validate={{
					required: required,
					func: () => !required || (value !== undefined && value.isValid())
				}}
				onvalidate={(e) => {
					numberValid = e.detail.valid;
				}}
				onkeydown={(e) => {
					if (e.ctrlKey || e.key.length > 1) {
						return;
					}

					if (/[0-9-()+]/.test(e.key)) {
						return;
					}

					e.preventDefault();
				}}
				oninput={(event: Event) => {
					const e = event as InputEvent;
					if (!e.target) return;
					const input = e.target as HTMLInputElement;

					const formatter = new AsYouType(formatterCountryCode);
					const formatted = formatter.input(input.value);

					if (formatted.length >= input.value.length) input.value = formatted;
					value = formatter.getNumber();
					console.log('updated value', value, value?.isValid());

					if (formatter.isValid() && formatter.isInternational() && value) {
						const country = formatter.getCountry();
						if (country) {
							setCountryByISO(country);
							input.value = value.formatNational();
						}
					}
				}}
			/>
		</div>
	</div>

	<div class={['opacity-0 transition-opacity', (!countriesValid || !numberValid) && 'opacity-100']}>
		<Label for={name} error>
			{invalidMessage !== undefined && invalidMessage != '' ? invalidMessage : 'Field is required'}
		</Label>
	</div>
</div>
