// Generates the PWA icon set from the project's galaxy mark, with no
// image dependency (a tiny pure-Node RGBA PNG encoder over zlib). The
// mark mirrors `static/favicon.svg`: a dark disc with an amber core,
// plus an amber orbit ring. Re-run after changing the palette/mark:
//
//   node scripts/gen-pwa-icons.mjs
//
// Outputs into static/icons/. The committed PNGs are placeholders; swap
// in real artwork at the same paths/sizes and the manifest is unchanged.

import { deflateSync } from "node:zlib";
import { mkdirSync, writeFileSync } from "node:fs";

const BG = [31, 41, 55]; // #1f2937
const AMBER = [251, 191, 36]; // #fbbf24

const CRC_TABLE = (() => {
	const t = new Uint32Array(256);
	for (let n = 0; n < 256; n++) {
		let c = n;
		for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
		t[n] = c >>> 0;
	}
	return t;
})();
function crc32(buf) {
	let c = 0xffffffff;
	for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
	return (c ^ 0xffffffff) >>> 0;
}
function chunk(type, data) {
	const len = Buffer.alloc(4);
	len.writeUInt32BE(data.length, 0);
	const body = Buffer.concat([Buffer.from(type, "latin1"), data]);
	const crc = Buffer.alloc(4);
	crc.writeUInt32BE(crc32(body), 0);
	return Buffer.concat([len, body, crc]);
}
function encodePng(size, rgba) {
	const stride = size * 4;
	const raw = Buffer.alloc((stride + 1) * size);
	for (let y = 0; y < size; y++) {
		raw[y * (stride + 1)] = 0; // filter: none
		rgba.copy(raw, y * (stride + 1) + 1, y * stride, y * stride + stride);
	}
	const ihdr = Buffer.alloc(13);
	ihdr.writeUInt32BE(size, 0);
	ihdr.writeUInt32BE(size, 4);
	ihdr[8] = 8; // bit depth
	ihdr[9] = 6; // colour type RGBA
	const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
	return Buffer.concat([
		sig,
		chunk("IHDR", ihdr),
		chunk("IDAT", deflateSync(raw, { level: 9 })),
		chunk("IEND", Buffer.alloc(0)),
	]);
}

// `fullBleed` fills the whole square opaquely (maskable / apple-touch);
// otherwise the disc is drawn on transparency with content in the safe
// zone untouched.
function draw(size, fullBleed) {
	const buf = Buffer.alloc(size * size * 4);
	const c = size / 2;
	const discR = size * 0.46;
	const ringR = fullBleed ? size * 0.3 : size * 0.34;
	const ringW = size * 0.04;
	const coreR = fullBleed ? size * 0.09 : size * 0.1;
	for (let y = 0; y < size; y++) {
		for (let x = 0; x < size; x++) {
			const i = (y * size + x) * 4;
			const d = Math.hypot(x + 0.5 - c, y + 0.5 - c);
			let col = null;
			if (fullBleed) col = BG;
			else if (d <= discR) col = BG;
			if (col !== null) {
				if (Math.abs(d - ringR) <= ringW / 2) col = AMBER;
				if (d <= coreR) col = AMBER;
				buf[i] = col[0];
				buf[i + 1] = col[1];
				buf[i + 2] = col[2];
				buf[i + 3] = 255;
			}
		}
	}
	return buf;
}

mkdirSync(new URL("../static/icons/", import.meta.url), { recursive: true });
const targets = [
	["icon-192.png", 192, false],
	["icon-512.png", 512, false],
	["icon-maskable-512.png", 512, true],
	["apple-touch-icon-180.png", 180, true],
];
for (const [name, size, fullBleed] of targets) {
	const path = new URL(`../static/icons/${name}`, import.meta.url);
	writeFileSync(path, encodePng(size, draw(size, fullBleed)));
	console.log("wrote", name, `${size}x${size}`);
}
