import { Match, Method, Router } from '@atelier/router.ts';
import { OrPromise } from '@atelier/util.ts';

import { Database } from '@db/sqlite';

import { viewShelf, viewShelves } from '@/backend/routes/shelves.ts';
import { postItem, viewCreateItem, viewItem, viewItems } from '@/backend/routes/items.ts';
import { viewHome } from '@/backend/routes/home.ts';

const { GET, POST, PATCH } = Method;

export type RequestCtx = {
	pathParams: Record<string, string>;
	db: Database;
};

export type Handler = (req: Request, ctx: RequestCtx) => OrPromise<Response>;

export function makeRoutes(db: Database) {
	const router = new Router();
	const r = (m: Method, p: Match, h: Handler) =>
		router.register(m, p, (req, pathParams) => {
			return h(req, { pathParams, db });
		});

	r(GET, '/', viewHome);
	r(GET, '/shelves', viewShelves);
	r(GET, '/shelves/:shelfXid', viewShelf);
	r(GET, '/items', viewItems);
	r(GET, '/items/create', viewCreateItem);
	r(GET, '/items/:itemXid', viewItem);
	r(POST, '/items/create', postItem);

	r(GET, '/static/js/bundle.js', serveJsBundle);
	r(GET, '/static/*', serveStatic);

	return router.handle.bind(router);
}

const EXT_TO_MIME = {
	'.css': 'text/css',
};
async function serveStatic(req: Request) {
	const url = new URL(req.url);
	const path = url.pathname.substring('/static'.length);

	const staticPath = import.meta.dirname + '/../static' + path;
	const data = await Deno.readFile(staticPath);
	let mimetype = 'text/plain';
	for (const [ext, mime] of Object.entries(EXT_TO_MIME)) {
		if (path.endsWith(ext)) {
			mimetype = mime;
		}
	}

	return new Response(data, { headers: { 'Content-Type': mimetype } });
}

const isProduction = false;
let cachedBundlePath: string | null = null;
let cachedBundleAt = new Date(0);
async function serveJsBundle(req: Request) {
	if (isProduction) {
		throw new Error('todo');
	}

	if (cachedBundlePath === null) {
		cachedBundlePath = await Deno.makeTempFile({ prefix: 'bundle', suffix: '.js' });
	}

	const bundlePath = import.meta.dirname + '/../frontend/bundle.ts';
	const stat = await Deno.stat(bundlePath);
	const mtime = stat.mtime ?? new Date(0);
	if (cachedBundleAt > mtime) {
		const file = await Deno.open(cachedBundlePath);
		return new Response(file.readable, {
			headers: { 'Content-Type': 'text/javascript' },
		});
	}

	const esbuild = await import('npm:esbuild');
	const { denoPlugins } = await import('jsr:@luca/esbuild-deno-loader');

	const result = await esbuild.build({
		plugins: [...denoPlugins()],
		entryPoints: [import.meta.dirname + '/../frontend/bundle.ts'],
		outfile: cachedBundlePath,
		bundle: true,
		format: 'esm',
		sourcemap: 'inline',
	});
	esbuild.stop();

	cachedBundleAt = new Date();

	return new Response((await Deno.open(cachedBundlePath)).readable, {
		headers: { 'Content-Type': 'text/javascript' },
	});
}
