import { desc, like } from "drizzle-orm";
import { browser } from "$app/environment";
import { asset } from "$app/paths";
import { animeTable } from "$lib/db/schema";

import { db, overwriteDatabaseFile } from "./client-db";

/**
 * Client-side, read-only AMQ database access.
 *
 * - Seeds SQLocal's managed database file from `static/data/amq.sqlite`
 * - Uses a version key so you control when clients refresh their local snapshot
 * - Exposes a small query helper to fetch a short anime list for display
 *
 * Notes:
 * - This module MUST only be used in the browser. Guard with `browser`, or call
 *   the exported functions from `onMount`.
 * - `overwriteDatabaseFile()` resets connections, so only call it when needed.
 */

/**
 * Bump this when you ship a new `static/data/amq.sqlite`.
 * This is intentionally manual so you control when clients refresh.
 */
export const AMQ_DB_SEED_VERSION = 1;

/**
 * The file name SQLocal uses internally (you can change this; it does not need
 * to match the static seed file name).
 */
const SQLOCAL_DB_FILE_NAME = "amq.sqlite3";

/**
 * Where the shipped, static database lives.
 * `static/data/amq.sqlite` -> served at `/data/amq.sqlite`
 */
const SEED_ASSET_PATH = "/data/amq.sqlite";

const seededStorageKey = (version: number) => `amq.sqlocal.seeded.v${version}`;

let initPromise: Promise<void> | null = null;

function ensureBrowser() {
	if (!browser) {
		throw new Error(
			"Client AMQ DB can only be used in the browser (SSR is not supported).",
		);
	}
}

/**
 * Ensure the SQLocal DB file is seeded with the shipped snapshot.
 * No-ops once seeded for the current seed version.
 */
export async function ensureAmqDbSeeded(
	opts: { version?: number; force?: boolean } = {},
): Promise<void> {
	ensureBrowser();

	const version = opts.version ?? AMQ_DB_SEED_VERSION;

	// Serialize seeding so multiple callers don't race.
	if (initPromise) return initPromise;

	initPromise = (async () => {
		const key = seededStorageKey(version);
		const alreadySeeded = localStorage.getItem(key) === "1";

		if (!opts.force && alreadySeeded) return;

		// Prefer streaming when available (better for large DB files).
		// Use `asset(...)` so this works under `paths.assets`/`base` setups.
		const url = asset(SEED_ASSET_PATH);

		const res = await fetch(url, { cache: "no-cache" });
		if (!res.ok) {
			throw new Error(
				`Failed to fetch AMQ seed DB from ${url}: ${res.status} ${res.statusText}`,
			);
		}

		const stream = res.body;
		if (stream) {
			await overwriteDatabaseFile(stream, async () => {
				// Read-only browsing: nothing required here.
				// If you later need to run PRAGMAs or migrations, do it here.
			});
		} else {
			const blob = await res.blob();
			await overwriteDatabaseFile(blob, async () => {});
		}

		// Mark this version as seeded. (Optionally clear older keys, but not required.)
		localStorage.setItem(key, "1");
	})();

	try {
		await initPromise;
	} finally {
		// Allow future seed attempts if this one threw.
		// If it succeeded, callers will no-op due to localStorage key.
		if (
			localStorage.getItem(
				seededStorageKey(opts.version ?? AMQ_DB_SEED_VERSION),
			) !== "1"
		) {
			initPromise = null;
		}
	}
}

/**
 * Helper: fetch a short list of anime for a "browse" page.
 *
 * Uses Drizzle against the SQLocal-backed database (sqlite-proxy driver).
 */
export async function getAnimeList(limit = 25): Promise<
	Array<{
		annId: number;
		mainName: string;
		year: number;
		seasonId: number;
		malId: number;
	}>
> {
	ensureBrowser();
	await ensureAmqDbSeeded();

	// Clamp to something reasonable to avoid accidental huge renders.
	const safeLimit = Math.max(1, Math.min(200, Math.floor(limit)));

	const rows = await db
		.select({
			annId: animeTable.annId,
			mainName: animeTable.mainName,
			year: animeTable.year,
			seasonId: animeTable.seasonId,
			malId: animeTable.malId,
		})
		.from(animeTable)
		.orderBy(
			desc(animeTable.year),
			desc(animeTable.seasonId),
			desc(animeTable.annId),
		)
		.limit(safeLimit);

	return rows;
}

/**
 * Helper: basic text search by `mainName` (and optionally other columns).
 * This is deliberately small and safe for client-side browsing.
 */
export async function searchAnimeByName(
	query: string,
	limit = 25,
): Promise<
	Array<{ annId: number; mainName: string; year: number; seasonId: number }>
> {
	ensureBrowser();
	await ensureAmqDbSeeded();

	const q = query.trim();
	if (!q) return [];

	const safeLimit = Math.max(1, Math.min(200, Math.floor(limit)));

	// Simple LIKE search. If you want better search later, consider FTS5 in the shipped snapshot.
	// Note: we don't escape %/_ here; if you need literal matching, we'll switch to a raw SQL
	// fragment with an explicit ESCAPE clause.
	const pattern = `%${q}%`;

	const rows = await db
		.select({
			annId: animeTable.annId,
			mainName: animeTable.mainName,
			year: animeTable.year,
			seasonId: animeTable.seasonId,
		})
		.from(animeTable)
		.where(like(animeTable.mainName, pattern))
		.orderBy(
			desc(animeTable.year),
			desc(animeTable.seasonId),
			desc(animeTable.annId),
		)
		.limit(safeLimit);

	return rows;
}

// Re-export for convenience in components that need to ensure seeding first.
export { db, overwriteDatabaseFile, SQLOCAL_DB_FILE_NAME };
