<script lang="ts">
	import { fly } from "svelte/transition";
	let {
		choices,
		filtered_indices,
		show_options = false,
		disabled = false,
		selected_indices = [],
		active_index = null,
		remember_scroll = false,
		offset_from_top = 0,
		from_top = false,
		onchange,
		onload
	}: {
		choices: [string, string | number][];
		filtered_indices: number[];
		show_options: boolean;
		disabled?: boolean;
		selected_indices?: (string | number)[];
		active_index: number | null;
		remember_scroll?: boolean;
		offset_from_top?: number;
		from_top?: boolean;
		onchange?: (index: any) => void;
		onload?: () => void;
	} = $props();

	let distance_from_top: number;
	let distance_from_bottom: number;
	let input_height: number;
	let input_width = $state(0);
	let refElement: HTMLDivElement;
	let listElement: HTMLUListElement;
	let top: string | null = $state(null);
	let bottom: string | null = $state(null);
	let max_height: number = $state(0);
	let innerHeight = $state(0);
	let list_scroll_y = 0;

	function calculate_window_distance(): void {
		const { top: ref_top, bottom: ref_bottom } =
			refElement.getBoundingClientRect();
		if (from_top) {
			distance_from_top = offset_from_top;
		} else {
			distance_from_top = ref_top;
		}
		distance_from_bottom = innerHeight - ref_bottom;
	}

	let scroll_timeout: NodeJS.Timeout | null = null;
	function scroll_listener(): void {
		if (!show_options) return;
		if (scroll_timeout !== null) {
			clearTimeout(scroll_timeout);
		}

		scroll_timeout = setTimeout(() => {
			calculate_window_distance();
			scroll_timeout = null;
		}, 10);
	}

	function restore_last_scroll(): void {
		listElement?.scrollTo?.(0, list_scroll_y);
	}

	$effect(() => {
		if (show_options && refElement) {
			if (remember_scroll) {
				restore_last_scroll();
			} else {
				if (listElement && selected_indices.length > 0) {
					let elements = listElement.querySelectorAll("li");
					for (const element of Array.from(elements)) {
						if (
							element.getAttribute("data-index") ===
							selected_indices[0].toString()
						) {
							listElement?.scrollTo?.(0, (element as HTMLLIElement).offsetTop);
							break;
						}
					}
				}
			}
			calculate_window_distance();
			const rect = refElement.parentElement?.getBoundingClientRect();
			input_height = rect?.height || 0;
			input_width = rect?.width || 0;
			onload?.();
		}
		if (distance_from_bottom > distance_from_top || from_top) {
			top = `${distance_from_top}px`;
			max_height = distance_from_bottom;
			bottom = null;
		} else {
			bottom = `${distance_from_bottom + input_height}px`;
			max_height = distance_from_top - input_height;
			top = null;
		}
	});
</script>

<svelte:window on:scroll={scroll_listener} bind:innerHeight />

<div class="reference" bind:this={refElement} />
{#if show_options && !disabled}
	<ul
		class="options"
		transition:fly={{ duration: 200, y: 5 }}
		onmousedown={(e) => {
			e.preventDefault();
			onchange?.((e.target as HTMLElement).dataset.index);
		}}
		onscroll={(e) => (list_scroll_y = e.currentTarget.scrollTop)}
		style:top
		style:bottom
		style:max-height={`calc(${max_height}px - var(--window-padding))`}
		style:width={input_width + "px"}
		bind:this={listElement}
		role="listbox"
	>
		{#each filtered_indices as index}
			<li
				class="item"
				class:selected={selected_indices.includes(index)}
				class:active={index === active_index}
				class:bg-gray-100={index === active_index}
				class:dark:bg-gray-600={index === active_index}
				style:width={input_width + "px"}
				data-index={index}
				aria-label={choices[index][0]}
				data-testid="dropdown-option"
				role="option"
				aria-selected={selected_indices.includes(index)}
			>
				<span class:hide={!selected_indices.includes(index)} class="inner-item">
					✓
				</span>
				{choices[index][0]}
			</li>
		{/each}
	</ul>
{/if}

<style>
	.options {
		--window-padding: var(--size-8);
		position: fixed;
		z-index: var(--layer-top);
		margin-left: 0;
		box-shadow: var(--shadow-drop-lg);
		border-radius: var(--container-radius);
		background: var(--background-fill-primary);
		min-width: fit-content;
		max-width: inherit;
		overflow: auto;
		color: var(--body-text-color);
		list-style: none;
	}

	.item {
		display: flex;
		cursor: pointer;
		padding: var(--size-2);
		word-break: break-word;
	}

	.item:hover,
	.active {
		background: var(--background-fill-secondary);
	}

	.inner-item {
		padding-right: var(--size-1);
	}

	.hide {
		visibility: hidden;
	}
</style>
