#include "gfx/dag_catalog.h"
#include <algorithm>
#include <string>

namespace fn::gfx {

static std::vector<DagNodeDef>& mutable_catalog() {
    static std::vector<DagNodeDef> catalog = []() {
        std::vector<DagNodeDef> v;

        // ── Gen: solid ────────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "solid";
            n.label  = "solid";
            n.desc   = "color constante";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"r", "g", "b"};
            n.param_defaults = {0.35f, 0.25f, 0.55f};
            n.controls = {
                { DagControl::Kind::Color, "color", {0, 1, 2}, 0.0f, 1.0f, 0.0f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    return vec4(p.x, p.y, p.z, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: gradient ─────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "gradient";
            n.label  = "gradient";
            n.desc   = "gradiente direccional";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"angle", "hue"};
            n.param_defaults = {0.8f, 0.5f};
            n.controls = {
                { DagControl::Kind::Slider, "angulo", {0, -1, -1}, 0.0f, 6.2832f, 0.01f },
                { DagControl::Kind::Slider, "tono",   {1, -1, -1}, 0.0f, 1.0f,    0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    vec2 dir = vec2(cos(p.x), sin(p.x));\n"
                       "    float t = dot(uv - 0.5, dir) + 0.5;\n"
                       "    vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.y + vec3(0.0, 0.33, 0.67) + t));\n"
                       "    return vec4(col, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: plasma ───────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "plasma";
            n.label  = "plasma";
            n.desc   = "onda trigonometrica";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"speed", "scale"};
            n.param_defaults = {1.0f, 2.0f};
            n.controls = {
                { DagControl::Kind::Slider, "velocidad", {0, -1, -1}, 0.0f,  3.0f, 0.01f },
                { DagControl::Kind::Slider, "escala",    {1, -1, -1}, 0.5f, 10.0f, 0.1f  },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    vec3 col = 0.5 + 0.5 * cos(u_time * p.x + uv.xyx * p.y + vec3(0.0, 2.0, 4.0));\n"
                       "    return vec4(col, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Op: circle ────────────────────────────────────────────────
        // Reclassified as Op (num_inputs=1): composites circle over input 'a'
        {
            DagNodeDef n;
            n.name   = "circle";
            n.label  = "circle";
            n.desc   = "sdf de circulo (composita sobre input)";
            n.kind   = DagKind::Op;
            n.num_inputs = 1;
            n.param_names    = {"cx", "cy", "radius", "soft"};
            n.param_defaults = {0.0f, 0.0f, 0.35f, 0.01f};
            n.controls = {
                { DagControl::Kind::XY,     "centro",    {0, 1, -1}, -0.8f, 0.8f,  0.01f  },
                { DagControl::Kind::Slider, "radio",     {2, -1, -1}, 0.0f, 1.0f,  0.01f  },
                { DagControl::Kind::Slider, "suavidad",  {3, -1, -1}, 0.001f, 0.1f, 0.001f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    float aspect = u_resolution.x / u_resolution.y;\n"
                       "    vec2 pos = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
                       "    float d = length(pos) - p.z;\n"
                       "    float fill = smoothstep(p.w, -p.w, d);\n"
                       "    return mix(a, vec4(1.0), fill);";
            };
            v.push_back(std::move(n));
        }

        // ── Op: invert ────────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "invert";
            n.label  = "invert";
            n.desc   = "1 - rgb";
            n.kind   = DagKind::Op;
            n.num_inputs = 1;
            n.param_names    = {};
            n.param_defaults = {};
            n.controls = {};
            n.body_glsl = [](int /*idx*/) -> std::string {
                return "    return vec4(1.0 - a.rgb, a.a);";
            };
            v.push_back(std::move(n));
        }

        // ── Op: gamma ─────────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "gamma";
            n.label  = "gamma";
            n.desc   = "pow(rgb, gamma)";
            n.kind   = DagKind::Op;
            n.num_inputs = 1;
            n.param_names    = {"gamma"};
            n.param_defaults = {1.0f};
            n.controls = {
                { DagControl::Kind::Slider, "gamma", {0, -1, -1}, 0.1f, 4.0f, 0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    return vec4(pow(a.rgb, vec3(1.0 / max(p.x, 0.001))), a.a);";
            };
            v.push_back(std::move(n));
        }

        // ── Op: hueShift ──────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "hueShift";
            n.label  = "hue shift";
            n.desc   = "rotar matiz";
            n.kind   = DagKind::Op;
            n.num_inputs = 1;
            n.param_names    = {"h"};
            n.param_defaults = {0.0f};
            n.controls = {
                { DagControl::Kind::Slider, "h", {0, -1, -1}, 0.0f, 1.0f, 0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    float ang = 6.28318 * p.x;\n"
                       "    float ca = cos(ang), sa = sin(ang);\n"
                       "    mat3 hueMat = mat3(\n"
                       "        vec3(0.299 + 0.701 * ca + 0.168 * sa, 0.587 - 0.587 * ca + 0.330 * sa, 0.114 - 0.114 * ca - 0.497 * sa),\n"
                       "        vec3(0.299 - 0.299 * ca - 0.328 * sa, 0.587 + 0.413 * ca + 0.035 * sa, 0.114 - 0.114 * ca + 0.292 * sa),\n"
                       "        vec3(0.299 - 0.300 * ca + 1.250 * sa, 0.587 - 0.588 * ca - 1.050 * sa, 0.114 + 0.886 * ca - 0.203 * sa)\n"
                       "    );\n"
                       "    return vec4(clamp(hueMat * a.rgb, 0.0, 1.0), a.a);";
            };
            v.push_back(std::move(n));
        }

        // ── Blend: mix ────────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "blend_mix";
            n.label  = "mix";
            n.desc   = "interpolacion mix(a, b, t)";
            n.kind   = DagKind::Blend;
            n.num_inputs = 2;
            n.param_names    = {"t"};
            n.param_defaults = {0.5f};
            n.controls = {
                { DagControl::Kind::Slider, "t", {0, -1, -1}, 0.0f, 1.0f, 0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    return mix(a, b, p.x);";
            };
            v.push_back(std::move(n));
        }

        // ── Blend: multiply ───────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "blend_multiply";
            n.label  = "multiply";
            n.desc   = "a * b";
            n.kind   = DagKind::Blend;
            n.num_inputs = 2;
            n.param_names    = {};
            n.param_defaults = {};
            n.controls = {};
            n.body_glsl = [](int /*idx*/) -> std::string {
                return "    return vec4(a.rgb * b.rgb, a.a);";
            };
            v.push_back(std::move(n));
        }

        // ── Blend: screen ─────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "blend_screen";
            n.label  = "screen";
            n.desc   = "1 - (1-a)(1-b)";
            n.kind   = DagKind::Blend;
            n.num_inputs = 2;
            n.param_names    = {};
            n.param_defaults = {};
            n.controls = {};
            n.body_glsl = [](int /*idx*/) -> std::string {
                return "    return vec4(1.0 - (1.0 - a.rgb) * (1.0 - b.rgb), a.a);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: checker ──────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "checker";
            n.label  = "checker";
            n.desc   = "tablero animado";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"scale", "rotation", "hue", "speed"};
            n.param_defaults = {8.0f, 0.0f, 0.55f, 0.2f};
            n.controls = {
                { DagControl::Kind::Slider, "escala",   {0, -1, -1}, 1.0f, 32.0f, 0.1f },
                { DagControl::Kind::Slider, "rotacion", {1, -1, -1}, 0.0f,  6.2832f, 0.01f },
                { DagControl::Kind::Slider, "tono",     {2, -1, -1}, 0.0f,  1.0f, 0.01f },
                { DagControl::Kind::Slider, "velocidad",{3, -1, -1}, 0.0f,  3.0f, 0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    float ang = p.y + u_time * p.w * 0.2;\n"
                       "    float ca = cos(ang), sa = sin(ang);\n"
                       "    vec2 q = uv - 0.5;\n"
                       "    q = vec2(ca*q.x - sa*q.y, sa*q.x + ca*q.y) * p.x + 0.5;\n"
                       "    vec2 cell = floor(q);\n"
                       "    float c = mod(cell.x + cell.y, 2.0);\n"
                       "    vec3 ca_col = 0.5 + 0.5 * cos(6.28318 * (p.z + vec3(0.0, 0.33, 0.67)));\n"
                       "    vec3 cb_col = 0.5 + 0.5 * cos(6.28318 * (p.z + 0.5 + vec3(0.0, 0.33, 0.67)));\n"
                       "    return vec4(mix(cb_col, ca_col, c), 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: stripes ──────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "stripes";
            n.label  = "stripes";
            n.desc   = "bandas direccionales";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"angle", "freq", "phase_speed", "hue"};
            n.param_defaults = {0.785f, 12.0f, 1.0f, 0.6f};
            n.controls = {
                { DagControl::Kind::Slider, "angulo",     {0, -1, -1}, 0.0f, 6.2832f, 0.01f },
                { DagControl::Kind::Slider, "frecuencia", {1, -1, -1}, 1.0f, 64.0f,   0.5f  },
                { DagControl::Kind::Slider, "velocidad",  {2, -1, -1}, 0.0f, 5.0f,    0.01f },
                { DagControl::Kind::Slider, "tono",       {3, -1, -1}, 0.0f, 1.0f,    0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    vec2 dir = vec2(cos(p.x), sin(p.x));\n"
                       "    float t = dot(uv - 0.5, dir) * p.y + u_time * p.z;\n"
                       "    float s = 0.5 + 0.5 * sin(t);\n"
                       "    vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.w + s + vec3(0.0, 0.33, 0.67)));\n"
                       "    return vec4(col, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: dots ─────────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "dots";
            n.label  = "dots";
            n.desc   = "rejilla de puntos";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"scale", "radius", "soft", "hue"};
            n.param_defaults = {16.0f, 0.3f, 0.05f, 0.7f};
            n.controls = {
                { DagControl::Kind::Slider, "escala",   {0, -1, -1}, 1.0f, 64.0f, 0.5f   },
                { DagControl::Kind::Slider, "radio",    {1, -1, -1}, 0.0f, 0.5f,  0.01f  },
                { DagControl::Kind::Slider, "suavidad", {2, -1, -1}, 0.001f, 0.2f, 0.001f },
                { DagControl::Kind::Slider, "tono",     {3, -1, -1}, 0.0f, 1.0f,  0.01f  },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    float aspect = u_resolution.x / u_resolution.y;\n"
                       "    vec2 q = vec2((uv.x - 0.5) * aspect, uv.y - 0.5) * p.x;\n"
                       "    vec2 cell = fract(q) - 0.5;\n"
                       "    float d = length(cell) - p.y;\n"
                       "    float fill = smoothstep(p.z, -p.z, d);\n"
                       "    vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.w + vec3(0.0, 0.33, 0.67)));\n"
                       "    return vec4(col * fill, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: rings ────────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "rings";
            n.label  = "rings";
            n.desc   = "anillos concentricos";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"cx", "cy", "freq", "speed"};
            n.param_defaults = {0.0f, 0.0f, 30.0f, 2.0f};
            n.controls = {
                { DagControl::Kind::XY,     "centro",    {0, 1, -1}, -0.5f, 0.5f, 0.01f },
                { DagControl::Kind::Slider, "frecuencia",{2, -1, -1}, 1.0f, 100.0f, 0.5f },
                { DagControl::Kind::Slider, "velocidad", {3, -1, -1}, 0.0f, 10.0f, 0.05f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    float aspect = u_resolution.x / u_resolution.y;\n"
                       "    vec2 q = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
                       "    float d = length(q);\n"
                       "    float r = 0.5 + 0.5 * sin(d * p.z - u_time * p.w);\n"
                       "    return vec4(vec3(r), 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: polar_rays ───────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "polar_rays";
            n.label  = "polar rays";
            n.desc   = "rayos radiales";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"cx", "cy", "count", "speed"};
            n.param_defaults = {0.0f, 0.0f, 12.0f, 0.5f};
            n.controls = {
                { DagControl::Kind::XY,     "centro",    {0, 1, -1}, -0.5f, 0.5f, 0.01f },
                { DagControl::Kind::Slider, "rayos",     {2, -1, -1}, 1.0f, 64.0f, 1.0f },
                { DagControl::Kind::Slider, "velocidad", {3, -1, -1}, -3.0f, 3.0f, 0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    float aspect = u_resolution.x / u_resolution.y;\n"
                       "    vec2 q = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
                       "    float a = atan(q.y, q.x);\n"
                       "    float r = 0.5 + 0.5 * sin(a * p.z + u_time * p.w);\n"
                       "    return vec4(vec3(r), 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: noise_value ──────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "noise_value";
            n.label  = "noise value";
            n.desc   = "value noise 2D";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"scale", "speed", "hue"};
            n.param_defaults = {6.0f, 0.3f, 0.5f};
            n.controls = {
                { DagControl::Kind::Slider, "escala",    {0, -1, -1}, 0.5f, 32.0f, 0.1f  },
                { DagControl::Kind::Slider, "velocidad", {1, -1, -1}, 0.0f, 3.0f,  0.01f },
                { DagControl::Kind::Slider, "tono",      {2, -1, -1}, 0.0f, 1.0f,  0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    vec2 q = uv * p.x + u_time * p.y;\n"
                       "    vec2 fl = floor(q);\n"
                       "    vec2 fr = fract(q);\n"
                       "    fr = fr * fr * (3.0 - 2.0 * fr);\n"
                       "    float a = fract(sin(dot(fl + vec2(0.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
                       "    float b = fract(sin(dot(fl + vec2(1.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
                       "    float c = fract(sin(dot(fl + vec2(0.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
                       "    float d = fract(sin(dot(fl + vec2(1.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
                       "    float n = mix(mix(a, b, fr.x), mix(c, d, fr.x), fr.y);\n"
                       "    vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + n + vec3(0.0, 0.33, 0.67)));\n"
                       "    return vec4(col, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: voronoi ──────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "voronoi";
            n.label  = "voronoi";
            n.desc   = "celdas voronoi";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"scale", "speed", "hue"};
            n.param_defaults = {8.0f, 0.5f, 0.4f};
            n.controls = {
                { DagControl::Kind::Slider, "escala",    {0, -1, -1}, 1.0f, 32.0f, 0.1f  },
                { DagControl::Kind::Slider, "velocidad", {1, -1, -1}, 0.0f, 3.0f,  0.01f },
                { DagControl::Kind::Slider, "tono",      {2, -1, -1}, 0.0f, 1.0f,  0.01f },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    vec2 q = uv * p.x;\n"
                       "    vec2 fl = floor(q);\n"
                       "    vec2 fr = fract(q);\n"
                       "    float md = 1.0;\n"
                       "    for (int yy = -1; yy <= 1; yy++) {\n"
                       "        for (int xx = -1; xx <= 1; xx++) {\n"
                       "            vec2 nb = vec2(float(xx), float(yy));\n"
                       "            vec2 r = fract(sin(dot(fl + nb, vec2(127.1, 311.7))) * vec2(43758.5453, 22578.1459)) * vec2(1.0);\n"
                       "            vec2 pt = nb + 0.5 + 0.5 * sin(u_time * p.y + 6.2831 * r) - fr;\n"
                       "            md = min(md, dot(pt, pt));\n"
                       "        }\n"
                       "    }\n"
                       "    float d = sqrt(md);\n"
                       "    vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + d + vec3(0.0, 0.33, 0.67)));\n"
                       "    return vec4(col, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Gen: truchet ──────────────────────────────────────────────
        {
            DagNodeDef n;
            n.name   = "truchet";
            n.label  = "truchet";
            n.desc   = "patron truchet curvo";
            n.kind   = DagKind::Gen;
            n.num_inputs = 0;
            n.param_names    = {"scale", "thickness", "hue"};
            n.param_defaults = {10.0f, 0.15f, 0.3f};
            n.controls = {
                { DagControl::Kind::Slider, "escala",   {0, -1, -1}, 1.0f, 40.0f, 0.5f   },
                { DagControl::Kind::Slider, "grosor",   {1, -1, -1}, 0.02f, 0.45f, 0.01f },
                { DagControl::Kind::Slider, "tono",     {2, -1, -1}, 0.0f, 1.0f, 0.01f   },
            };
            n.body_glsl = [](int idx) -> std::string {
                std::string i = std::to_string(idx);
                return "    vec4 p = u_params[" + i + "];\n"
                       "    vec2 q = uv * p.x;\n"
                       "    vec2 fl = floor(q);\n"
                       "    vec2 fr = fract(q);\n"
                       "    float h = fract(sin(dot(fl, vec2(12.9898, 78.233))) * 43758.5453);\n"
                       "    if (h > 0.5) fr.x = 1.0 - fr.x;\n"
                       "    float d1 = abs(length(fr) - 0.5);\n"
                       "    float d2 = abs(length(fr - 1.0) - 0.5);\n"
                       "    float d  = min(d1, d2);\n"
                       "    float fill = 1.0 - smoothstep(p.y - 0.02, p.y, d);\n"
                       "    vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + vec3(0.0, 0.33, 0.67)));\n"
                       "    return vec4(col * fill, 1.0);";
            };
            v.push_back(std::move(n));
        }

        // ── Output (sink — drives fragColor) ─────────────────────────
        {
            DagNodeDef n;
            n.name   = "output";
            n.label  = "Output";
            n.desc   = "canvas DAG output";
            n.kind   = DagKind::Output;
            n.num_inputs = 1;
            n.param_names    = {};
            n.param_defaults = {};
            n.controls = {};
            n.body_glsl = [](int) -> std::string { return ""; };
            v.push_back(std::move(n));
        }

        for (auto& n : v) n.is_builtin = true;
        return v;
    }();
    return catalog;
}

const std::vector<DagNodeDef>& dag_catalog() {
    return mutable_catalog();
}

const DagNodeDef* dag_find(const std::string& name) {
    for (const auto& n : dag_catalog()) {
        if (n.name == name) return &n;
    }
    return nullptr;
}

bool dag_register_node(DagNodeDef def) {
    auto& cat = mutable_catalog();
    for (auto& n : cat) {
        if (n.name == def.name) {
            if (n.is_builtin) return false;
            n = std::move(def);
            n.is_builtin = false;
            return true;
        }
    }
    def.is_builtin = false;
    cat.push_back(std::move(def));
    return true;
}

bool dag_unregister_node(const std::string& name) {
    auto& cat = mutable_catalog();
    auto it = std::find_if(cat.begin(), cat.end(),
        [&](const DagNodeDef& n){ return n.name == name && !n.is_builtin; });
    if (it == cat.end()) return false;
    cat.erase(it);
    return true;
}

} // namespace fn::gfx

#ifdef DAG_CATALOG_TEST
#include <cassert>
#include <cstdio>
#include <set>

int main() {
    using namespace fn::gfx;
    const auto& cat = dag_catalog();

    // 1. Catalog not empty
    assert(!cat.empty());

    // 2. All node names are unique
    std::set<std::string> names;
    for (const auto& n : cat) {
        assert(names.insert(n.name).second && "duplicate node name");
    }

    // 3. Invariants by kind
    int gen_count = 0, op_count = 0, blend_count = 0, output_count = 0;
    for (const auto& n : cat) {
        switch (n.kind) {
            case DagKind::Gen:
                assert(n.num_inputs == 0 && "Gen must have 0 inputs");
                ++gen_count; break;
            case DagKind::Op:
                assert(n.num_inputs >= 1 && "Op must have >= 1 input");
                ++op_count; break;
            case DagKind::Blend:
                assert(n.num_inputs >= 2 && "Blend must have >= 2 inputs");
                ++blend_count; break;
            case DagKind::Output:
                assert(n.num_inputs == 1 && "Output must have exactly 1 input");
                ++output_count; break;
        }
        assert(n.num_inputs >= 0 && n.num_inputs <= 4);
        assert(n.body_glsl && "body_glsl must be set");
    }

    // 4. Exactly one Output in the catalog
    assert(output_count == 1 && "catalog must declare exactly one Output node");

    // 5. At least one Gen and one Op
    assert(gen_count >= 1);
    assert(op_count >= 1);
    assert(blend_count >= 1);

    // 6. dag_find works and returns nullptr for unknown names
    assert(dag_find("output") != nullptr);
    assert(dag_find("plasma") != nullptr);
    assert(dag_find("__nonexistent__") == nullptr);

    // 7. Every non-Output body emits non-empty GLSL
    for (const auto& n : cat) {
        if (n.kind == DagKind::Output) continue;
        auto body = n.body_glsl(0);
        assert(!body.empty() && "non-Output nodes must emit body");
        // sanity: should contain a return statement
        assert(body.find("return") != std::string::npos);
    }

    // 8. Control param indices stay within 0..param_count-1 of their node
    for (const auto& n : cat) {
        int pc = static_cast<int>(n.param_defaults.size());
        for (const auto& c : n.controls) {
            for (int idx : c.param_idx) {
                assert(idx >= -1 && idx < pc);
            }
        }
        assert(n.param_names.size() == n.param_defaults.size());
    }

    std::printf("dag_catalog: 8/8 asserts passed (%zu nodes)\n", cat.size());
    return 0;
}
#endif
