import { ApiError } from '$lib/api/client';
import {
    listMyDistinctPageTags,
    listMyPageTags,
    listTaggedChapters,
    listTaggedMangas,
    searchPages,
    CONTENT_WARNINGS,
    type ContentWarning,
    type PageSearchItem,
    type PageTagSummary,
    type TaggedChapterAggregate,
    type TaggedMangaAggregate,
    type TaggedPageItem
} from '$lib/api/page_tags';
import type { PageLoad } from './$types';

export const ssr = false;

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

/** Parse a CSV of content warnings, keeping only valid vocabulary values. */
function parseWarnings(raw: string | null): ContentWarning[] {
    if (!raw) return [];
    return raw
        .split(',')
        .map((s) => s.trim().toLowerCase())
        .filter((s): s is ContentWarning =>
            (CONTENT_WARNINGS as string[]).includes(s)
        );
}

/**
 * Loader for the /search page. URL is the source of truth — refresh
 * or share a link lands the user on the same view.
 *
 * - `?tag=` exact-match tag filter. Empty → no results, just the
 *   chip cloud for browsing.
 * - `?view=pages|chapters|mangas` — defaults to `pages` when omitted.
 * - `?order=desc|asc` — only meaningful for chapters/mangas tabs.
 *   `desc` (most matches first) is the default.
 *
 * `?text=` is reserved for the planned OCR text-search input. The
 * backend rejects it with 501 + stable code
 * `text_search_not_yet_supported` today; the frontend never sets it.
 */
export const load: PageLoad = async ({ url }) => {
    const tag = url.searchParams.get('tag');
    const viewParam = url.searchParams.get('view');
    const view: View =
        viewParam === 'chapters' || viewParam === 'mangas' ? viewParam : 'pages';
    const order: Order = url.searchParams.get('order') === 'asc' ? 'asc' : 'desc';

    // Content-search inputs (OCR/scene text + content-warning filters).
    const text = (url.searchParams.get('text') ?? '').trim();
    const cwInclude = parseWarnings(url.searchParams.get('cw_include'));
    const cwExclude = parseWarnings(url.searchParams.get('cw_exclude'));
    // A positive filter (text or an included warning) is required for the
    // backend; cw_exclude alone can't drive a search.
    const contentSearch = text !== '' || cwInclude.length > 0;

    const empty = {
        authenticated: true,
        tag,
        view,
        order,
        text,
        cwInclude,
        cwExclude,
        contentSearch,
        distinct: [] as PageTagSummary[],
        pages: [] as TaggedPageItem[],
        chapters: [] as TaggedChapterAggregate[],
        mangas: [] as TaggedMangaAggregate[],
        results: [] as PageSearchItem[],
        total: 0,
        error: null as string | null
    };

    try {
        const distinct = await listMyDistinctPageTags(undefined, 100);

        // Content search takes precedence over tag browsing.
        if (contentSearch) {
            const r = await searchPages({
                text: text || undefined,
                cwInclude,
                cwExclude,
                limit: 100
            });
            return { ...empty, distinct, results: r.items, total: r.page.total ?? 0 };
        }

        // No tag selected → just the chip cloud.
        if (!tag) return { ...empty, distinct };

        if (view === 'chapters') {
            const r = await listTaggedChapters({ tag, order, limit: 100 });
            return {
                ...empty,
                distinct,
                chapters: r.items,
                total: r.page.total ?? 0
            };
        }
        if (view === 'mangas') {
            const r = await listTaggedMangas({ tag, order, limit: 100 });
            return {
                ...empty,
                distinct,
                mangas: r.items,
                total: r.page.total ?? 0
            };
        }
        const r = await listMyPageTags({ tag, limit: 100 });
        return {
            ...empty,
            distinct,
            pages: r.items,
            total: r.page.total ?? 0
        };
    } catch (e) {
        if (e instanceof ApiError && e.status === 401) {
            return { ...empty, authenticated: false };
        }
        if (e instanceof ApiError) {
            return { ...empty, error: e.message };
        }
        throw e;
    }
};
