import { request, type Manga, type MangaStatus, type Page } from './client';
import type { ContentWarning } from './page_tags';

export type MangaSort = 'created' | 'updated' | 'title' | 'author';
export type SortOrder = 'asc' | 'desc';

export type AuthorRef = { id: string; name: string };
export type GenreRef = { id: string; name: string };
export type TagRef = { id: string; name: string; added_by: string | null };

/** Card shape returned by `GET /v1/mangas` — authors + genres, no tags. */
export type MangaCard = Manga & {
    authors: AuthorRef[];
    genres: GenreRef[];
};

/** Detail shape returned by `GET /v1/mangas/:id`. Includes user tags. */
export type MangaDetail = Manga & {
    authors: AuthorRef[];
    genres: GenreRef[];
    tags: TagRef[];
    /** Deduped union of content warnings across the manga's pages. */
    content_warnings: ContentWarning[];
    /** Total bytes of all this manga's stored chapter pages (cover
     * excluded). `null` when any page is unmeasured (show an em-dash);
     * `0` when there are no pages. */
    chapter_storage_bytes: number | null;
};

export type ListOptions = {
    search?: string;
    status?: MangaStatus;
    /** AND across the list — every id must be attached to the manga. */
    authorIds?: string[];
    genreIds?: string[];
    tagIds?: string[];
    /** Content warnings the manga must carry (all) / must not carry (any). */
    cwInclude?: ContentWarning[];
    cwExclude?: ContentWarning[];
    limit?: number;
    offset?: number;
    sort?: MangaSort;
    order?: SortOrder;
};

export type MangasPage = {
    items: MangaCard[];
    page: Page;
};

export async function listMangas(opts: ListOptions = {}): Promise<MangasPage> {
    const params = new URLSearchParams();
    if (opts.search) params.set('search', opts.search);
    if (opts.status) params.set('status', opts.status);
    if (opts.authorIds && opts.authorIds.length) {
        params.set('author_id', opts.authorIds.join(','));
    }
    if (opts.genreIds && opts.genreIds.length) {
        params.set('genre_id', opts.genreIds.join(','));
    }
    if (opts.tagIds && opts.tagIds.length) {
        params.set('tag_id', opts.tagIds.join(','));
    }
    if (opts.cwInclude && opts.cwInclude.length) {
        params.set('cw_include', opts.cwInclude.join(','));
    }
    if (opts.cwExclude && opts.cwExclude.length) {
        params.set('cw_exclude', opts.cwExclude.join(','));
    }
    if (opts.limit != null) params.set('limit', String(opts.limit));
    if (opts.offset != null) params.set('offset', String(opts.offset));
    if (opts.sort) params.set('sort', opts.sort);
    if (opts.order) params.set('order', opts.order);
    const qs = params.toString();
    return request<MangasPage>(`/v1/mangas${qs ? `?${qs}` : ''}`);
}

export async function getManga(id: string): Promise<MangaDetail> {
    return request<MangaDetail>(`/v1/mangas/${encodeURIComponent(id)}`);
}

/**
 * GET /v1/mangas/:id/similar — up to 5 mangas ranked by tag overlap with
 * `id`. Returns a plain `{ items }` object (a fixed top-N, not a paginated
 * collection), so we unwrap to the card array the page wants.
 */
export async function getSimilarMangas(id: string): Promise<MangaCard[]> {
    const res = await request<{ items: MangaCard[] }>(
        `/v1/mangas/${encodeURIComponent(id)}/similar`
    );
    // Defensive: a malformed 200 body (items omitted) must still yield an
    // array so the page's `similar.length` guard can't throw.
    return res.items ?? [];
}

export type NewManga = {
    title: string;
    status?: MangaStatus;
    /** Author display names; resolved server-side, case-insensitive. */
    authors?: string[];
    description?: string | null;
    alt_titles?: string[];
    genre_ids?: string[];
};

/**
 * POST /api/v1/mangas is multipart. The metadata part is JSON; the cover
 * part is the raw image bytes. The browser fills in the multipart boundary
 * automatically when `body` is a FormData, so we deliberately do not set
 * Content-Type ourselves.
 */
export async function createManga(
    input: NewManga,
    cover?: Blob
): Promise<MangaDetail> {
    const form = new FormData();
    form.append(
        'metadata',
        new Blob([JSON.stringify(input)], { type: 'application/json' })
    );
    if (cover) form.append('cover', cover);
    return request<MangaDetail>('/v1/mangas', { method: 'POST', body: form });
}

export type MangaPatch = {
    title?: string;
    status?: MangaStatus;
    description?: string | null;
    alt_titles?: string[];
    authors?: string[];
    genre_ids?: string[];
};

export async function updateManga(
    id: string,
    patch: MangaPatch
): Promise<MangaDetail> {
    return request<MangaDetail>(`/v1/mangas/${encodeURIComponent(id)}`, {
        method: 'PATCH',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(patch)
    });
}

/**
 * PUT /api/v1/mangas/:id/cover (multipart). Replaces the cover image and
 * returns the refreshed detail. As with createManga the browser fills in
 * the multipart boundary automatically, so we must NOT set Content-Type.
 */
export async function updateMangaCover(
    id: string,
    cover: Blob
): Promise<MangaDetail> {
    const form = new FormData();
    form.append('cover', cover);
    return request<MangaDetail>(
        `/v1/mangas/${encodeURIComponent(id)}/cover`,
        { method: 'PUT', body: form }
    );
}

/** DELETE /api/v1/mangas/:id/cover. Returns the refreshed detail. */
export async function deleteMangaCover(id: string): Promise<MangaDetail> {
    return request<MangaDetail>(
        `/v1/mangas/${encodeURIComponent(id)}/cover`,
        { method: 'DELETE' }
    );
}

export async function attachTag(
    mangaId: string,
    name: string
): Promise<TagRef> {
    return request<TagRef>(`/v1/mangas/${encodeURIComponent(mangaId)}/tags`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ name })
    });
}

export async function detachTag(mangaId: string, tagId: string): Promise<void> {
    await request<void>(
        `/v1/mangas/${encodeURIComponent(mangaId)}/tags/${encodeURIComponent(tagId)}`,
        { method: 'DELETE' }
    );
}

export type { Manga, MangaStatus, Page };
