<script lang="ts">
    import AdaptiveDialog from './AdaptiveDialog.svelte';
    import {
        addMangaToCollection,
        createCollection,
        listMyCollections,
        getMyCollectionsContaining,
        removeMangaFromCollection,
        type CollectionSummary
    } from '$lib/api/collections';
    import {
        addPageToCollection,
        getMyCollectionsContainingPage,
        removePageFromCollection
    } from '$lib/api/page_collections';
    import Plus from '@lucide/svelte/icons/plus';

    /**
     * Discriminated-union target so the same modal serves both the
     * manga-detail "Add to collection" flow and the reader's page
     * context menu. The list / pre-check / toggle / create-and-add
     * scaffolding is identical for both — only the leaf API call
     * differs, dispatched through `loadContaining` / `addTo` /
     * `removeFrom` below.
     */
    export type Target =
        | { kind: 'manga'; id: string }
        | { kind: 'page'; id: string };

    let {
        open,
        target,
        onClose
    }: {
        open: boolean;
        target: Target;
        onClose: () => void;
    } = $props();

    let collections = $state<CollectionSummary[]>([]);
    let containingIds = $state<Set<string>>(new Set());
    let busyIds = $state<Set<string>>(new Set());
    let newName = $state('');
    let creating = $state(false);
    let loading = $state(false);
    let error: string | null = $state(null);

    // Refetch on open and whenever the target changes (e.g., the user
    // right-clicks a different page while the modal is in flight). The
    // dependency on `target.id` is explicit so the $effect re-runs.
    $effect(() => {
        if (open) {
            // Touching target.id wires the reactive dependency.
            void target.id;
            void load();
        }
    });

    async function loadContaining(t: Target): Promise<string[]> {
        return t.kind === 'manga'
            ? getMyCollectionsContaining(t.id)
            : getMyCollectionsContainingPage(t.id);
    }

    async function addTo(collectionId: string, t: Target): Promise<void> {
        return t.kind === 'manga'
            ? addMangaToCollection(collectionId, t.id)
            : addPageToCollection(collectionId, t.id);
    }

    async function removeFrom(collectionId: string, t: Target): Promise<void> {
        return t.kind === 'manga'
            ? removeMangaFromCollection(collectionId, t.id)
            : removePageFromCollection(collectionId, t.id);
    }

    async function load() {
        loading = true;
        error = null;
        try {
            const [page, ids] = await Promise.all([
                listMyCollections({ limit: 200 }),
                loadContaining(target)
            ]);
            collections = page.items;
            containingIds = new Set(ids);
        } catch (e) {
            error = (e as Error).message;
        } finally {
            loading = false;
        }
    }

    function withAdd<T>(s: Set<T>, v: T): Set<T> {
        const n = new Set(s);
        n.add(v);
        return n;
    }
    function withDelete<T>(s: Set<T>, v: T): Set<T> {
        const n = new Set(s);
        n.delete(v);
        return n;
    }

    async function toggle(collection: CollectionSummary) {
        if (busyIds.has(collection.id)) return;
        const wasIn = containingIds.has(collection.id);
        containingIds = wasIn
            ? withDelete(containingIds, collection.id)
            : withAdd(containingIds, collection.id);
        busyIds = withAdd(busyIds, collection.id);
        try {
            if (wasIn) {
                await removeFrom(collection.id, target);
                // manga_count drifts when pages are toggled — the
                // backend doesn't track page_count yet, so we only
                // adjust the count for the manga path. Page targets
                // leave manga_count as-is.
                if (target.kind === 'manga') {
                    collection.manga_count = Math.max(0, collection.manga_count - 1);
                }
            } else {
                await addTo(collection.id, target);
                if (target.kind === 'manga') {
                    collection.manga_count += 1;
                }
            }
        } catch (e) {
            containingIds = wasIn
                ? withAdd(containingIds, collection.id)
                : withDelete(containingIds, collection.id);
            error = (e as Error).message;
        } finally {
            busyIds = withDelete(busyIds, collection.id);
        }
    }

    async function createAndAdd() {
        const name = newName.trim();
        if (!name || creating) return;
        creating = true;
        error = null;
        try {
            const created = await createCollection({ name });
            await addTo(created.id, target);
            collections = [
                {
                    ...created,
                    manga_count: target.kind === 'manga' ? 1 : 0,
                    sample_covers: []
                },
                ...collections
            ];
            containingIds = new Set([...containingIds, created.id]);
            newName = '';
        } catch (e) {
            error = (e as Error).message;
        } finally {
            creating = false;
        }
    }

    function onCreateSubmit(e: SubmitEvent) {
        e.preventDefault();
        void createAndAdd();
    }
</script>

<AdaptiveDialog {open} {onClose} title="Add to collection" size="md" testid="add-to-collection-modal">
    {#if loading}
        <p class="status">Loading your collections…</p>
    {:else if error}
        <p class="error" role="alert" data-testid="add-to-collection-error">{error}</p>
    {:else if collections.length === 0}
        <p class="status" data-testid="no-collections">
            You don't have any collections yet. Create one below to get started.
        </p>
    {:else}
        <ul class="collection-list">
            {#each collections as c (c.id)}
                {@const checked = containingIds.has(c.id)}
                {@const busy = busyIds.has(c.id)}
                <li>
                    <label class="row" class:checked>
                        <input
                            type="checkbox"
                            {checked}
                            disabled={busy}
                            onchange={() => toggle(c)}
                            data-testid={`collection-toggle-${c.id}`}
                        />
                        <span class="row-label">
                            <span class="row-name">{c.name}</span>
                            {#if target.kind === 'manga'}
                                <!-- The count is the manga count only.
                                     Suppress it on page targets to
                                     avoid the rot of showing "0
                                     mangas" beside a collection that
                                     has 12 saved pages — the user
                                     would read the modal as broken.
                                     A backend page_count is a
                                     follow-up. -->
                                <span class="row-count">
                                    {c.manga_count}
                                    {c.manga_count === 1 ? 'manga' : 'mangas'}
                                </span>
                            {/if}
                        </span>
                    </label>
                </li>
            {/each}
        </ul>
    {/if}

    <form
        class="create-form"
        onsubmit={onCreateSubmit}
        action="javascript:void(0)"
    >
        <input
            type="text"
            bind:value={newName}
            maxlength="64"
            placeholder="Create new collection"
            aria-label="New collection name"
            data-testid="new-collection-name"
        />
        <button
            type="submit"
            class="create-btn"
            disabled={!newName.trim() || creating}
            data-testid="create-collection-btn"
        >
            <Plus size={14} aria-hidden="true" />
            <span>{creating ? 'Creating…' : 'Create + add'}</span>
        </button>
    </form>
</AdaptiveDialog>

<style>
    .status {
        color: var(--text-muted);
    }

    .error {
        color: var(--danger);
        margin: 0 0 var(--space-2);
    }

    .collection-list {
        list-style: none;
        padding: 0;
        margin: 0 0 var(--space-3);
        display: flex;
        flex-direction: column;
        gap: var(--space-1);
        max-height: 16rem;
        overflow-y: auto;
    }

    .row {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        padding: var(--space-2);
        border-radius: var(--radius-md);
        cursor: pointer;
        transition: background var(--transition);
    }

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

    .row.checked {
        background: var(--primary-soft-bg);
    }

    .row-label {
        display: flex;
        flex-direction: column;
        min-width: 0;
    }

    .row-name {
        color: var(--text);
        font-weight: var(--weight-medium);
    }

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

    .create-form {
        display: flex;
        gap: var(--space-2);
        align-items: center;
        padding-top: var(--space-3);
        border-top: 1px solid var(--border);
    }

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

    .create-btn {
        display: inline-flex;
        align-items: center;
        gap: var(--space-1);
        background: var(--primary);
        color: var(--primary-contrast);
        border: 1px solid var(--primary);
        padding: 0 var(--space-3);
        height: 36px;
        white-space: nowrap;
    }

    .create-btn:hover:not(:disabled) {
        background: var(--primary-hover);
        border-color: var(--primary-hover);
    }
</style>
