import { JSXElement } from "solid-js";
import { FA_ICONS } from "@appgen/faIcons";

interface CustomIconDef {
    viewBox: [number, number];
    content: string;
}

interface IconProps {
    icon: string;
    size?: number | [number, number];
    class?: string;
    prefix?: string;
    // Per-icon style override: `true` forces solid (fas), `false` forces
    // regular (far); when omitted, follows the app-wide FA_DEFAULT_SOLID switch.
    // Solid falls back to regular when a solid variant isn't in the bundle.
    solid?: boolean;
    style?: Partial<CSSStyleProperties>;
}

// Vite-style HMR API the dev bundler injects into every module (see
// internal/bundler/hmr_server.go + docs/hmr.md); undefined in production.
// `declare global` is required here — a bare `interface ImportMeta` in a
// module file wouldn't merge with the ambient one `import.meta`'s type
// actually resolves against.
declare global {
    interface ImportMeta {
        hot?: {
            readonly data: Record<string, any>;
            accept(cb?: (mod: any) => void): void;
            accept(deps: string | string[], cb?: (mod: any) => void): void;
            dispose(cb: (data: Record<string, any>) => void): void;
            prune(cb: (data: Record<string, any>) => void): void;
            invalidate(): void;
            decline(): void;
        };
    }
}

// The FontAwesome family + default weight are theme-driven so this component
// stays identical across projects. Each project's CSS theme sets `--fa-style`
// (classic → far/fas, sharp → fasr/fass) and `--fa-default-solid` (0/1). Read
// once at load; falls back to classic/regular under SSR (no getComputedStyle).
function readIconTheme(): { regular: string; solid: string; defaultSolid: boolean } {
    let sharp = false, defaultSolid = false;
    if (typeof document !== "undefined" && typeof getComputedStyle === "function" && document.documentElement) {
        const cs = getComputedStyle(document.documentElement);
        sharp = cs.getPropertyValue("--fa-style").trim() === "sharp";
        defaultSolid = cs.getPropertyValue("--fa-default-solid").trim() === "1";
    }
    return { regular: sharp ? "fasr" : "far", solid: sharp ? "fass" : "fas", defaultSolid };
}

const _iconTheme = readIconTheme();
const FA_PREFIX_REGULAR = _iconTheme.regular;
const FA_PREFIX_SOLID = _iconTheme.solid;

// Set `--fa-default-solid: 1` in the theme to make the app default to solid icons.
// Individual icons still override per-call with `solid` (true/false) or `prefix`.
const FA_DEFAULT_SOLID: boolean = _iconTheme.defaultSolid;

const FA_PREFIX_DEFAULT = FA_DEFAULT_SOLID ? FA_PREFIX_SOLID : FA_PREFIX_REGULAR;

const ICON_BASE = "shrink-0";
const ICON_INLINE = "inline-block align-middle";

// Custom (non-FontAwesome) icon registry — a registered name overrides any FA
// lookup for the same name. This shared component ships with it EMPTY: each
// project registers its own SVGs from its app entry (see frontend/src/appIcons.ts)
// via registerIcon, so Icons.tsx stays identical across projects.
// Persisted across this module's own HMR reloads (see docs/hmr.md). Icons.tsx
// is an HMR boundary, and its FA icon subset import churns on every SPA source
// edit (generateFAIcons regenerates it each time), so this module reloads far
// more often than one would expect from editing Icons.tsx itself. A fresh
// module instance would otherwise start with an empty registry, since
// registerIcon() only runs once — from appIcons.ts's side-effect import at
// initial page load — blanking custom icons (playground, e-cd, ...) until a
// full page refresh. import.meta.hot.data survives across reloads of this
// module, so stash the registry there instead of a plain module-scoped const.
const customIcons: Record<string, CustomIconDef> =
    (import.meta.hot?.data.customIcons as Record<string, CustomIconDef> | undefined) ?? {};
if (import.meta.hot) import.meta.hot.data.customIcons = customIcons;


// Register a custom icon under `name`, overriding FontAwesome for that name.
// Call from the app's own icon module (e.g. appIcons.ts), imported for side
// effect at startup so every icon is registered before the first Icon renders.
export function registerIcon(name: string, def: CustomIconDef) {
    customIcons[name] = def;
}

// [minX, minY, width, height, svgPath] — viewBox is cropped to the glyph.
type FAEntry = readonly [number, number, number, number, string];

function resolveFAIcon(name: string, prefix: string): FAEntry | undefined {
    return FA_ICONS[prefix + ":" + name];
}

export function Icon(props: IconProps): JSXElement {
    const size = props.size ?? 16;

    const custom = customIcons[props.icon];
    // Style resolution: an explicit `prefix` wins; then a per-icon `solid`
    // override (true→solid, false→regular); otherwise the app-wide default set
    // by FA_DEFAULT_SOLID. The fallback chain lets any icon resolve regardless
    // of which style it actually ships in, so opting into solid never blanks.
    const faDef = (): FAEntry | undefined => {
        if (custom) return undefined;
        let want: string;
        if (props.prefix) want = props.prefix;
        else if (props.solid === true) want = FA_PREFIX_SOLID;
        else if (props.solid === false) want = FA_PREFIX_REGULAR;
        else want = FA_PREFIX_DEFAULT;
        return resolveFAIcon(props.icon, want)
            || resolveFAIcon(props.icon, FA_PREFIX_REGULAR)
            || resolveFAIcon(props.icon, FA_PREFIX_SOLID);
    };

    // viewBox as [minX, minY, width, height]: custom icons are 0-origin; FA icons
    // carry a viewBox cropped to the glyph so they render at their intended size.
    const box = (): [number, number, number, number] => {
        const c = custom;
        if (c) return [0, 0, c.viewBox[0], c.viewBox[1]];
        const fa = faDef();
        return fa ? [fa[0], fa[1], fa[2], fa[3]] : [0, 0, 512, 512];
    };
    const vw = () => box()[2];
    const vh = () => box()[3];
    const svgContent = () => {
        const c = custom;
        if (c) return c.content;
        const fa = faDef();
        return fa ? '<path d="' + fa[4] + '"/>' : "";
    };

    const h = (): number => {
        const s = size;
        return Array.isArray(s) ? (s[1] ?? s[0]) : s;
    };
    const w = (): number => {
        const s = size;
        return Array.isArray(s) ? s[0] : Math.round(h() * (vw() / vh()));
    };

    return (
        <svg xmlns="http://www.w3.org/2000/svg" viewBox={box().join(" ")} fill="currentColor" width={w()} height={h()} class={ICON_BASE + " " + (props.class || "")} innerHTML={svgContent()}></svg>
    );
}

export function IconInline(props: IconProps): JSXElement {
    return <Icon icon={props.icon} size={props.size} prefix={props.prefix} solid={props.solid} class={ICON_INLINE + " " + (props.class || "")}/>;
}

export function IconSuccess(props: IconProps): JSXElement {
    return <Icon icon={props.icon} size={props.size} prefix={props.prefix} solid={props.solid} class={ICON_INLINE + " text-green-600 dark:text-green-400 " + (props.class || "")}/>;
}

export function IconError(props: IconProps): JSXElement {
    return <Icon icon={props.icon} size={props.size} prefix={props.prefix} solid={props.solid} class={ICON_INLINE + " text-red-600 dark:text-red-400 " + (props.class || "")}/>;
}

export function IconContainer(props: {children?: JSXElement}): JSXElement {
    return <span class="flex flex-row items-center gap-2">{props.children}</span>;
}
