// Pre-generates the Academy narration MP3s with Azure TTS and writes them to
// public/academy-audio/. Run locally (the key never ships): reads AZURE_VOICE_KEY and
// AZURE_VOICE from .env, region eastus. Idempotent — pass --force to regenerate everything;
// otherwise only missing slide audio is synthesized. The spoken text is slide.tts (phonetic
// respelling) when present, else the display transcript.
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const { ACADEMY_TRACKS } = await import(path.join(root, "public/js/academy-content.js"));

const env = Object.fromEntries(
	(await readFile(path.join(root, ".env"), "utf8"))
		.split("\n")
		.filter((line) => line.includes("="))
		.map((line) => {
			const i = line.indexOf("=");
			return [line.slice(0, i).trim(), line.slice(i + 1).trim().replace(/^"|"$/g, "")];
		}),
);
const KEY = env.AZURE_VOICE_KEY;
const VOICE = env.AZURE_VOICE;
if (!KEY || !VOICE) throw new Error("AZURE_VOICE_KEY / AZURE_VOICE missing from .env");

const outDir = path.join(root, "public", "academy-audio");
await mkdir(outDir, { recursive: true });
const force = process.argv.includes("--force");

const escapeXml = (s) =>
	s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

const slides = [];
const seen = new Set();
for (const track of ACADEMY_TRACKS)
	for (const lesson of track.lessons)
		for (const slide of lesson.slides) {
			if (seen.has(slide.id)) throw new Error(`duplicate slide id ${slide.id}`);
			seen.add(slide.id);
			slides.push(slide);
		}

let made = 0;
let skipped = 0;
for (const slide of slides) {
	const file = path.join(outDir, `${slide.id}.mp3`);
	if (!force && existsSync(file)) {
		skipped++;
		continue;
	}
	const ssml = `<speak version='1.0' xml:lang='en-US'><voice name='${VOICE}'>${escapeXml(slide.tts ?? slide.transcript)}</voice></speak>`;
	const res = await fetch("https://eastus.tts.speech.microsoft.com/cognitiveservices/v1", {
		method: "POST",
		headers: {
			"Ocp-Apim-Subscription-Key": KEY,
			"Content-Type": "application/ssml+xml",
			"X-Microsoft-OutputFormat": "audio-24khz-48kbitrate-mono-mp3",
			"User-Agent": "roast-academy",
		},
		body: ssml,
	});
	if (!res.ok)
		throw new Error(`TTS failed for ${slide.id}: ${res.status} ${await res.text()}`);
	const buffer = Buffer.from(await res.arrayBuffer());
	if (buffer.length < 2_000) throw new Error(`suspiciously small audio for ${slide.id}`);
	await writeFile(file, buffer);
	made++;
	console.log(`✓ ${slide.id} (${Math.round(buffer.length / 1024)} KB)`);
	// Be polite to the TTS quota.
	await new Promise((resolve) => setTimeout(resolve, 400));
}
console.log(`Done: ${made} generated, ${skipped} already present, ${slides.length} total.`);
