/**
 * Typst compiler — spawns the Typst CLI to generate PDFs.
 *
 * Ported from pocket-press/lib/typst.ts.
 * Uses Bun.spawn() instead of Node execFile().
 */

import { writeFile, readFile, readdir, mkdir, unlink, rm } from "node:fs/promises";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import type { ParsedDocument } from "./parse-document";

const TYPST_DIR = resolve(import.meta.dir, "typst");

export interface CompileOptions {
	document: ParsedDocument;
	formatId: string;
}

export interface CompileResult {
	pdf: Buffer;
	pageCount: number;
}

export interface PageImage {
	png: Buffer;
	widthPx: number;
	heightPx: number;
}

export interface CompilePagesResult {
	pages: PageImage[];
	pageCount: number;
}

export async function compileDocument(options: CompileOptions): Promise<CompileResult> {
	const { document, formatId } = options;
	const jobId = randomUUID();
	const tmpDir = join("/tmp", `rpubs-${jobId}`);
	await mkdir(tmpDir, { recursive: true });

	const dataPath = join(tmpDir, "data.json");
	const driverPath = join(tmpDir, "main.typ");
	const outputPath = join(tmpDir, "output.pdf");

	try {
		// Write the content data as JSON
		await writeFile(dataPath, JSON.stringify(document, null, 2));

		// Write the Typst driver file that imports format + template
		const formatImport = join(TYPST_DIR, "formats", `${formatId}.typ`);
		const templateImport = join(TYPST_DIR, "templates", "pocket-book.typ");

		const driverContent = `
#import "${formatImport}": page-setup
#show: page-setup
#include "${templateImport}"
`;

		await writeFile(driverPath, driverContent);

		// Compile with Typst CLI using Bun.spawn
		const proc = Bun.spawn(
			[
				"typst",
				"compile",
				driverPath,
				outputPath,
				"--root",
				"/",
				"--input",
				`data-path=${dataPath}`,
				"--font-path",
				join(TYPST_DIR, "fonts"),
			],
			{
				stdout: "pipe",
				stderr: "pipe",
			}
		);

		const exitCode = await proc.exited;

		if (exitCode !== 0) {
			const stderr = await new Response(proc.stderr).text();
			throw new Error(`Typst compilation failed: ${stderr}`);
		}

		const pdf = Buffer.from(await readFile(outputPath));

		// Count pages (rough: look for /Type /Page in PDF)
		const pdfStr = pdf.toString("latin1");
		const pageCount = (pdfStr.match(/\/Type\s*\/Page[^s]/g) || []).length;

		return { pdf, pageCount };
	} finally {
		// Clean up temp files
		await Promise.allSettled([
			unlink(dataPath),
			unlink(driverPath),
			unlink(outputPath),
		]).catch(() => {});
		// Remove temp dir (best effort)
		import("node:fs/promises").then(({ rmdir }) => rmdir(tmpDir).catch(() => {}));
	}
}

/**
 * Compile a document to per-page PNG images. Used for fixed-layout EPUB.
 * Uses Typst's built-in PNG rasterizer — no extra deps.
 */
export async function compileDocumentToPages(
	options: CompileOptions & { ppi?: number },
): Promise<CompilePagesResult> {
	const { document, formatId, ppi = 144 } = options;
	const jobId = randomUUID();
	const tmpDir = join("/tmp", `rpubs-png-${jobId}`);
	await mkdir(tmpDir, { recursive: true });

	const dataPath = join(tmpDir, "data.json");
	const driverPath = join(tmpDir, "main.typ");
	const outputPattern = join(tmpDir, "page-{n}.png");

	try {
		await writeFile(dataPath, JSON.stringify(document, null, 2));

		const formatImport = join(TYPST_DIR, "formats", `${formatId}.typ`);
		const templateImport = join(TYPST_DIR, "templates", "pocket-book.typ");

		const driverContent = `
#import "${formatImport}": page-setup
#show: page-setup
#include "${templateImport}"
`;
		await writeFile(driverPath, driverContent);

		const proc = Bun.spawn(
			[
				"typst",
				"compile",
				driverPath,
				outputPattern,
				"--format",
				"png",
				"--ppi",
				String(ppi),
				"--root",
				"/",
				"--input",
				`data-path=${dataPath}`,
				"--font-path",
				join(TYPST_DIR, "fonts"),
			],
			{ stdout: "pipe", stderr: "pipe" },
		);

		const exitCode = await proc.exited;
		if (exitCode !== 0) {
			const stderr = await new Response(proc.stderr).text();
			throw new Error(`Typst PNG compilation failed: ${stderr}`);
		}

		// Collect rendered pages in numeric order.
		const files = (await readdir(tmpDir))
			.filter((f) => f.startsWith("page-") && f.endsWith(".png"))
			.sort((a, b) => {
				const na = parseInt(a.replace(/[^0-9]/g, ""), 10);
				const nb = parseInt(b.replace(/[^0-9]/g, ""), 10);
				return na - nb;
			});

		const pages: PageImage[] = [];
		for (const f of files) {
			const png = Buffer.from(await readFile(join(tmpDir, f)));
			const dims = readPngDimensions(png);
			pages.push({ png, widthPx: dims.width, heightPx: dims.height });
		}

		return { pages, pageCount: pages.length };
	} finally {
		await rm(tmpDir, { recursive: true, force: true }).catch(() => {});
	}
}

/** Read width/height from a PNG IHDR chunk (bytes 16–23). */
function readPngDimensions(png: Buffer): { width: number; height: number } {
	if (png.length < 24 || png[0] !== 0x89 || png[1] !== 0x50) {
		return { width: 0, height: 0 };
	}
	const width = png.readUInt32BE(16);
	const height = png.readUInt32BE(20);
	return { width, height };
}
