// Unit tests para `graph_icons` (issue 0049f).
// Cubre la parte CPU del builder — bake del atlas, layout en grid 16x16,
// regiones 1-indexed, uv_table consistente, y comportamiento ante codepoints
// inexistentes en la fuente. La subida a GPU se desactiva con la env
// `FN_GRAPH_ICONS_SKIP_GL=1` (set en el setup) para que el test corra sin
// contexto GL en CI.

#define CATCH_CONFIG_MAIN
#include "catch_amalgamated.hpp"

#include "viz/graph_icons.h"

#include <cstdint>
#include <cstdlib>

namespace {

// Tabler 3.41: codepoints que sabemos que existen en la TTF embebida en el
// repo (ver cpp/functions/core/icons_tabler.h).
const uint16_t k_known[] = {
    0xEB4Du, // TI_USER
    0xEAE5u, // TI_MAIL
    0xEAB9u, // TI_GLOBE
    0xEB09u, // TI_PHONE
    0xEA4Fu, // TI_BUILDING
    0xEA88u, // TI_DATABASE
};
constexpr int k_known_count = sizeof(k_known) / sizeof(k_known[0]);

bool any_alpha_in_region(const unsigned char* pixels, int W,
                         float u0, float v0, float u1, float v1)
{
    int x0 = (int)(u0 * W);
    int y0 = (int)(v0 * W);
    int x1 = (int)(u1 * W);
    int y1 = (int)(v1 * W);
    for (int y = y0; y < y1; ++y) {
        for (int x = x0; x < x1; ++x) {
            if (pixels[(y * W + x) * 4 + 3] > 0) return true;
        }
    }
    return false;
}

struct EnvSetup {
    EnvSetup() { setenv("FN_GRAPH_ICONS_SKIP_GL", "1", /*overwrite=*/1); }
};
EnvSetup _env_setup;

} // namespace

TEST_CASE("graph_icons_build con 6 codepoints conocidos", "[viz][graph_icons]") {
    IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
    REQUIRE(a != nullptr);
    REQUIRE(graph_icons_count(a)  == k_known_count);
    REQUIRE(graph_icons_width(a)  == 512);
    REQUIRE(graph_icons_height(a) == 512);
    graph_icons_destroy(a);
}

TEST_CASE("regiones 1-indexed y orden conservado", "[viz][graph_icons]") {
    IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
    REQUIRE(a != nullptr);

    // icon_id = 0 -> nullptr (reservado para "sin icono")
    REQUIRE(graph_icons_region(a, 0) == nullptr);

    // icon_id = i+1 -> region con codepoint en posicion i del array
    for (int i = 0; i < k_known_count; ++i) {
        const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
        REQUIRE(r != nullptr);
        REQUIRE(r->id        == (uint16_t)(i + 1));
        REQUIRE(r->codepoint == k_known[i]);
        REQUIRE(r->u0 < r->u1);
        REQUIRE(r->v0 < r->v1);
        REQUIRE(r->u0 >= 0.0f);
        REQUIRE(r->v1 <= 1.0f);
    }

    // icon_id fuera de rango -> nullptr
    REQUIRE(graph_icons_region(a, (uint16_t)(k_known_count + 1)) == nullptr);
    REQUIRE(graph_icons_region(a, 9999) == nullptr);

    graph_icons_destroy(a);
}

TEST_CASE("uv_table coincide con regions", "[viz][graph_icons]") {
    IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
    REQUIRE(a != nullptr);

    const float* uv = graph_icons_uv_table(a);
    REQUIRE(uv != nullptr);

    for (int i = 0; i < k_known_count; ++i) {
        const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
        REQUIRE(uv[i * 4 + 0] == r->u0);
        REQUIRE(uv[i * 4 + 1] == r->v0);
        REQUIRE(uv[i * 4 + 2] == r->u1);
        REQUIRE(uv[i * 4 + 3] == r->v1);
    }

    graph_icons_destroy(a);
}

TEST_CASE("regiones tienen contenido (alpha != 0)", "[viz][graph_icons]") {
    // El test no se puede correr si el TTF no esta accesible — en ese caso
    // graph_icons_build devuelve nullptr y skipeamos.
    IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
    if (!a) {
        WARN("tabler-icons.ttf no encontrado — test de contenido skipped. "
             "Defina FN_CPP_ROOT o copie el TTF a ./assets/.");
        return;
    }

    const unsigned char* px = graph_icons_pixels(a);
    REQUIRE(px != nullptr);

    for (int i = 0; i < k_known_count; ++i) {
        const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
        REQUIRE(any_alpha_in_region(px, graph_icons_width(a),
                                     r->u0, r->v0, r->u1, r->v1));
    }

    graph_icons_destroy(a);
}

TEST_CASE("count fuera de rango devuelve nullptr", "[viz][graph_icons]") {
    REQUIRE(graph_icons_build(k_known, 0,    32) == nullptr);
    REQUIRE(graph_icons_build(k_known, -1,   32) == nullptr);
    REQUIRE(graph_icons_build(k_known, 257,  32) == nullptr); // max 256
    REQUIRE(graph_icons_build(nullptr, k_known_count, 32) == nullptr);
}

TEST_CASE("layout en grid 16x16", "[viz][graph_icons]") {
    IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
    REQUIRE(a != nullptr);

    // Cada celda es 32px = 32/512 = 0.0625 en UV. Con 1px padding, las UVs
    // van de 1/512 a 31/512 dentro de la celda.
    const float cell_uv = 32.0f / 512.0f;
    for (int i = 0; i < k_known_count; ++i) {
        const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
        int row = i / 16;
        int col = i % 16;
        // u0 esta dentro de [col*cell, (col+1)*cell]
        REQUIRE(r->u0 >= col * cell_uv);
        REQUIRE(r->u1 <= (col + 1) * cell_uv);
        REQUIRE(r->v0 >= row * cell_uv);
        REQUIRE(r->v1 <= (row + 1) * cell_uv);
    }

    graph_icons_destroy(a);
}
