// Build step for the nucleic-site Worker.
//
// The source of truth is repo-root `website/` (kept human-readable). This script
// minifies every text asset (HTML — including its inline CSS/JS —, standalone CSS,
// and standalone JS) into `dist/`, copying binary/other files verbatim. wrangler
// deploys from `dist/` (see wrangler.jsonc `assets.directory`), so the edge serves
// the minified output while the repo keeps the readable source.
//
// Run via `npm run build`; `npm run deploy`/`check`/`dev` run it first automatically.

import { readdir, readFile, writeFile, mkdir, copyFile, rm, stat } from 'node:fs/promises'
import { join, extname, relative, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { minify as minifyHtml } from 'html-minifier-terser'
import { minify as minifyJs } from 'terser'
import CleanCSS from 'clean-css'

const here = dirname(fileURLToPath(import.meta.url))
const SRC = join(here, '..', '..', 'website')
const OUT = join(here, 'dist')

const cleanCss = new CleanCSS({ returnPromise: true })

const HTML_OPTS = {
	collapseWhitespace: true,
	conservativeCollapse: false,
	removeComments: true,
	removeRedundantAttributes: true,
	removeScriptTypeAttributes: true,
	removeStyleLinkTypeAttributes: true,
	useShortDoctype: true,
	minifyCSS: true,
	minifyJS: true,
	// Keep boolean/marker attributes (e.g. data-nuc) intact and avoid overly
	// aggressive rewrites that can break hand-tuned markup.
	sortAttributes: false,
	sortClassName: false,
}

// Recursively list every file under `dir`, returned as paths relative to `dir`.
async function walk(dir, base = dir) {
	const out = []
	for (const entry of await readdir(dir, { withFileTypes: true })) {
		const full = join(dir, entry.name)
		if (entry.isDirectory()) out.push(...(await walk(full, base)))
		else if (entry.isFile()) out.push(relative(base, full))
	}
	return out
}

async function minifyFile(rel) {
	const srcPath = join(SRC, rel)
	const outPath = join(OUT, rel)
	await mkdir(dirname(outPath), { recursive: true })

	const ext = extname(rel).toLowerCase()
	if (ext === '.html' || ext === '.htm') {
		const min = await minifyHtml(await readFile(srcPath, 'utf8'), HTML_OPTS)
		await writeFile(outPath, min)
		return { rel, kind: 'html' }
	}
	if (ext === '.css') {
		const { styles, errors } = await cleanCss.minify(await readFile(srcPath, 'utf8'))
		if (errors.length) throw new Error(`clean-css failed on ${rel}: ${errors.join('; ')}`)
		await writeFile(outPath, styles)
		return { rel, kind: 'css' }
	}
	if (ext === '.js' || ext === '.mjs') {
		const { code, error } = await minifyJs(await readFile(srcPath, 'utf8'))
		if (error) throw new Error(`terser failed on ${rel}: ${error}`)
		await writeFile(outPath, code)
		return { rel, kind: 'js' }
	}
	// Binary or non-minifiable (png, woff2, _headers, …) — copy verbatim.
	await copyFile(srcPath, outPath)
	return { rel, kind: 'copy' }
}

const kb = (n) => `${(n / 1024).toFixed(1)} KB`

async function main() {
	await rm(OUT, { recursive: true, force: true })
	await mkdir(OUT, { recursive: true })

	const files = (await walk(SRC)).sort()
	let srcTotal = 0
	let outTotal = 0

	for (const rel of files) {
		const res = await minifyFile(rel)
		const srcSize = (await stat(join(SRC, rel))).size
		const outSize = (await stat(join(OUT, rel))).size
		srcTotal += srcSize
		outTotal += outSize
		if (res.kind === 'copy') {
			console.log(`  copy    ${rel}  (${kb(outSize)})`)
		} else {
			const pct = srcSize ? Math.round((1 - outSize / srcSize) * 100) : 0
			console.log(`  ${res.kind.padEnd(6)}  ${rel}  ${kb(srcSize)} → ${kb(outSize)}  (-${pct}%)`)
		}
	}

	const pct = srcTotal ? Math.round((1 - outTotal / srcTotal) * 100) : 0
	console.log(`\nMinified ${files.length} files → dist/  ${kb(srcTotal)} → ${kb(outTotal)}  (-${pct}% overall)`)
}

main().catch((err) => {
	console.error(err)
	process.exit(1)
})
