#!/usr/bin/env node
/* eslint-disable no-console */
/* global process */

/**
 * Builds the Font Awesome icon subset SVG sprite for the Rspamd WebUI.
 *
 * Reads the icon names used in the markup from icons-manifest.txt, resolves
 * each to its Font Awesome solid SVG (including v5 aliases), and emits a
 * sprite of <symbol> elements to interface/img/icons.svg (plus width rules
 * for non-square icons in interface/css/icons.css). The sprite is referenced
 * at runtime via <use href="img/icons.svg#fa-<name>"/>.
 *
 * The generated files are committed; this script is dev-only and only runs
 * when icons are added/removed or Font Awesome is bumped. Node is not required
 * at runtime (the shipped WebUI is static).
 *
 * Usage:  npm install && node icons-build.mjs
 */
import {readFileSync, writeFileSync} from "node:fs";

const ROOT = import.meta.dirname;
const FA_DIR = `${ROOT}/node_modules/@fortawesome/fontawesome-free`;
const SVGS_DIR = `${FA_DIR}/svgs/solid`;
const MANIFEST = `${ROOT}/icons-manifest.txt`;
const OUT = `${ROOT}/interface/img/icons.svg`;
const CSS_OUT = `${ROOT}/interface/css/icons.css`;

// v5 alias -> FA6 canonical solid icon name (stable v5->v6 renames).
const ALIASES = {
    "cog": "gear",
    "columns": "table-columns",
    "exclamation-triangle": "triangle-exclamation",
    "external-link": "arrow-up-right-from-square",
    "redo-alt": "rotate-right",
    "save": "floppy-disk",
    "search": "magnifying-glass",
    "sync-alt": "arrows-rotate",
    "tasks": "list-check",
    "times": "xmark",
    "times-circle": "circle-xmark",
    "trash-alt": "trash-can",
};

function readManifest(path) {
    return readFileSync(path, "utf8")
        .split("\n")
        .map((line) => line.trim())
        .filter((line) => line && !line.startsWith("#"));
}

// Read a file's text, or null if it does not exist.
function readFileOrNull(path) {
    try {
        return readFileSync(path, "utf8");
    } catch {
        return null;
    }
}

// Extract the viewBox and inner markup from a Font Awesome solid SVG.
function parseSvg(svg) {
    const match = svg.match(/<svg[^>]*viewBox="([^"]+)"[^>]*>([\s\S]*)<\/svg>/u);
    if (!match) {
        throw new Error("unexpected Font Awesome SVG format");
    }
    const inner = match[2].replace(/<!--[\s\S]*?-->/gu, "").trim();
    return {inner, viewBox: match[1]};
}

function fontAwesomeVersion() {
    const pkgJson = readFileOrNull(`${FA_DIR}/package.json`);
    if (pkgJson === null) {
        console.error(`Font Awesome not found at ${FA_DIR}. Run "npm install".`);
        process.exit(1);
    }
    const {version} = JSON.parse(pkgJson);
    return version;
}

function main() {
    const version = fontAwesomeVersion();
    const names = readManifest(MANIFEST);
    const symbols = [];
    const widthRules = [];
    const missing = [];

    for (const name of names) {
        const canonical = ALIASES[name] ?? name;
        const svg = readFileOrNull(`${SVGS_DIR}/${canonical}.svg`);
        if (svg === null) {
            missing.push(`${name}${canonical !== name ? ` (-> ${canonical})` : ""}`);
            continue;
        }
        const {inner, viewBox} = parseSvg(svg);
        symbols.push(`  <symbol id="fa-${name}" viewBox="${viewBox}">${inner}</symbol>`);
        // Width (em) relative to the 1em height, for non-square icons. Square
        // icons fall back to the .svg-inline--fa default width of 1em.
        const parts = viewBox.split(/\s+/u);
        const ratio = Number(parts[2]) / Number(parts[3]);
        if (Math.abs(ratio - 1) > 0.001) {
            const width = Math.round(ratio * 1000) / 1000;
            widthRules.push(`.svg-inline--fa[data-icon="fa-${name}"] { width: ${width}em; }`);
        }
    }

    if (missing.length) {
        console.error("Missing Font Awesome icons (check manifest/aliases and that the npm package is installed):");
        for (const name of missing) {
            console.error(`  ${name}`);
        }
        process.exit(1);
    }

    const out = `<!-- Font Awesome Free ${version} subset (Icons: CC BY 4.0, https://fontawesome.com/license/free) -->
<!-- Auto-generated by icons-build.mjs - DO NOT EDIT. Edit icons-manifest.txt and re-run "node icons-build.mjs". -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
${symbols.join("\n")}
</svg>
`;
    writeFileSync(OUT, out, "utf8");
    console.log(`Wrote ${symbols.length} icons -> ${OUT}`);

    const css = `/* stylelint-disable */
/* Font Awesome Free ${version} subset icon widths (Icons: CC BY 4.0, https://fontawesome.com/license/free) */
/* Auto-generated by icons-build.mjs - DO NOT EDIT. Re-run "node icons-build.mjs". */
/* Default .svg-inline--fa width is 1em (square viewBox); rules below size non-square icons. */
${widthRules.sort().join("\n")}
`;
    writeFileSync(CSS_OUT, css, "utf8");
    console.log(`Wrote ${widthRules.length} non-square width rules -> ${CSS_OUT}`);
}

main();
