<script lang="ts">
    import { onMount, tick } from 'svelte';
    import FolderPlus from '@lucide/svelte/icons/folder-plus';
    import Tag from '@lucide/svelte/icons/tag';
    import Download from '@lucide/svelte/icons/download';
    import Link2 from '@lucide/svelte/icons/link-2';
    import Sparkles from '@lucide/svelte/icons/sparkles';

    /**
     * Floating context menu anchored at `(anchor.x, anchor.y)` in
     * viewport coordinates. After mount the menu position is clamped
     * so it doesn't overflow the viewport on the right or bottom edge.
     *
     * Closing rules:
     * - Esc.
     * - pointerdown outside the menu (capture-phase so we beat the
     *   underlying right-click handler on the next page image).
     * - Any scroll or resize, since the anchor is stale.
     */
    let {
        open,
        anchor,
        onClose,
        onAddToCollection,
        onAddTag,
        onSaveImage,
        onCopyLink,
        collectionsCount,
        tags,
        canAnalyze = false,
        onAnalyzePage,
        analyzeState = 'idle'
    }: {
        open: boolean;
        anchor: { x: number; y: number };
        onClose: () => void;
        onAddToCollection: () => void;
        onAddTag: () => void;
        /** Open the page image in a new tab so the browser's "Save as"
         *  is one click away. */
        onSaveImage: () => void;
        /** Copy the canonical reader URL (with `?page=N`) to clipboard. */
        onCopyLink: () => void;
        /** Null while the count is still loading. */
        collectionsCount: number | null;
        /** May be empty. */
        tags: string[];
        /** Admin-only: show the "Queue for analysis" action. */
        canAnalyze?: boolean;
        /** Force-enqueue the current page for AI analysis. */
        onAnalyzePage?: () => void;
        /** Transient state for the analyze action's label. */
        analyzeState?: 'idle' | 'busy' | 'done' | 'error';
    } = $props();

    let menuEl: HTMLDivElement | undefined = $state();
    let pos = $state<{ left: number; top: number }>({ left: 0, top: 0 });

    $effect(() => {
        if (open) {
            // Seed at the anchor, then post-mount nudge into the
            // viewport once we know the menu's measured size.
            pos = { left: anchor.x, top: anchor.y };
            void clampAfterMount();
        }
    });

    async function clampAfterMount() {
        await tick();
        if (!menuEl) return;
        const rect = menuEl.getBoundingClientRect();
        const vw = window.innerWidth;
        const vh = window.innerHeight;
        let left = anchor.x;
        let top = anchor.y;
        if (left + rect.width + 8 > vw) left = Math.max(8, vw - rect.width - 8);
        if (top + rect.height + 8 > vh) top = Math.max(8, vh - rect.height - 8);
        pos = { left, top };
    }

    function onDocPointerDown(e: PointerEvent) {
        if (!open) return;
        if (menuEl && !menuEl.contains(e.target as Node)) {
            onClose();
        }
    }

    function onDocKeydown(e: KeyboardEvent) {
        if (!open) return;
        if (e.key === 'Escape') {
            e.stopPropagation();
            onClose();
        }
    }

    function onScrollOrResize() {
        if (open) onClose();
    }

    onMount(() => {
        document.addEventListener('pointerdown', onDocPointerDown, true);
        document.addEventListener('keydown', onDocKeydown, true);
        window.addEventListener('scroll', onScrollOrResize, true);
        window.addEventListener('resize', onScrollOrResize);
        return () => {
            document.removeEventListener('pointerdown', onDocPointerDown, true);
            document.removeEventListener('keydown', onDocKeydown, true);
            window.removeEventListener('scroll', onScrollOrResize, true);
            window.removeEventListener('resize', onScrollOrResize);
        };
    });

    const collectionsLine = $derived(
        collectionsCount == null
            ? 'Loading…'
            : collectionsCount === 0
              ? 'Not in any collection'
              : `In ${collectionsCount} collection${collectionsCount === 1 ? '' : 's'}`
    );

    const tagsLine = $derived(
        tags.length === 0 ? 'No tags yet' : `Tagged: ${tags.join(', ')}`
    );
</script>

{#if open}
    <!--
        Rendered as a popover-shaped div + plain <button> children
        rather than role="menu" + role="menuitem". The ARIA menu
        pattern expects arrow-key navigation between menuitems, and
        implementing that here would either re-invent a small focus
        manager or violate the contract. `role="group"` is the
        minimal role that gives `aria-label` a mooring — without it,
        the label is ignored and a screen-reader user landing on the
        first <button> would have no announced container.
    -->
    <div
        bind:this={menuEl}
        class="menu"
        role="group"
        aria-label="Page actions"
        style:left="{pos.left}px"
        style:top="{pos.top}px"
        data-testid="page-context-menu"
    >
        <button
            type="button"
            class="item"
            onclick={onAddToCollection}
            data-testid="page-context-add-to-collection"
        >
            <FolderPlus size={14} aria-hidden="true" />
            <span>Add to collection…</span>
        </button>
        <button
            type="button"
            class="item"
            onclick={onAddTag}
            data-testid="page-context-add-tag"
        >
            <Tag size={14} aria-hidden="true" />
            <span>Add tag…</span>
        </button>
        <div class="divider" aria-hidden="true"></div>
        <button
            type="button"
            class="item"
            onclick={onSaveImage}
            data-testid="page-context-save-image"
        >
            <Download size={14} aria-hidden="true" />
            <span>Save image</span>
        </button>
        <button
            type="button"
            class="item"
            onclick={onCopyLink}
            data-testid="page-context-copy-link"
        >
            <Link2 size={14} aria-hidden="true" />
            <span>Copy page link</span>
        </button>
        {#if canAnalyze}
            <div class="divider" aria-hidden="true"></div>
            <button
                type="button"
                class="item"
                onclick={onAnalyzePage}
                disabled={analyzeState === 'busy'}
                data-testid="page-context-analyze"
            >
                <Sparkles size={14} aria-hidden="true" />
                <span>
                    {#if analyzeState === 'busy'}Queueing…
                    {:else if analyzeState === 'done'}Queued for analysis ✓
                    {:else if analyzeState === 'error'}Queue failed — retry
                    {:else}Queue for analysis{/if}
                </span>
            </button>
        {/if}
        <div class="divider" aria-hidden="true"></div>
        <p class="hint" data-testid="page-context-collections-line">
            {collectionsLine}
        </p>
        <p class="hint" data-testid="page-context-tags-line">{tagsLine}</p>
    </div>
{/if}

<style>
    .menu {
        position: fixed;
        z-index: var(--z-modal);
        background: var(--surface);
        color: var(--text);
        border: 1px solid var(--border);
        border-radius: var(--radius-md);
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
        padding: var(--space-1);
        min-width: 14rem;
        max-width: min(20rem, calc(100vw - 16px));
        display: flex;
        flex-direction: column;
        gap: 2px;
    }

    .item {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        padding: var(--space-2);
        background: transparent;
        color: var(--text);
        border: 1px solid transparent;
        border-radius: var(--radius-sm);
        text-align: left;
        cursor: pointer;
    }

    .item:hover,
    .item:focus-visible {
        background: var(--surface-elevated);
    }

    .divider {
        height: 1px;
        background: var(--border);
        margin: var(--space-1) 0;
    }

    .hint {
        margin: 0;
        padding: 0 var(--space-2);
        color: var(--text-muted);
        font-size: var(--font-xs);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>
