<script lang="ts">
	import { focusTrap } from '$lib/actions/focus-trap';
	import { scrollLock } from '$lib/actions/scroll-lock';
	import { modalInert } from '$lib/actions/modal-inert';
	import type { Snippet } from 'svelte';

	// Accessible name is REQUIRED. Pass `titleId` when the dialog renders its own
	// visible heading (the common case — link aria-labelledby to that heading's id),
	// or `ariaLabel` for dialogs without a visible title (e.g. an image preview).
	// Failing to provide either leaves the dialog without an accessible name,
	// which screen readers announce as just "dialog" — useless. The runtime
	// warning below catches missed cases during dev.
	interface Props {
		open: boolean;
		titleId?: string;
		ariaLabel?: string;
		onClose: () => void;
		closeOnBackdrop?: boolean;
		children: Snippet;
	}

	let { open, titleId, ariaLabel, onClose, closeOnBackdrop = true, children }: Props = $props();

	$effect(() => {
		if (open && !titleId && !ariaLabel && typeof console !== 'undefined') {
			console.warn('<Modal> opened without titleId or ariaLabel — dialog has no accessible name.');
		}
	});
</script>

{#if open}
	<!-- display:contents wrapper: keeps the backdrop + dialog visually unchanged
	     while giving `modalInert` a single node whose siblings (the background
	     page, nav, etc.) get inerted for assistive tech. -->
	<div class="contents" use:modalInert>
		<button
			type="button"
			class="fixed inset-0 z-50 bg-black/50"
			aria-label="Schließen"
			tabindex="-1"
			onclick={closeOnBackdrop ? onClose : () => {}}
		></button>
		<div
			class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4"
			role="dialog"
			aria-modal="true"
			aria-labelledby={titleId}
			aria-label={titleId ? undefined : ariaLabel}
			use:focusTrap={{ onclose: onClose }}
			use:scrollLock
		>
			<div class="card pointer-events-auto w-full max-w-sm p-6 shadow-xl">
				{@render children()}
			</div>
		</div>
	</div>
{/if}
