// Tests parciales para viz::render (cpp/functions/viz/viz_render.cpp).
//
// render() requiere un contexto ImGui + ImPlot vivo — no se puede ejercitar
// en tests headless. Este archivo cubre SOLO las funciones helper publicas
// (first_numeric_col, first_category_col, extract_numeric, extract_category)
// que son logica pura sin dependencia de ImGui/ImPlot.
//
// Smoke real del dispatcher: primitives_gallery --capture (golden images, issue 0048).
// Issue 0081-G.

#define CATCH_CONFIG_MAIN
#include "catch_amalgamated.hpp"

// Incluir solo el header de tipos (no ImGui, no ImPlot) para construir
// StageOutput de prueba.
#include "core/data_table_types.h"

// Declaraciones adelantadas de los helpers publicos (evita incluir viz_render.h
// que arrastra imgui.h). Replicar aqui es legal para tests headless.
#include <cmath>
#include <string>
#include <vector>

namespace data_table { struct StageOutput; }

// Incluir la implementacion completa via el .h para obtener las firmas
// publicas. viz_render.h incluye imgui.h, que en tests sin contexto GLFW
// es solo tipos — safe para compilar (no linkamos fn_framework aqui).
//
// Para evitar el pull-in de imgui.h (que necesita GLFW o al menos el stub),
// re-declaramos solo las funciones helper que necesitamos testear.
// Las implementaciones estan en viz_render.cpp que linkamos directamente.

namespace viz {
int first_numeric_col(const data_table::StageOutput& out);
int first_category_col(const data_table::StageOutput& out);
std::vector<double> extract_numeric(const data_table::StageOutput& out, int col);
std::vector<std::string> extract_category(const data_table::StageOutput& out, int col);
} // namespace viz

using namespace data_table;

// ---------------------------------------------------------------------------
// Helper: construir un StageOutput simple para tests
// ---------------------------------------------------------------------------
namespace {

struct TestTable {
    std::vector<std::string>  backing;
    std::vector<const char*>  cells;
    std::vector<std::string>  headers;
    std::vector<ColumnType>   types;
    int rows = 0, cols = 0;

    void add_row(std::initializer_list<const char*> row) {
        for (const char* s : row) backing.emplace_back(s ? s : "");
        ++rows;
    }

    StageOutput build() {
        StageOutput out;
        cols = (int)headers.size();
        cells.clear();
        for (const auto& s : backing) cells.push_back(s.c_str());
        out.headers = headers;
        out.types   = types;
        out.rows    = rows;
        out.cols    = cols;
        out.cells   = cells;
        return out;
    }
};

} // anon

// ---------------------------------------------------------------------------
// first_numeric_col
// ---------------------------------------------------------------------------

TEST_CASE("first_numeric_col returns -1 on empty output", "[viz_render]") {
    StageOutput out;
    REQUIRE(viz::first_numeric_col(out) == -1);
}

TEST_CASE("first_numeric_col returns 0 for all-numeric output", "[viz_render]") {
    TestTable t;
    t.headers = {"a", "b"};
    t.types   = {ColumnType::Float, ColumnType::Int};
    t.add_row({"1.0", "2"});
    auto out = t.build();
    REQUIRE(viz::first_numeric_col(out) == 0);
}

TEST_CASE("first_numeric_col skips string columns", "[viz_render]") {
    TestTable t;
    t.headers = {"cat", "val"};
    t.types   = {ColumnType::String, ColumnType::Float};
    t.add_row({"alfa", "3.14"});
    auto out = t.build();
    // Primera col es String -> primera numerica es col 1
    REQUIRE(viz::first_numeric_col(out) == 1);
}

// ---------------------------------------------------------------------------
// first_category_col
// ---------------------------------------------------------------------------

TEST_CASE("first_category_col returns -1 on all-numeric output", "[viz_render]") {
    TestTable t;
    t.headers = {"x", "y"};
    t.types   = {ColumnType::Int, ColumnType::Float};
    t.add_row({"1", "2.0"});
    auto out = t.build();
    REQUIRE(viz::first_category_col(out) == -1);
}

TEST_CASE("first_category_col returns first string column", "[viz_render]") {
    TestTable t;
    t.headers = {"num", "cat"};
    t.types   = {ColumnType::Int, ColumnType::String};
    t.add_row({"42", "hello"});
    auto out = t.build();
    REQUIRE(viz::first_category_col(out) == 1);
}

// ---------------------------------------------------------------------------
// extract_numeric
// ---------------------------------------------------------------------------

TEST_CASE("extract_numeric returns empty for out-of-range col", "[viz_render]") {
    TestTable t;
    t.headers = {"x"};
    t.types   = {ColumnType::Float};
    t.add_row({"1.5"});
    auto out = t.build();
    // col -1
    REQUIRE(viz::extract_numeric(out, -1).empty());
    // col >= cols
    REQUIRE(viz::extract_numeric(out, 5).empty());
}

TEST_CASE("extract_numeric returns NaN for unparseable cells", "[viz_render]") {
    TestTable t;
    t.headers = {"val"};
    t.types   = {ColumnType::String};
    t.add_row({"abc"});
    t.add_row({"3.14"});
    t.add_row({""});
    auto out = t.build();
    auto v = viz::extract_numeric(out, 0);
    REQUIRE(v.size() == 3);
    REQUIRE(std::isnan(v[0]));
    REQUIRE(!std::isnan(v[1]));
    REQUIRE(std::abs(v[1] - 3.14) < 1e-9);
    REQUIRE(std::isnan(v[2]));
}

// ---------------------------------------------------------------------------
// extract_category
// ---------------------------------------------------------------------------

TEST_CASE("extract_category returns empty for out-of-range col", "[viz_render]") {
    TestTable t;
    t.headers = {"s"};
    t.types   = {ColumnType::String};
    t.add_row({"hello"});
    auto out = t.build();
    REQUIRE(viz::extract_category(out, -1).empty());
    REQUIRE(viz::extract_category(out, 10).empty());
}

TEST_CASE("extract_category returns empty strings for null cells", "[viz_render]") {
    // Construimos un StageOutput con un cell ptr nulo manualmente
    StageOutput out;
    out.headers = {"s"};
    out.types   = {ColumnType::String};
    out.rows    = 2;
    out.cols    = 1;
    // cell[0] = nullptr, cell[1] = "hello"
    static const char* hello = "hello";
    out.cells   = {nullptr, hello};
    auto v = viz::extract_category(out, 0);
    REQUIRE(v.size() == 2);
    REQUIRE(v[0].empty());
    REQUIRE(v[1] == "hello");
}
