<script module lang="ts">
	import { SvelteMap, SvelteSet } from 'svelte/reactivity';
	import { tv, type VariantProps } from 'tailwind-variants';

	export const dropdown = tv({
		slots: {
			root: 'relative inline-block',
			plainTrigger:
				'flex cursor-pointer items-center gap-1 border-0 bg-transparent p-0 text-inherit',
			menu: 'absolute right-0 z-50 mt-2 w-56 overflow-hidden rounded border border-border bg-surface text-sm shadow-menu'
		},
		variants: {
			open: {
				true: {
					menu: 'block'
				},
				false: {
					menu: 'hidden'
				}
			}
		},
		defaultVariants: {
			open: false
		}
	});

	export type DropdownVariants = VariantProps<typeof dropdown>;

	// opening one dropdown in a group closes its siblings.
	const groups = new SvelteMap<string, SvelteSet<() => void>>();

	function registerDropdown(group: string | undefined, close: () => void) {
		if (!group) return () => {};
		let set = groups.get(group);
		if (!set) {
			set = new SvelteSet();
			groups.set(group, set);
		}
		set.add(close);
		return () => {
			set.delete(close);
		};
	}

	function closeGroupExcept(group: string | undefined, keep: () => void) {
		if (!group) return;
		groups.get(group)?.forEach((close) => {
			if (close !== keep) close();
		});
	}
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';
	import { onMount, setContext } from 'svelte';
	import Button from './Button.svelte';

	interface Props {
		id?: string;
		group?: string;
		variant?: 'plain' | 'button';
		label?: string;
		trigger: Snippet;
		children: Snippet;
	}

	let { id, group, variant = 'plain', label, trigger, children }: Props = $props();

	let open = $state(false);
	const classes = $derived(dropdown({ open }));
	let root = $state<HTMLElement>();

	const close = () => {
		open = false;
	};

	const toggle = () => {
		open = !open;
		if (open) closeGroupExcept(group, close);
	};

	setContext('dropdown-close', close);

	onMount(() => {
		const unregister = registerDropdown(group, close);

		const onDocumentClick = (event: MouseEvent) => {
			if (open && event.target instanceof Node && root && !root.contains(event.target)) close();
		};
		const onKeydown = (event: KeyboardEvent) => {
			if (event.key === 'Escape') close();
		};

		document.addEventListener('click', onDocumentClick);
		document.addEventListener('keydown', onKeydown);

		return () => {
			unregister();
			document.removeEventListener('click', onDocumentClick);
			document.removeEventListener('keydown', onKeydown);
		};
	});
</script>

<div bind:this={root} class={classes.root()}>
	{#if variant === 'button'}
		<Button
			size="sm"
			class="gap-2 !py-0 text-sm"
			onclick={toggle}
			aria-haspopup="menu"
			aria-expanded={open}
			aria-label={label}
		>
			{@render trigger()}
		</Button>
	{:else}
		<button
			type="button"
			class={classes.plainTrigger()}
			onclick={toggle}
			aria-haspopup="menu"
			aria-expanded={open}
			aria-label={label}
		>
			{@render trigger()}
		</button>
	{/if}

	<div {id} class={classes.menu()} role="menu">
		{@render children()}
	</div>
</div>
