/**
 * Focus management for modal dialogs.
 *
 * `trapFocus` is a Svelte action for an element with `role="dialog"` and
 * `aria-modal="true"`. On mount it remembers the currently-focused
 * element, moves focus into the dialog, and keeps Tab / Shift+Tab cycling
 * within it; on destroy it restores focus to the original element. ESC
 * handling stays with the component (it owns the open/close state).
 *
 * Initial focus goes to the element marked `data-autofocus`, else the
 * first focusable element, else the dialog node itself.
 */

const FOCUSABLE_SELECTOR = [
	"a[href]",
	"button:not([disabled])",
	"input:not([disabled])",
	"select:not([disabled])",
	"textarea:not([disabled])",
	'[tabindex]:not([tabindex="-1"])',
].join(",");

function focusableWithin(node: HTMLElement): HTMLElement[] {
	return Array.from(
		node.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
	).filter((el) => el.offsetParent !== null || el === document.activeElement);
}

/** Svelte action: trap and restore focus for a modal dialog node. */
export function trapFocus(node: HTMLElement): { destroy(): void } {
	const previouslyFocused =
		document.activeElement instanceof HTMLElement
			? document.activeElement
			: null;

	function onKeydown(event: KeyboardEvent): void {
		if (event.key !== "Tab") return;
		const items = focusableWithin(node);
		if (items.length === 0) {
			event.preventDefault();
			node.focus();
			return;
		}
		const first = items[0];
		const last = items[items.length - 1];
		const active = document.activeElement;
		if (event.shiftKey && active === first) {
			event.preventDefault();
			last.focus();
		} else if (!event.shiftKey && active === last) {
			event.preventDefault();
			first.focus();
		}
	}

	const initial =
		node.querySelector<HTMLElement>("[data-autofocus]") ??
		focusableWithin(node)[0] ??
		node;
	initial.focus();

	node.addEventListener("keydown", onKeydown);

	return {
		destroy(): void {
			node.removeEventListener("keydown", onKeydown);
			previouslyFocused?.focus();
		},
	};
}
