<script lang="ts">
    import { onMount } from 'svelte';
    import type { Snippet } from 'svelte';
    import X from '@lucide/svelte/icons/x';

    let {
        open,
        title,
        onClose,
        children,
        footer,
        testid
    }: {
        open: boolean;
        title: string;
        onClose: () => void;
        children: Snippet;
        footer?: Snippet;
        testid?: string;
    } = $props();

    let panel: HTMLDivElement | undefined = $state();
    let previouslyFocused: HTMLElement | null = null;

    $effect(() => {
        if (open) {
            previouslyFocused = document.activeElement as HTMLElement | null;
            queueMicrotask(() => panel?.focus());
        } else if (previouslyFocused) {
            previouslyFocused.focus();
            previouslyFocused = null;
        }
    });

    function focusable(): HTMLElement[] {
        if (!panel) return [];
        const selector = [
            'a[href]',
            'button:not([disabled])',
            'input:not([disabled]):not([type="hidden"])',
            'select:not([disabled])',
            'textarea:not([disabled])',
            '[tabindex]:not([tabindex="-1"])'
        ].join(',');
        return Array.from(panel.querySelectorAll<HTMLElement>(selector));
    }

    function onKeydown(e: KeyboardEvent) {
        if (!open) return;
        if (e.key === 'Escape') {
            e.stopPropagation();
            onClose();
            return;
        }
        if (e.key === 'Tab') {
            const items = focusable();
            if (items.length === 0) {
                e.preventDefault();
                panel?.focus();
                return;
            }
            const first = items[0];
            const last = items[items.length - 1];
            const active = document.activeElement as HTMLElement | null;
            if (e.shiftKey) {
                if (active === first || !panel?.contains(active)) {
                    e.preventDefault();
                    last.focus();
                }
            } else if (active === last) {
                e.preventDefault();
                first.focus();
            }
        }
    }

    onMount(() => {
        document.addEventListener('keydown', onKeydown);
        return () => document.removeEventListener('keydown', onKeydown);
    });

    function onScrimClick(e: MouseEvent) {
        if (e.target === e.currentTarget) onClose();
    }
</script>

{#if open}
    <div
        class="scrim"
        onclick={onScrimClick}
        role="presentation"
        data-testid={testid ? `${testid}-scrim` : undefined}
    >
        <div
            class="panel"
            role="dialog"
            aria-modal="true"
            aria-labelledby="sheet-title"
            tabindex="-1"
            bind:this={panel}
            data-testid={testid}
        >
            <div class="handle" aria-hidden="true"></div>
            <header class="header">
                <h2 id="sheet-title">{title}</h2>
                <button
                    type="button"
                    class="close"
                    onclick={onClose}
                    aria-label="Close"
                    title="Close"
                    data-testid={testid ? `${testid}-close` : undefined}
                >
                    <X size={18} aria-hidden="true" />
                </button>
            </header>
            <div class="body">{@render children()}</div>
            {#if footer}
                <footer class="footer">{@render footer()}</footer>
            {/if}
        </div>
    </div>
{/if}

<style>
    .scrim {
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: var(--z-modal);
        display: flex;
        align-items: flex-end;
        justify-content: center;
        animation: scrim-in 180ms ease-out;
    }

    @keyframes scrim-in {
        from {
            background: rgba(0, 0, 0, 0);
        }
        to {
            background: rgba(0, 0, 0, 0.4);
        }
    }

    .panel {
        background: var(--surface);
        color: var(--text);
        border: 1px solid var(--border);
        border-top-left-radius: var(--radius-lg);
        border-top-right-radius: var(--radius-lg);
        box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.2);
        width: 100%;
        max-width: 32rem;
        max-height: 85vh;
        display: flex;
        flex-direction: column;
        outline: none;
        /* Honor home-indicator safe area so the footer / body don't
           sit under the iOS gesture area. */
        padding-bottom: var(--safe-bottom);
        animation: panel-in 220ms ease-out;
    }

    @keyframes panel-in {
        from {
            transform: translateY(100%);
        }
        to {
            transform: translateY(0);
        }
    }

    .handle {
        width: 36px;
        height: 4px;
        margin: var(--space-2) auto var(--space-1);
        background: var(--border-strong);
        border-radius: var(--radius-pill);
        flex-shrink: 0;
    }

    .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: var(--space-2) var(--space-4) var(--space-3);
        border-bottom: 1px solid var(--border);
    }

    .header h2 {
        margin: 0;
        font-size: var(--font-lg);
    }

    .close {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 32px;
        height: 32px;
        padding: 0;
        background: transparent;
        color: var(--text-muted);
        border: 1px solid transparent;
        border-radius: var(--radius-sm);
    }

    .close:hover {
        background: var(--surface-elevated);
        color: var(--text);
    }

    .body {
        padding: var(--space-4);
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }

    .footer {
        padding: var(--space-3) var(--space-4);
        border-top: 1px solid var(--border);
        display: flex;
        gap: var(--space-2);
        justify-content: flex-end;
    }
</style>
