<script lang="ts">
    import { page } from '$app/stores';
    import { goto } from '$app/navigation';
    import SegmentedControl from '$lib/components/SegmentedControl.svelte';
    import TaggedPageRow from '$lib/components/TaggedPageRow.svelte';
    import TaggedChapterRow from '$lib/components/TaggedChapterRow.svelte';
    import TaggedMangaRow from '$lib/components/TaggedMangaRow.svelte';
    import SearchResultsSkeleton from '$lib/components/SearchResultsSkeleton.svelte';
    import { CONTENT_WARNINGS, type ContentWarning } from '$lib/api/page_tags';
    import X from '@lucide/svelte/icons/x';

    let { data } = $props();

    // Free-text content-search draft, seeded from the URL. The input is
    // user-edited from here; the URL stays the source of truth on submit.
    // svelte-ignore state_referenced_locally
    let textDraft = $state(data.text);

    type View = 'pages' | 'chapters' | 'mangas';
    type Order = 'desc' | 'asc';

    const VIEWS: { label: string; value: View }[] = [
        { label: 'Pages', value: 'pages' },
        { label: 'Chapters', value: 'chapters' },
        { label: 'Mangas', value: 'mangas' }
    ];

    const ORDERS: { label: string; value: Order }[] = [
        { label: 'Most pages', value: 'desc' },
        { label: 'Fewest pages', value: 'asc' }
    ];

    /**
     * Update a single URL param and re-trigger the SvelteKit loader
     * via goto with replaceState — same pattern as /library tab
     * persistence. Passing `null` (or the param's default value)
     * removes the param so the URL stays clean.
     */
    function setParam(key: string, value: string | null) {
        const url = new URL($page.url);
        if (value == null || value === '') url.searchParams.delete(key);
        else url.searchParams.set(key, value);
        void goto(url.toString(), {
            replaceState: true,
            keepFocus: true,
            noScroll: true
        });
    }

    /** Update several URL params in one navigation. */
    function setParams(updates: Record<string, string | null>) {
        const url = new URL($page.url);
        for (const [k, v] of Object.entries(updates)) {
            if (v == null || v === '') url.searchParams.delete(k);
            else url.searchParams.set(k, v);
        }
        void goto(url.toString(), {
            replaceState: true,
            keepFocus: true,
            noScroll: true
        });
    }

    function submitContentSearch(e: SubmitEvent) {
        e.preventDefault();
        setParam('text', textDraft.trim() || null);
    }

    /** Tri-state per warning: none → include → exclude → none. */
    function cycleWarning(w: ContentWarning) {
        const inc = new Set(data.cwInclude);
        const exc = new Set(data.cwExclude);
        if (inc.has(w)) {
            inc.delete(w);
            exc.add(w);
        } else if (exc.has(w)) {
            exc.delete(w);
        } else {
            inc.add(w);
        }
        setParams({
            cw_include: [...inc].join(',') || null,
            cw_exclude: [...exc].join(',') || null
        });
    }

    function setView(v: View) {
        setParam('view', v === 'pages' ? null : v);
    }
    function setOrder(o: Order) {
        setParam('order', o === 'desc' ? null : 'asc');
    }
    function setTag(t: string | null) {
        // Reset view + order when the tag changes, so a user picking
        // a fresh tag lands on the Pages tab with default sort.
        const url = new URL($page.url);
        url.searchParams.delete('view');
        url.searchParams.delete('order');
        if (t == null || t === '') url.searchParams.delete('tag');
        else url.searchParams.set('tag', t);
        void goto(url.toString(), {
            replaceState: true,
            keepFocus: true,
            noScroll: true
        });
    }

    // Tag input draft for the autocomplete chip cloud / dropdown.
    let draft = $state('');
    const filteredCloud = $derived.by(() => {
        const q = draft.trim().toLowerCase();
        if (!q) return data.distinct;
        return data.distinct.filter((s) => s.tag.startsWith(q));
    });

    function onSubmitTag(e: SubmitEvent) {
        e.preventDefault();
        const q = draft.trim().toLowerCase();
        if (!q) return;
        // Pick the typed text directly — the backend normalizes and
        // returns an empty result if the tag doesn't exist for this
        // user, which the empty-state line handles cleanly.
        setTag(q);
        draft = '';
    }
</script>

<svelte:head>
    <title>Mangalord | Page search</title>
</svelte:head>

<h1 class="heading">Page search</h1>

{#if !data.authenticated}
    <p class="hint" data-testid="search-signin">
        <a href="/login?next=/search">Sign in</a> to search your tags.
    </p>
{:else if data.error}
    <p class="error" role="alert" data-testid="search-error">{data.error}</p>
{:else}
    <!-- Content search: free-text over OCR + scene text, plus content
         warning include/exclude toggles. Takes precedence over tag
         browsing when a text query or an included warning is active. -->
    <section class="filter content-search" aria-label="Content search">
        <form
            class="tag-form"
            onsubmit={submitContentSearch}
            action="javascript:void(0)"
        >
            <input
                type="text"
                bind:value={textDraft}
                placeholder="Search page text (OCR + scene)…"
                aria-label="Text search"
                data-testid="search-text-input"
            />
        </form>
        <div class="cw-filters" data-testid="search-cw-filters">
            <span class="cw-group-label">Warnings</span>
            {#each CONTENT_WARNINGS as w (w)}
                <button
                    type="button"
                    class="cw-toggle"
                    class:include={data.cwInclude.includes(w)}
                    class:exclude={data.cwExclude.includes(w)}
                    onclick={() => cycleWarning(w)}
                    aria-pressed={data.cwInclude.includes(w) ||
                        data.cwExclude.includes(w)}
                    title="Click to include, again to exclude, again to clear"
                    data-testid={`cw-toggle-${w}`}
                >
                    {#if data.cwInclude.includes(w)}＋{:else if data.cwExclude.includes(w)}−{/if}
                    {w}
                </button>
            {/each}
        </div>
    </section>

    {#if data.contentSearch}
        {#await data.results}
            <SearchResultsSkeleton />
        {:then r}
            {#if r.error}
                <p class="error" role="alert" data-testid="search-results-error">
                    Couldn't load results: {r.error}
                </p>
            {:else if r.results.length === 0}
                <p class="hint" data-testid="search-content-empty">
                    No pages match this search.
                </p>
            {:else}
                <ul class="list" data-testid="search-content-list">
                    {#each r.results as p (p.page_id)}
                        <TaggedPageRow
                            item={{ ...p, tag: '', tagged_at: '' }}
                            showTagPill={false}
                            testid={`search-result-${p.page_id}`}
                        />
                    {/each}
                </ul>
            {/if}
        {/await}
    {:else}
    <!-- Tag filter. When a tag is selected it's shown as a chip with
         an x to clear; when none is, the input doubles as the entry
         point + autocomplete. -->
    <section class="filter" aria-label="Tag filter">
        {#if data.tag}
            <div class="active-tag" data-testid="search-active-tag">
                <span class="tag-pill">
                    {data.tag}
                    <button
                        type="button"
                        class="tag-clear"
                        aria-label="Clear tag"
                        onclick={() => setTag(null)}
                        data-testid="search-clear-tag"
                    >
                        <X size={12} aria-hidden="true" />
                    </button>
                </span>
            </div>
        {:else}
            <form class="tag-form" onsubmit={onSubmitTag} action="javascript:void(0)">
                <input
                    type="text"
                    bind:value={draft}
                    placeholder="Type a tag and press Enter"
                    aria-label="Tag"
                    data-testid="search-tag-input"
                />
            </form>
        {/if}
    </section>

    {#if !data.tag}
        <!-- Empty state: chip cloud doubles as autocomplete result. -->
        {#if data.distinct.length === 0}
            <p class="hint" data-testid="search-no-tags">
                You haven't tagged any pages yet. Open a chapter and
                right-click (or long-press on mobile) a page to add a
                tag.
            </p>
        {:else if filteredCloud.length === 0}
            <p class="hint" data-testid="search-no-matches">
                No tags match "{draft}".
            </p>
        {:else}
            <p class="cloud-hint">Browse your tags</p>
            <div class="chip-cloud" data-testid="search-chip-cloud">
                {#each filteredCloud as s (s.tag)}
                    <button
                        type="button"
                        class="chip"
                        onclick={() => setTag(s.tag)}
                        data-testid={`search-chip-${s.tag}`}
                    >
                        {s.tag}
                        <span class="count">{s.count}</span>
                    </button>
                {/each}
            </div>
            <p class="empty-hint" data-testid="search-no-tag-selected">
                No tag selected. Pick one above to see matching pages,
                chapters, and mangas.
            </p>
        {/if}
    {:else}
        <!-- Tag selected: tabs + sort + results. -->
        <div class="tab-row">
            <SegmentedControl
                ariaLabel="Result type"
                value={data.view}
                options={VIEWS}
                onchange={setView}
                testid="search-tabs"
            />
        </div>

        {#if data.view !== 'pages'}
            <div class="tab-row">
                <SegmentedControl
                    ariaLabel="Sort"
                    value={data.order}
                    options={ORDERS}
                    onchange={setOrder}
                    testid="search-sort"
                />
            </div>
        {/if}

        {#await data.results}
            <SearchResultsSkeleton />
        {:then r}
            {#if r.error}
                <p class="error" role="alert" data-testid="search-results-error">
                    Couldn't load results: {r.error}
                </p>
            {:else if data.view === 'pages'}
                {#if r.pages.length === 0}
                    <p class="hint" data-testid="search-pages-empty">
                        No pages tagged with "{data.tag}".
                    </p>
                {:else}
                    <ul class="list" data-testid="search-pages-list">
                        {#each r.pages as p (p.page_id)}
                            <TaggedPageRow
                                item={p}
                                showTagPill={false}
                                testid={`search-page-row-${p.page_id}`}
                            />
                        {/each}
                    </ul>
                {/if}
            {:else if data.view === 'chapters'}
                {#if r.chapters.length === 0}
                    <p class="hint" data-testid="search-chapters-empty">
                        No chapters contain pages tagged with "{data.tag}".
                    </p>
                {:else}
                    <ul class="list" data-testid="search-chapters-list">
                        {#each r.chapters as c (c.chapter_id)}
                            <TaggedChapterRow
                                item={c}
                                testid={`search-chapter-row-${c.chapter_id}`}
                            />
                        {/each}
                    </ul>
                {/if}
            {:else}
                {#if r.mangas.length === 0}
                    <p class="hint" data-testid="search-mangas-empty">
                        No mangas contain pages tagged with "{data.tag}".
                    </p>
                {:else}
                    <ul class="list" data-testid="search-mangas-list">
                        {#each r.mangas as m (m.manga_id)}
                            <TaggedMangaRow
                                item={m}
                                testid={`search-manga-row-${m.manga_id}`}
                            />
                        {/each}
                    </ul>
                {/if}
            {/if}
        {/await}
    {/if}
    {/if}
{/if}

<style>
    .heading {
        margin-bottom: var(--space-3);
    }

    .hint,
    .empty-hint {
        color: var(--text-muted);
    }

    .empty-hint {
        margin-top: var(--space-4);
        text-align: center;
    }

    .cloud-hint {
        margin: var(--space-3) 0 var(--space-2);
        color: var(--text-muted);
        font-size: var(--font-sm);
    }

    .error {
        color: var(--danger);
    }

    .filter {
        margin-bottom: var(--space-3);
    }

    .tag-form input {
        width: 100%;
        max-width: 24rem;
    }

    .content-search {
        display: flex;
        flex-direction: column;
        gap: var(--space-2);
        padding-bottom: var(--space-3);
        border-bottom: 1px solid var(--border);
    }

    .cw-filters {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--space-1);
    }

    .cw-group-label {
        color: var(--text-muted);
        font-size: var(--font-sm);
        margin-right: var(--space-1);
    }

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

    .cw-toggle.include {
        background: var(--primary-soft-bg);
        color: var(--primary);
        border-color: var(--primary);
    }

    .cw-toggle.exclude {
        background: color-mix(in srgb, var(--danger) 14%, transparent);
        color: var(--danger);
        border-color: var(--danger);
    }

    .active-tag {
        display: flex;
        align-items: center;
    }

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

    .tag-clear {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: transparent;
        color: inherit;
        border: 0;
        padding: 0;
        cursor: pointer;
        line-height: 0;
    }

    .tag-clear:hover {
        color: var(--text);
    }

    .chip-cloud {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-1);
        margin-bottom: var(--space-3);
    }

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

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

    .count {
        color: var(--text-muted);
        font-size: var(--font-xs);
    }

    .tab-row {
        margin-bottom: var(--space-3);
        display: flex;
    }

    .tab-row :global(.segmented) {
        width: 100%;
    }

    .tab-row :global(.seg) {
        flex: 1;
    }

    .list {
        list-style: none;
        padding: 0;
        margin: 0;
        display: flex;
        flex-direction: column;
        gap: var(--space-3);
    }
</style>
