import { number, string, z } from 'zod';
import type { components } from './openapi/schema';
import { arrayCoerce, commonQuerySchema, stringArrayCoerce, stringCoerce } from './common';

export type Book = components['schemas']['BookRead'];
export type BookFile = components['schemas']['FileMetadataRead'];
export type BookProgress = components['schemas']['BookProgressRead'];

/**
 * A file the library already held, so the upload did not store it again.
 *
 * `book_id` is null when the match was another file in the same upload — there is
 * no book to point at yet.
 */
export type DuplicateFile = components['schemas']['DuplicateFileRead'];
export type BooksUploadResult = components['schemas']['BooksUploadResult'];

/**
 * A stored book that may be the same book as another one.
 *
 * Weaker than `DuplicateFile`, and deliberately so: that one means the library holds
 * these exact bytes, this one means the metadata agrees. A second edition and a
 * translation both look like this, so nothing is ever refused on the strength of it.
 */
export type DuplicateBook = components['schemas']['DuplicateBookRead'];

/** A book that was imported, together with what it might be a second copy of. */
export type PossibleDuplicate = components['schemas']['PossibleDuplicateRead'];

/** Books the library holds that all look like copies of one book. */
export type DuplicateBookGroup = components['schemas']['DuplicateBookGroupRead'];

/** The metadata a reader resolved while merging, or while reviewing a provider. */
export type BookMetadataUpdate = components['schemas']['BookMetadataUpdate'];

/** Mirrors BookMerge in backend/src/chitai/schemas/book.py */
export const bookMergeSchema = z.object({
	library_id: z.coerce.number(),
	survivor_id: z.coerce.number(),
	merged_ids: z.array(z.coerce.number()).min(1, 'Pick at least one book to fold in'),
	// Passed through untouched — the backend validates it as BookMetadataUpdate, and
	// duplicating that shape here would be two places to keep in step for no gain.
	metadata: z.record(z.string(), z.unknown()).optional()
});

/** Mirrors DuplicateDismissal in backend/src/chitai/schemas/book.py */
export const duplicateDismissalSchema = z.object({
	book_a_id: z.coerce.number(),
	book_b_id: z.coerce.number()
});

export const bookQuerySchema = commonQuerySchema.extend({
	libraries: stringArrayCoerce,
	authors: stringArrayCoerce,
	publishers: stringArrayCoerce,
	tags: stringArrayCoerce,
	shelves: stringArrayCoerce,
	progress: arrayCoerce(z.enum(['unread', 'in_progress', 'read']))
		.nullable()
		.optional(),
	ids: stringArrayCoerce,
	cacheTimestamp: z.number().optional()
});

const identifiersSchema = z
	.string()
	.transform((str, ctx) => {
		try {
			return JSON.parse(str);
		} catch {
			ctx.addIssue({
				code: 'custom',
				message: 'Must be a valid JSON string'
			});
			return z.NEVER;
		}
	})
	.pipe(
		z.record(z.string(), z.string()) // Key-value pairs, both strings
	);

export const editBookMetadataSchema = z.object({
	book_id: z.string(),
	title: string().min(1, 'Title cannot be empty'),
	subtitle: string().optional(),
	authors: z.array(z.string()).default([]),
	tags: z.array(z.string()).default([]),
	description: z.string().optional(),
	publisher: z.string().optional(),
	published_date: z
		.string()
		.optional()
		.transform((val) => (val ? new Date(val) : undefined)),
	series: z.string().optional(),
	series_position: z.string().optional(),
	identifiers: identifiersSchema,
	edition: z.number().optional(),
	pages: z.number().optional(),
	language: z.string().optional()
});

export const bookCoverUpload = z.object({
	book_id: stringCoerce,
	file: z.file()
});

export const booksUpload = z.object({
	library_id: stringCoerce,
	files: z.array(z.file()).min(1, 'At least one file must be uploaded')
});

export const bookFilesUpload = z.object({
	book_id: stringCoerce,
	files: z.array(z.file()).min(1, 'At least one file must be uploaded')
});

export const deleteBooksSchema = z.object({
	book_ids: stringArrayCoerce, // TODO Could also set minimum length of 1
	delete_files: z.boolean(),
	library_id: stringCoerce
});

export const deleteBookFilesSchema = z.object({
	book_id: stringCoerce,
	file_ids: stringArrayCoerce,
	delete_files: z.boolean()
});

// Mirrors BookProgressCreate in backend/src/chitai/schemas/book.py
export const updateBookProgressSchema = z.object({
	book_ids: stringArrayCoerce,
	percentage: number().min(0).max(1),
	completed: z.boolean().optional().default(false),
	epub_cfi: z.string().optional(),
	epub_xpointer: z.string().optional(),
	pdf_page: z.number().int().optional()
});

export type BookQuery = z.infer<typeof bookQuerySchema>;
export type BookEditMetadata = z.infer<typeof editBookMetadataSchema>;
export type DeleteBook = z.infer<typeof deleteBooksSchema>;
export type DeleteBookFiles = z.infer<typeof deleteBookFilesSchema>;
export type DuplicateDismissal = z.infer<typeof duplicateDismissalSchema>;
export type BookMerge = z.infer<typeof bookMergeSchema>;
export type UpdateBookProgress = z.infer<typeof updateBookProgressSchema>;
