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=` drives OCR full-text search against the page OCR index (the
 * active OCR backend populates it). A non-empty `text` (or an included
 * content-warning) switches the page into content-search mode below.
 */
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 base = {
        authenticated: true,
        tag,
        view,
        order,
        text,
        cwInclude,
        cwExclude,
        contentSearch,
        distinct: [] as PageTagSummary[],
        error: null as string | null
    };

    const emptyResults = {
        pages: [] as TaggedPageItem[],
        chapters: [] as TaggedChapterAggregate[],
        mangas: [] as TaggedMangaAggregate[],
        results: [] as PageSearchItem[],
        total: 0,
        error: null as string | null
    };

    // The chip cloud + auth gate come from `distinct`, so it's awaited. A 401
    // here means "not signed in"; any other error is surfaced inline. The
    // view-specific results are streamed (see below).
    let distinct: PageTagSummary[];
    try {
        distinct = await listMyDistinctPageTags(undefined, 100);
    } catch (e) {
        if (e instanceof ApiError && e.status === 401) {
            return { ...base, authenticated: false, results: Promise.resolve(emptyResults) };
        }
        // Any other HTTP error or a raw network failure (a non-ApiError
        // TypeError) is surfaced inline rather than escaping to the framework
        // error page.
        const message = e instanceof ApiError ? e.message : 'Something went wrong.';
        return { ...base, error: message, results: Promise.resolve(emptyResults) };
    }

    // Streamed so the results list shows a skeleton while it (re-)queries on
    // every tag / view / sort / text change instead of freezing the old list.
    const results = (async () => {
        try {
            // Content search takes precedence over tag browsing.
            if (contentSearch) {
                const r = await searchPages({
                    text: text || undefined,
                    cwInclude,
                    cwExclude,
                    limit: 100
                });
                return { ...emptyResults, results: r.items, total: r.page.total ?? 0 };
            }
            // No tag selected → just the chip cloud, no results.
            if (!tag) return emptyResults;
            if (view === 'chapters') {
                const r = await listTaggedChapters({ tag, order, limit: 100 });
                return { ...emptyResults, chapters: r.items, total: r.page.total ?? 0 };
            }
            if (view === 'mangas') {
                const r = await listTaggedMangas({ tag, order, limit: 100 });
                return { ...emptyResults, mangas: r.items, total: r.page.total ?? 0 };
            }
            const r = await listMyPageTags({ tag, limit: 100 });
            return { ...emptyResults, pages: r.items, total: r.page.total ?? 0 };
        } catch (e) {
            const message = e instanceof ApiError ? e.message : 'Something went wrong.';
            return { ...emptyResults, error: message };
        }
    })();

    return { ...base, distinct, results };
};
