<script lang="ts">
	import { isValueValid, validate, type ValidatorOptions } from '@svelte-toolkit/validate';
	import Label from './Label.svelte';

	let {
		label,
		length,
		required,
		name,
		value = $bindable(''),
		oncomplete,
		onchange
	}: {
		label?: string;
		length: number;
		required?: boolean;
		name: string;
		value?: string;
		oncomplete?: (value: string) => void;
		onchange?: (value: string) => void;
	} = $props();

	let hiddenInput: HTMLInputElement;
	let valid: boolean = $state(true);

	const validateOpts: ValidatorOptions = {
		required: required,
		length: length,
		autovalOnInvalid: true
	};

	const inputs: HTMLInputElement[] = $state([]);

	// firstEmptyInputIndex returns the index of the first empty input in the pin input
	const firstEmptyInputIndex = () => {
		for (let i = 0; i < inputs.length; i++) {
			if (inputs[i].value === '') {
				return i;
			}
		}

		return inputs.length - 1;
	};

	// joinValue joins the values of the pin input into a single string
	const joinValue = () => {
		return inputs.map((input) => input.value).join('');
	};

	// updateHiddenInput updates the hidden input value with the current value of the pin input
	const updateHiddenInput = () => {
		console.log('updating hidden');
		hiddenInput.value = value;
		hiddenInput.dispatchEvent(new KeyboardEvent('keyup'));
	};

	// onfocusinput selects all the text in an input when it is focused
	const onfocusinput = (index: number) => {
		return () => inputs[index].select();
	};

	// onmousedown triggers focus, forces selection of text and prevents focus on blank inputs
	const onmousedown = (index: number) => {
		return (e: MouseEvent) => {
			e.preventDefault();

			let node = inputs[index];
			if (node.value === '') node = inputs[firstEmptyInputIndex()];

			node.focus();
			node.select();
		};
	};

	// onkeydowninput handles accessibility and editing
	const onkeydowninput = (index: number) => {
		return async (e: KeyboardEvent) => {
			const node = inputs[index];

			// allow pasting into fields
			if (e.key === 'v' && (e.ctrlKey || e.metaKey)) {
				const clipboardData = await navigator.clipboard.readText();
				const clipboardValue = clipboardData.replace(/\D/g, '').slice(0, length);
				const clipboardValid = await isValueValid(clipboardValue, validateOpts);

				console.log('pasting', clipboardValue, clipboardValid);

				if (clipboardValid) {
					value = clipboardValue;
					inputs[inputs.length - 1].focus();
				}

				return;
			}

			// passthrough ctrl, cmd, and alt keys
			if (e.ctrlKey || e.metaKey || e.altKey) {
				return;
			}

			e.preventDefault(); // prevent default behaviour for all keys

			// allow plain backspace and (forward) delete behaviour under some circumstances
			if (
				(e.key === 'Backspace' || e.key === 'Delete') &&
				node.value !== '' &&
				(index === inputs.length - 1 || inputs[index + 1].value === '')
			) {
				inputs[index].value = '';
				value = joinValue();
				return;
			}

			if (e.key === 'Backspace' && node.value === '' && index > 0) {
				inputs[index - 1].focus();
				inputs[index - 1].value = '';
				value = joinValue();
			} else if ((e.key === 'ArrowLeft' || e.key === 'ArrowDown') && index > 0) {
				inputs[index - 1].focus();
			} else if (
				(e.key === 'ArrowRight' || e.key === 'ArrowUp') &&
				index < inputs.length - 1 &&
				node.value !== ''
			) {
				inputs[index + 1].focus();
			} else if (e.key !== 'Backspace' && /^\d$/.test(e.key)) {
				if (
					node.selectionStart === null ||
					node.selectionEnd === null ||
					(node.value !== '' && Math.abs(node.selectionEnd - node.selectionStart) === 0)
				)
					return;

				inputs[index].value = e.key;
				value = joinValue();

				if (index < inputs.length - 1) {
					inputs[index + 1].focus();
				}
			}
		};
	};

	$effect(() => {
		if (value) updateHiddenInput();
		if (onchange !== undefined) onchange(value);
		if (oncomplete !== undefined && value.length === length) oncomplete(value);
	});
</script>

<input
	class="hidden"
	use:validate={validateOpts}
	onvalidate={(e) => {
		valid = e.detail.valid;
	}}
	bind:this={hiddenInput}
/>

{#if label}
	<Label bigError={!valid} for={name}>{label}</Label>
{/if}

<div>
	<div class="flex gap-4">
		{#each { length: length } as _, i}
			<input
				type="text"
				class={[
					'px[1.125rem] w-[5ch] rounded-sm pt-4 pb-3.5 transition-colors',
					'text-center align-middle font-mono font-normal placeholder:font-normal',
					'border-accent dark:border-accent/50 border',
					'text-text placeholder:text-text/30 dark:text-background dark:placeholder:text-background/30',
					'dark:bg-text-800 bg-white dark:sm:bg-slate-800',
					!valid && i >= value.length && 'border-red-500!'
				]}
				value={value[i] || ''}
				{required}
				maxlength="1"
				onfocus={onfocusinput(i)}
				onmousedown={onmousedown(i)}
				onkeydown={onkeydowninput(i)}
				bind:this={inputs[i]}
				placeholder="0"
			/>
		{/each}
	</div>
</div>
