/*
 * Icon rendering for the Rspamd WebUI.
 *
 * Replaces <i class="fas fa-<name>"> placeholders with inline
 * <svg class="svg-inline--fa"><use href="img/icons.svg#fa-<name>"/></svg>,
 * sourcing glyphs from the prebuilt Font Awesome subset sprite
 * (interface/img/icons.svg, generated by icons-build.mjs at the repo root).
 * This replaces the Font Awesome "SVG with JS" framework (~900 KB of JS/CSS).
 *
 * Loaded for its side effect: an initial sweep plus a MutationObserver that
 * renders icons inserted later (lazy-loaded tabs, table rows). Exports
 * setIcon() for code that swaps an icon dynamically (theme toggle, map modal).
 */

define([], () => {
    "use strict";

    const SPRITE = "img/icons.svg";
    const NS = "http://www.w3.org/2000/svg";
    const ICON_CLASS = /^fa-[a-z0-9-]+$/u;
    const STYLE_CLASSES = new Set(["fas", "fa-solid", "fa-regular", "fa-brands"]);
    const UTILITIES = new Set([
        "fa-border",
        "fa-bounce",
        "fa-fade",
        "fa-flip",
        "fa-fw",
        "fa-lg",
        "fa-pull-left",
        "fa-pull-right",
        "fa-pulse",
        "fa-sm",
        "fa-spin",
        "fa-xs",
    ]);

    // Whether `cls` is an icon-name class (fa-<name>): not a style prefix
    // (fas, fa-solid, ...) and not a modifier/utility (fa-spin, fa-fw, ...).
    function isIconName(cls) {
        return ICON_CLASS.test(cls) && !STYLE_CLASSES.has(cls) && !UTILITIES.has(cls);
    }

    // Whether `cls` should be carried from the <i> to the rendered <svg>:
    // keep spacing and modifier classes (fa-spin, ...); drop style prefixes,
    // the icon-name class, and svg-inline--fa.
    function keepClass(cls) {
        return cls !== "svg-inline--fa" && !STYLE_CLASSES.has(cls) && !isIconName(cls);
    }

    function iconName(classList) {
        return Array.from(classList).find(isIconName);
    }

    // Build an <svg> referencing glyph `name` in the sprite, carrying `classes`.
    function buildSvg(name, classes) {
        const svg = document.createElementNS(NS, "svg");
        svg.classList.add("svg-inline--fa");
        svg.setAttribute("data-icon", name);
        classes.forEach((cls) => svg.classList.add(cls));
        const use = document.createElementNS(NS, "use");
        use.setAttribute("href", `${SPRITE}#${name}`);
        svg.append(use);
        return svg;
    }

    // Replace `el` (an <i> placeholder or an already-rendered <svg>) with an
    // <svg> showing glyph `name`, preserving non-class attributes (id, title,
    // aria-*, ...) and modifier classes (spacing, fa-spin, ...).
    function setIcon(el, name) {
        const keep = Array.from(el.classList).filter(keepClass);
        const svg = buildSvg(name, keep);
        Array.from(el.attributes).forEach((attr) => {
            if (attr.name !== "class" && attr.name !== "data-icon") {
                svg.setAttribute(attr.name, attr.value);
            }
        });
        el.replaceWith(svg);
        return svg;
    }

    function findIcons(root) {
        if (root.matches && root.matches("i.fas, i.fa-solid")) {
            return [root];
        }
        return Array.from(root.querySelectorAll("i.fas, i.fa-solid"));
    }

    function render(root) {
        if (!root || root.nodeType !== Node.ELEMENT_NODE) {
            return;
        }
        findIcons(root).forEach((el) => {
            const name = iconName(el.classList);
            if (name) {
                setIcon(el, name);
            }
        });
    }

    // Initial sweep, then watch nodes added later (lazy tabs, table rows).
    render(document.body);
    new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => render(node));
        });
    }).observe(document.body, {childList: true, subtree: true});

    return {setIcon};
});
