<script lang="ts" module>
	export type ActionSelectOption = {
		value?: string;
		icon?: IconDef | Snippet<[opt: ActionSelectOption]>;
		label: string | Snippet<[opt: ActionSelectOption]>;
		disabled?: boolean;
		onchoose?: (opt: ActionSelectOption) => void;
	};
</script>

<script lang="ts">
	import { Select } from 'melt/builders';
	import { tick, type Snippet } from 'svelte';
	import Label from './Label.svelte';
	import Button from './Button.svelte';
	import { CaretDown, Check } from 'phosphor-svelte';
	import type { ClassValue } from 'svelte/elements';
	import type { IconDef } from './util';

	interface Props {
		name?: string;
		label?: string;
		placeholder?: string;
		value?: ActionSelectOption;
		stateless?: boolean;
		tabbable?: boolean;
		sameWidth?: boolean;
		options: ActionSelectOption[];
		class?: ClassValue | null | undefined;
		frameless?: boolean;
		onchange?: (value: ActionSelectOption | undefined) => void;
		children?: Snippet;
	}

	let {
		name,
		label,
		placeholder = 'Choose an action',
		value = $bindable(undefined),
		stateless = false,
		tabbable = true,
		sameWidth = true,
		options,
		class: classValue,
		frameless = false,
		onchange,
		children
	}: Props = $props();

	const select = new Select<ActionSelectOption>({
		forceVisible: true,
		value: () => value,
		sameWidth: () => sameWidth,
		onValueChange: (val) => {
			if (val?.disabled) return;
			val?.onchoose?.(val);
			onchange?.(val);
			if (!stateless) value = val;
		}
	});

	let buttonElement = $state<HTMLButtonElement | null>(null);
	let allowFocus = false;

	$effect(() => {
		tick().then(() => {
			setTimeout(() => {
				allowFocus = true;
			}, 100);
		});
	});

	$effect(() => {
		if (buttonElement) {
			// Prevent focus during hydration
			const originalFocus = buttonElement.focus.bind(buttonElement);
			buttonElement.focus = (...args) => {
				if (allowFocus) {
					return originalFocus(...args);
				}
			};
		}
	});
</script>

<div class={[classValue]}>
	{#if label}
		<Label {...select.label}>{label}</Label>
	{/if}

	{#if name}
		<input type="hidden" {name} value={value?.value} />
	{/if}

	<Button
		bind:ref={buttonElement}
		class={[
			'flex w-full items-center rounded-xl!',
			frameless && 'hover:bg-sui-text-100/50 bg-transparent',
			frameless && select.open && 'bg-sui-text-100/50!'
		]}
		{...select.trigger}
		{...!tabbable && { tabindex: -1 }}
	>
		{#if children}{@render children()}
		{:else}
			{#if stateless || !select.value}
				{placeholder}
			{:else}
				{@render optionIcon(select.value)}
				{@render optionLabel(select.value)}
			{/if}

			<CaretDown
				weight="bold"
				size="1.1em"
				class={[select.open && '-scale-y-100', 'ml-auto transition-transform']}
			/>
		{/if}
	</Button>

	<div
		{...select.content}
		class={['border-sui-accent flex flex-col gap-1 rounded-xl border p-2 shadow-md']}
	>
		{#each options as option (option.label)}
			<div
				{...select.getOption(option, typeof option.label === 'string' ? option.label : undefined)}
				class={[
					'flex w-full items-center gap-3 rounded px-5 py-2 text-base font-medium transition-colors select-none',
					option.disabled
						? ['cursor-not-allowed opacity-50']
						: stateless
							? [select.highlighted?.label === option.label && 'bg-sui-text-100', 'cursor-pointer']
							: [
									select.highlighted?.label === option.label &&
										'bg-sui-accent/80 text-sui-background',
									select.value?.label === option.label && 'bg-sui-accent text-sui-background'
								]
				]}
			>
				{@render optionIcon(option)}
				{@render optionLabel(option)}

				{#if !stateless && select.value?.label === option.label}
					<Check size="1.1em" weight="bold" class={['ml-auto']} />
				{/if}
			</div>
		{/each}
	</div>
</div>

{#snippet optionLabel(opt: ActionSelectOption)}
	{#if typeof opt.label === 'string'}
		{opt.label}
	{:else}
		{@render opt.label(opt)}
	{/if}
{/snippet}

{#snippet optionIcon(opt: ActionSelectOption)}
	{#if opt.icon && typeof opt.icon === 'object'}
		<opt.icon.component {...opt.icon.props} />
	{:else if opt.icon}
		{@render opt.icon(opt)}
	{/if}
{/snippet}

<style lang="postcss">
	[data-melt-select-content] {
		position: absolute;
		pointer-events: none;
		opacity: 0;

		transform: scale(0.975);

		transition: 0.2s;
		transition-property: opacity, transform;
		transform-origin: var(--melt-popover-content-transform-origin, center);
	}

	[data-melt-select-content][data-open] {
		pointer-events: auto;
		opacity: 1;
		transform: scale(1);
	}
</style>
