<script lang="ts">
	import { Select as SelectPrimitive } from "bits-ui";
	import SelectScrollUpButton from "./select-scroll-up-button.svelte";
	import SelectScrollDownButton from "./select-scroll-down-button.svelte";
	import { cn, type WithoutChild } from "$lib/utils.js";
	import { getContext } from "svelte";
	import { get } from "svelte/store";
	import { selectSearchContextKey, type SelectSearchContext } from "./select-search-context";

	let {
		ref = $bindable(null),
		class: className,
		sideOffset = 4,
		portalProps,
		children,
		searchable = true,
		searchPlaceholder = "Buscar...",
		autoFocusSearch = true,
		...restProps
	}: WithoutChild<SelectPrimitive.ContentProps> & {
		portalProps?: SelectPrimitive.PortalProps;
		searchable?: boolean;
		searchPlaceholder?: string;
		autoFocusSearch?: boolean;
	} = $props();

	const searchContext = getContext<SelectSearchContext | undefined>(selectSearchContextKey);
	let searchQuery = $state('');
	let isOpen = $state(false);
	let searchInputRef = $state<HTMLInputElement | null>(null);
	let prevOpen = $state(false);

	$effect(() => {
		if (!searchContext) return;
		const unsubscribe = searchContext.query.subscribe((value) => {
			searchQuery = value;
		});
		return unsubscribe;
	});

	$effect(() => {
		if (!searchContext) return;
		const unsubscribe = searchContext.open.subscribe((value) => {
			isOpen = value;
		});
		return unsubscribe;
	});

	$effect(() => {
		if (!searchable || !autoFocusSearch || !isOpen || !searchInputRef) return;
		searchInputRef.focus();
		searchInputRef.select();
	});

	/** bits-ui hace preventDefault en onCloseAutoFocus; devolvemos foco al trigger de este Select. */
	$effect(() => {
		if (!searchContext) return;
		const justClosed = prevOpen && !isOpen;
		prevOpen = isOpen;
		if (!justClosed) return;
		queueMicrotask(() => {
			const trigger = get(searchContext.triggerRef);
			if (trigger && document.contains(trigger)) {
				try {
					trigger.focus();
				} catch {
					/* ignore */
				}
			}
		});
	});

	function updateQuery(next: string) {
		searchQuery = next;
		searchContext?.query.set(next);
	}

	function handleContentKeydown(event: KeyboardEvent) {
		if (!searchable || !searchInputRef) return;
		if (event.ctrlKey || event.metaKey || event.altKey) return;
		if (event.key.length === 1) {
			searchInputRef.focus();
		}
	}

	/** Despacha un KeyboardEvent al trigger de bits-ui usando el contexto de búsqueda. */
	function dispatchToTrigger(key: string) {
		const trigger = searchContext ? get(searchContext.triggerRef) : null;
		if (!trigger) return;
		trigger.dispatchEvent(
			new KeyboardEvent('keydown', { key, code: key, bubbles: true, cancelable: true })
		);
	}

	/**
	 * Selecciona el ítem resaltado (o el primero si ninguno lo está) usando la ruta
	 * de teclado de bits-ui: TriggerState.onkeydown → #handleKeyboardSelection → toggleItem.
	 *
	 * Se evitan PointerEvents sintéticos porque onpointerup verifica opts.ref.current,
	 * que es un Attachment y puede estar en estado inconsistente durante el despacho.
	 */
	function selectHighlightedItem() {
		requestAnimationFrame(() => {
			// Si no hay ítem resaltado todavía, bits-ui resalta el primero síncronamente con ArrowDown
			const hasHighlighted = !!ref?.querySelector('[data-highlighted]');
			if (!hasHighlighted) {
				dispatchToTrigger('ArrowDown');
			}
			// TriggerState.onkeydown(Enter) → #handleKeyboardSelection → toggleItem → handleClose
			dispatchToTrigger('Enter');
		});
	}

	/** Maneja teclado en el buscador: Tab cierra, flechas navegan, Enter selecciona, resto filtra. */
	function handleSearchInputKeydown(e: KeyboardEvent) {
		if (e.key === 'Tab') {
			// Cierra el dropdown y deja que Tab mueva el foco al campo siguiente naturalmente
			searchContext?.setOpen(false);
			return;
		}
		if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
			e.preventDefault();
			e.stopPropagation();
			dispatchToTrigger(e.key);
			return;
		}
		if (e.key === 'Enter' && !e.isComposing) {
			e.preventDefault();
			e.stopPropagation();
			selectHighlightedItem();
			return;
		}
		// Detener burbujeo de caracteres para que handleContentKeydown no robe el foco
		e.stopPropagation();
	}
</script>

<SelectPrimitive.Portal {...portalProps}>
	<SelectPrimitive.Content
		bind:ref
		{sideOffset}
		data-slot="select-content"
		onkeydown={handleContentKeydown}
		class={cn(
			"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) relative z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
			className
		)}
		{...restProps}
	>
		<SelectScrollUpButton />
		
		{#if searchable}
			<div class="border-input sticky top-0 z-10 bg-popover px-2 py-1.5">
				<input
					type="text"
					bind:this={searchInputRef}
					value={searchQuery}
					placeholder={searchPlaceholder}
					class="placeholder:text-muted-foreground flex h-8 w-full rounded-md border border-input bg-background px-3 py-1 text-sm outline-none focus:border-ring focus:ring-1 focus:ring-ring"
					oninput={(event) => updateQuery((event.currentTarget as HTMLInputElement).value)}
					onclick={(e) => e.stopPropagation()}
					onkeydown={handleSearchInputKeydown}
				/>
			</div>
		{/if}

		<SelectPrimitive.Viewport
			class={cn(
				"h-(--bits-select-anchor-height) min-w-(--bits-select-anchor-width) w-full scroll-my-1 p-1"
			)}
		>
			{@render children?.()}
		</SelectPrimitive.Viewport>
		<SelectScrollDownButton />
	</SelectPrimitive.Content>
</SelectPrimitive.Portal>
