<script lang="ts">
    import {
        getMyTagsForPage,
        addTagToPage,
        removeTagFromPage,
        listMyDistinctPageTags,
        type PageTagSummary
    } from '$lib/api/page_tags';
    import X from '@lucide/svelte/icons/x';

    /**
     * Panel content for editing a user's tags on a single page. Slots
     * into either a `Sheet` (mobile) or a `Modal` (desktop) — both
     * provide the chrome (title bar, dismiss, focus trap), this
     * component owns only the chip editor + autocomplete.
     */
    let {
        pageId,
        onChange
    }: {
        pageId: string;
        /** Fires after every successful add/remove so the host can
         *  refresh the context menu's "Tagged: …" line. */
        onChange?: (tags: string[]) => void;
    } = $props();

    let tags = $state<string[]>([]);
    let suggestions = $state<PageTagSummary[]>([]);
    let draft = $state('');
    let loading = $state(false);
    let busy = $state(false);
    let error: string | null = $state(null);
    let inputEl: HTMLInputElement | undefined = $state();

    $effect(() => {
        // Re-load when the host swaps in a different page.
        void pageId;
        void load();
    });

    async function load() {
        loading = true;
        error = null;
        try {
            const [current, dist] = await Promise.all([
                getMyTagsForPage(pageId),
                listMyDistinctPageTags(undefined, 12)
            ]);
            tags = current;
            suggestions = dist;
        } catch (e) {
            error = (e as Error).message;
        } finally {
            loading = false;
        }
    }

    async function refreshSuggestions() {
        try {
            const next = await listMyDistinctPageTags(
                draft.trim() || undefined,
                12
            );
            suggestions = next ?? [];
        } catch {
            // Soft-fail — autocomplete is non-critical.
        }
    }

    async function add(rawTag: string) {
        const tag = rawTag.trim();
        if (!tag || busy) return;
        busy = true;
        error = null;
        try {
            await addTagToPage(pageId, tag);
            // Re-read from the server so the chip shows the
            // normalized form (lowercase, whitespace-collapsed) rather
            // than whatever the user typed.
            tags = await getMyTagsForPage(pageId);
            draft = '';
            onChange?.(tags);
            void refreshSuggestions();
        } catch (e) {
            error = (e as Error).message;
        } finally {
            busy = false;
            // The input is `disabled={busy}` during the request, which
            // makes browsers drop focus. Restore it so the user can
            // immediately type the next tag in a "tag, tag, tag" flow
            // without clicking back into the field.
            queueMicrotask(() => inputEl?.focus());
        }
    }

    async function remove(tag: string) {
        if (busy) return;
        busy = true;
        error = null;
        // Optimistic — slice it out, revert on failure.
        const previous = tags;
        tags = tags.filter((t) => t !== tag);
        try {
            await removeTagFromPage(pageId, tag);
            onChange?.(tags);
        } catch (e) {
            tags = previous;
            error = (e as Error).message;
        } finally {
            busy = false;
        }
    }

    function onSubmit(e: SubmitEvent) {
        e.preventDefault();
        void add(draft);
    }

    function onInput(e: Event) {
        draft = (e.target as HTMLInputElement).value;
        void refreshSuggestions();
    }

    function onKeydown(e: KeyboardEvent) {
        // Enter is handled by the form submit, but support comma as a
        // chip separator since users type tag lists.
        if (e.key === ',') {
            e.preventDefault();
            void add(draft);
        }
    }

    // Suggestions minus already-present tags, since adding a present
    // tag is a no-op and clutters the row.
    const filteredSuggestions = $derived(
        suggestions.filter((s) => !tags.includes(s.tag))
    );
</script>

<div class="panel" data-testid="add-tags-panel">
    {#if loading}
        <p class="status">Loading tags…</p>
    {:else}
        <div class="chips" data-testid="add-tags-chips">
            {#each tags as t (t)}
                <span class="chip">
                    <span>{t}</span>
                    <button
                        type="button"
                        class="chip-x"
                        aria-label={`Remove ${t}`}
                        disabled={busy}
                        onclick={() => remove(t)}
                        data-testid={`add-tags-remove-${t}`}
                    >
                        <X size={12} aria-hidden="true" />
                    </button>
                </span>
            {/each}
            {#if tags.length === 0}
                <span class="hint">No tags yet — add one below.</span>
            {/if}
        </div>
    {/if}

    <form class="input-row" onsubmit={onSubmit} action="javascript:void(0)">
        <input
            bind:this={inputEl}
            type="text"
            value={draft}
            oninput={onInput}
            onkeydown={onKeydown}
            placeholder="Type a tag and press Enter"
            aria-label="New tag"
            maxlength="64"
            disabled={busy}
            data-testid="add-tags-input"
        />
        <button
            type="submit"
            class="add-btn"
            disabled={!draft.trim() || busy}
            data-testid="add-tags-submit"
        >
            Add
        </button>
    </form>

    {#if filteredSuggestions.length > 0}
        <div class="suggestions" data-testid="add-tags-suggestions">
            <p class="suggestions-label">Suggestions</p>
            <div class="suggestion-chips">
                {#each filteredSuggestions as s (s.tag)}
                    <button
                        type="button"
                        class="suggestion-chip"
                        disabled={busy}
                        onclick={() => add(s.tag)}
                        data-testid={`add-tags-suggest-${s.tag}`}
                    >
                        + {s.tag}
                    </button>
                {/each}
            </div>
        </div>
    {/if}

    {#if error}
        <p class="error" role="alert" data-testid="add-tags-error">{error}</p>
    {/if}
</div>

<style>
    .panel {
        display: flex;
        flex-direction: column;
        gap: var(--space-3);
    }

    .status,
    .hint {
        color: var(--text-muted);
        margin: 0;
    }

    .chips {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-1);
        min-height: 32px;
        align-items: center;
    }

    .chip {
        display: inline-flex;
        align-items: center;
        gap: var(--space-1);
        background: var(--primary-soft-bg);
        color: var(--text);
        padding: 2px var(--space-2);
        border-radius: var(--radius-pill);
        font-size: var(--font-sm);
    }

    .chip-x {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: transparent;
        color: var(--text-muted);
        border: 0;
        padding: 0;
        cursor: pointer;
        line-height: 0;
    }

    .chip-x:hover:not(:disabled) {
        color: var(--text);
    }

    .input-row {
        display: flex;
        gap: var(--space-2);
        align-items: center;
    }

    .input-row input {
        flex: 1;
        min-width: 0;
    }

    .add-btn {
        background: var(--primary);
        color: var(--primary-contrast);
        border: 1px solid var(--primary);
        padding: 0 var(--space-3);
        height: 36px;
    }

    .add-btn:hover:not(:disabled) {
        background: var(--primary-hover);
        border-color: var(--primary-hover);
    }

    .suggestions-label {
        margin: 0 0 var(--space-1);
        color: var(--text-muted);
        font-size: var(--font-xs);
    }

    .suggestion-chips {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-1);
    }

    .suggestion-chip {
        background: var(--surface);
        color: var(--text);
        border: 1px solid var(--border);
        padding: 2px var(--space-2);
        border-radius: var(--radius-pill);
        font-size: var(--font-sm);
        cursor: pointer;
    }

    .suggestion-chip:hover:not(:disabled) {
        background: var(--surface-elevated);
    }

    .error {
        color: var(--danger);
        margin: 0;
    }
</style>
