/**
 * This script generates a JSON file containing the fonts served by Google Fonts,
 * and also injects the Computer Modern font families as served by https://github.com/bitmaks/cm-web-fonts
 * to ensure they appear in the application's typography options.
 *
 * See:
 * - Google Fonts API: https://developers.google.com/fonts/docs/developer_api
 * - Computer Modern Web Fonts: https://github.com/bitmaks/cm-web-fonts
 */

import type { APIResponse, Variant, WebFont, Weight } from "./types";
import { mkdir, readFile, writeFile } from "node:fs/promises";

const args = process.argv.slice(2);
const argForce = args.includes("--force");
const argCompress = args.includes("--compress");

// biome-ignore lint/style/noNonNullAssertion: it's okay
const argLimit = args.includes("--limit") ? Number.parseInt(args[args.indexOf("--limit") + 1]!, 10) : 500;

const skippedFamilies = ["Material Icons", "Material Symbols", "Noto Color Emoji"];

const FONTS_DIR = "./scripts/fonts";
const RESPONSE_FILE = `${FONTS_DIR}/response.json`;
const WEBFONTLIST_FILE = `${FONTS_DIR}/webfontlist.json`;

/** Returns JSON from Google Fonts API or (unless --force) from local cache if it exists */
async function getGoogleFontsJSON() {
	let contents: string | null = null;

	try {
		contents = await readFile(RESPONSE_FILE, "utf-8");
	} catch {
		// If the file doesn't exist or there's an error reading, just continue.
	}

	if (!argForce && contents) return JSON.parse(contents) as APIResponse;

	const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
	if (!apiKey) throw new Error("GOOGLE_CLOUD_API_KEY is not set");

	const url = `https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}&sort=popularity`;
	const response = await fetch(url);
	const data = (await response.json()) as APIResponse;

	const jsonString = argCompress ? JSON.stringify(data) : JSON.stringify(data, null, 2);
	await mkdir(FONTS_DIR, { recursive: true });
	await writeFile(RESPONSE_FILE, jsonString, "utf-8");

	return data;
}

/** Map Google Fonts API variant strings to simple weights */
function variantToWeight(variant: Variant): Weight | null {
	if (["100", "200", "300", "500", "600", "700", "800", "900"].includes(variant)) return variant as Weight;
	if (variant === "regular") return "400";
	return null;
}

/**
 * Helper: Get additional Computer Modern webfonts (manually curated from https://github.com/bitmaks/cm-web-fonts)
 * These do NOT come from Google Fonts but should appear in webfontlist.json output.
 * Files are delivered via jsDelivr CDN.
 */
function getComputerModernWebFonts(): WebFont[] {
	const CDN = "https://cdn.jsdelivr.net/gh/bitmaks/cm-web-fonts@latest/font";

	return [
		{
			type: "web",
			category: "display",
			family: "Computer Modern Bright",
			weights: ["400", "700"],
			preview: `${CDN}/Bright/cmunbmr.woff`,
			files: {
				"400": `${CDN}/Bright/cmunbmr.woff`,
				"400italic": `${CDN}/Bright/cmunbmo.woff`,
				"700": `${CDN}/Bright/cmunbbx.woff`,
				"700italic": `${CDN}/Bright/cmunbxo.woff`,
			},
		},
		{
			type: "web",
			category: "serif",
			family: "Computer Modern Concrete",
			weights: ["400", "700"],
			preview: `${CDN}/Concrete/cmunorm.woff`,
			files: {
				"400": `${CDN}/Concrete/cmunorm.woff`,
				"400italic": `${CDN}/Concrete/cmunobi.woff`,
				"700": `${CDN}/Concrete/cmunobx.woff`,
				"700italic": `${CDN}/Concrete/cmunoti.woff`,
			},
		},
		{
			type: "web",
			category: "sans-serif",
			family: "Computer Modern Sans",
			weights: ["400", "700"],
			preview: `${CDN}/Sans/cmunss.woff`,
			files: {
				"400": `${CDN}/Sans/cmunss.woff`,
				"400italic": `${CDN}/Sans/cmunsi.woff`,
				"700": `${CDN}/Sans/cmunsx.woff`,
				"700italic": `${CDN}/Sans/cmunso.woff`,
			},
		},
		{
			type: "web",
			category: "serif",
			family: "Computer Modern Serif",
			weights: ["400", "700"],
			preview: `${CDN}/Serif/cmunrm.woff`,
			files: {
				"400": `${CDN}/Serif/cmunrm.woff`,
				"400italic": `${CDN}/Serif/cmunti.woff`,
				"700": `${CDN}/Serif/cmunbx.woff`,
				"700italic": `${CDN}/Serif/cmunbi.woff`,
			},
		},
		{
			type: "web",
			category: "monospace",
			family: "Computer Modern Typewriter",
			weights: ["400", "700"],
			preview: `${CDN}/Typewriter/cmuntt.woff`,
			files: {
				"400": `${CDN}/Typewriter/cmuntt.woff`,
				"400italic": `${CDN}/Typewriter/cmunit.woff`,
				"700": `${CDN}/Typewriter/cmuntx.woff`,
				"700italic": `${CDN}/Typewriter/cmuntb.woff`,
			},
		},
	];
}

/**
 * Helper: Additional metric-compatible web fonts that aren't in the
 * popularity-sorted Google Fonts slice but are needed as render targets
 * for legacy local-font aliases (#2989). Carlito is the open-source
 * metric-compatible equivalent of Calibri.
 */
function getMetricCompatibleFonts(): WebFont[] {
	const CDN = "https://fonts.gstatic.com/s/carlito/v4";

	return [
		{
			type: "web",
			category: "sans-serif",
			family: "Carlito",
			weights: ["400", "700"],
			preview: `${CDN}/3Jn9SDPw3m-pk039DDeBSQ.ttf`,
			files: {
				"400": `${CDN}/3Jn9SDPw3m-pk039DDeBSQ.ttf`,
				"700": `${CDN}/3Jn4SDPw3m-pk039BIykWXolVg.ttf`,
				"400italic": `${CDN}/3Jn_SDPw3m-pk039DDKxTl0F.ttf`,
				"700italic": `${CDN}/3Jn6SDPw3m-pk039DDK59XgVUcBD.ttf`,
			},
		},
	];
}

async function generateFonts() {
	const response = await getGoogleFontsJSON();
	console.log(`Found ${response.items.length} fonts in total (Google Fonts).`);

	const filteredItems = response.items.filter(
		(item) => !skippedFamilies.some((family) => item.family.includes(family)),
	);

	const googleFontResults: WebFont[] = filteredItems.slice(0, argLimit).map((item) => {
		// 1. weights: Only non-italic, convert "regular" to "400"
		const weights: Weight[] = item.variants.map((v) => variantToWeight(v)).filter((w): w is Weight => !!w);

		// 2. files: all files, but change "regular"->"400", "italic"->"400italic"
		const files: Record<string, string> = {};

		for (const [variant, url] of Object.entries(item.files)) {
			let key = variant;
			if (variant === "regular") key = "400";
			else if (variant === "italic") key = "400italic";
			files[key] = url;
		}

		return {
			type: "web",
			category: item.category,
			family: item.family,
			weights,
			preview: item.menu,
			files,
		} satisfies WebFont;
	});

	// Manually append Computer Modern web fonts
	const computerModernFonts = getComputerModernWebFonts();
	// Manually append metric-compatible fonts not covered by the popularity slice
	const metricCompatFonts = getMetricCompatibleFonts();
	// De-duplicate against the Google Fonts slice in case a manual entry
	// later enters the popularity top-N.
	const googleFontFamilies = new Set(googleFontResults.map((f) => f.family));
	const filteredMetricCompat = metricCompatFonts.filter((f) => !googleFontFamilies.has(f.family));

	const allWebFonts: WebFont[] = [...computerModernFonts, ...googleFontResults, ...filteredMetricCompat];

	console.log(
		`Added ${computerModernFonts.length} Computer Modern Web Fonts and ${filteredMetricCompat.length} metric-compatible fonts. Total output: ${allWebFonts.length} web fonts.`,
	);

	const jsonString = argCompress ? JSON.stringify(allWebFonts) : JSON.stringify(allWebFonts, null, 2);
	await mkdir(FONTS_DIR, { recursive: true });
	await writeFile(WEBFONTLIST_FILE, jsonString, "utf-8");

	console.log(`Generated ${allWebFonts.length} fonts in the list (including Computer Modern web fonts).`);
}

if (import.meta.main) {
	await generateFonts();
}
