// tql_emit_test.cpp — Tests for tql_emit (pure TQL serialization).
// Run with: cmake --build cpp/build --target tql_emit_test && cpp/build/tql_emit_test
// Or via: ./fn run tql_emit_cpp_core

#include "core/tql_emit.h"
#include "core/tql_helpers.h"

#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

using namespace data_table;

// ---------------------------------------------------------------------------
// Minimal assertions
// ---------------------------------------------------------------------------
static int g_pass = 0, g_fail = 0;

static void check(bool cond, const char* label) {
    if (cond) {
        std::printf("PASS: %s\n", label);
        ++g_pass;
    } else {
        std::printf("FAIL: %s\n", label);
        ++g_fail;
    }
}

static bool contains(const std::string& haystack, const char* needle) {
    return haystack.find(needle) != std::string::npos;
}

// ---------------------------------------------------------------------------
// Test: emit empty state produces valid header
// ---------------------------------------------------------------------------
static void test_emit_empty_state() {
    State st;
    std::vector<std::string> headers;
    std::vector<ColumnType>  types;
    std::string out = tql::emit(st, headers, types);

    check(contains(out, "version = 1"),         "emit empty state: version=1");
    check(contains(out, "display = \"table\""), "emit empty state: display=table");
    check(contains(out, "stages = {"),          "emit empty state: stages block");
    check(contains(out, "columns = {"),         "emit empty state: columns block");
    check(contains(out, "views = {"),           "emit empty state: views block");
}

// ---------------------------------------------------------------------------
// Test: emit state with one column
// ---------------------------------------------------------------------------
static void test_emit_single_column() {
    State st;
    st.stages.push_back(Stage{});
    st.col_visible = {true};
    st.col_order   = {0};

    std::vector<std::string> headers = {"name"};
    std::vector<ColumnType>  types   = {ColumnType::String};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "name = \"name\""),      "emit single col: name field");
    check(contains(out, "type = \"string\""),    "emit single col: type=string");
    check(contains(out, "visible = true"),       "emit single col: visible=true");
    check(contains(out, "order = 1"),            "emit single col: order=1");
}

// ---------------------------------------------------------------------------
// Test: emit state with filter in stage 0
// ---------------------------------------------------------------------------
static void test_emit_filter() {
    State st;
    Stage s0;
    s0.filters.push_back({0, Op::Eq, "Alice"});
    st.stages.push_back(s0);

    std::vector<std::string> headers = {"name", "age"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::Int};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "filter = {"),           "emit filter: filter block");
    check(contains(out, "\"=\""),                "emit filter: op=");
    check(contains(out, "\"name\""),             "emit filter: col name");
    check(contains(out, "\"Alice\""),            "emit filter: value");
}

// ---------------------------------------------------------------------------
// Test: emit state with sort
// ---------------------------------------------------------------------------
static void test_emit_sort() {
    State st;
    Stage s0;
    s0.sorts.push_back({"age", true}); // desc
    st.stages.push_back(s0);

    std::vector<std::string> headers = {"name", "age"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::Int};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "sort = {"),    "emit sort: sort block");
    check(contains(out, "\"desc\""),    "emit sort: direction desc");
    check(contains(out, "\"age\""),     "emit sort: col name age");
}

// ---------------------------------------------------------------------------
// Test: emit stage 1 with breakout + aggregation
// ---------------------------------------------------------------------------
static void test_emit_grouped_stage() {
    State st;
    st.stages.push_back(Stage{}); // stage 0 empty

    Stage s1;
    s1.breakouts.push_back("dept");
    Aggregation a;
    a.fn  = AggFn::Sum;
    a.col = "salary";
    s1.aggregations.push_back(a);
    st.stages.push_back(s1);

    std::vector<std::string> headers = {"dept", "salary"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::Float};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "breakout = {"), "emit grouped: breakout block");
    check(contains(out, "\"dept\""),     "emit grouped: breakout col");
    check(contains(out, "aggregation = {"), "emit grouped: aggregation block");
    check(contains(out, "\"sum\""),      "emit grouped: agg fn sum");
    check(contains(out, "\"salary\""),   "emit grouped: agg col salary");
}

// ---------------------------------------------------------------------------
// Test: emit with color rule
// ---------------------------------------------------------------------------
static void test_emit_color_rule() {
    State st;
    st.stages.push_back(Stage{});
    ColorRule cr;
    cr.col    = 0;
    cr.equals = "Alice";
    cr.color  = 0xFF0000FF; // red, full alpha: ABGR -> r=0xFF g=0x00 b=0x00 a=0xFF
    st.color_rules.push_back(cr);

    std::vector<std::string> headers = {"name"};
    std::vector<ColumnType>  types   = {ColumnType::String};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "color_rules = {"),  "emit color rule: block");
    check(contains(out, "equals = \"Alice\""), "emit color rule: equals");
    check(contains(out, "color = \"#"),      "emit color rule: hex color");
}

// ---------------------------------------------------------------------------
// Test: emit with viz panel (extra panel)
// ---------------------------------------------------------------------------
static void test_emit_viz_panel() {
    State st;
    st.stages.push_back(Stage{});
    st.display = ViewMode::Bar;
    st.viz_config.x_col  = "month";
    st.viz_config.y_cols = {"revenue", "cost"};

    std::vector<std::string> headers = {"month", "revenue", "cost"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::Float, ColumnType::Float};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "display = \"bar\""), "emit viz panel: display=bar");
    check(contains(out, "x_col = \"month\""), "emit viz panel: x_col");
    check(contains(out, "y_cols = {"),        "emit viz panel: y_cols block");
    check(contains(out, "\"revenue\""),       "emit viz panel: y_col revenue");
}

// ---------------------------------------------------------------------------
// Test: emit join
// ---------------------------------------------------------------------------
static void test_emit_join() {
    State st;
    st.stages.push_back(Stage{});
    Join jn;
    jn.alias    = "dept_table";
    jn.source   = "departments";
    jn.strategy = JoinStrategy::Left;
    jn.on.push_back({"dept_id", "id"});
    st.joins.push_back(jn);

    std::vector<std::string> headers = {"dept_id", "name"};
    std::vector<ColumnType>  types   = {ColumnType::Int, ColumnType::String};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "joins = {"),          "emit join: joins block");
    check(contains(out, "alias = \"dept_table\""), "emit join: alias");
    check(contains(out, "source = \"departments\""), "emit join: source");
    check(contains(out, "strategy = \"left\""), "emit join: strategy left");
    check(contains(out, "\"dept_id\""),         "emit join: on left col");
    check(contains(out, "\"id\""),              "emit join: on right col");
}

// ---------------------------------------------------------------------------
// Test: lua_string_literal escaping
// ---------------------------------------------------------------------------
static void test_lua_string_literal() {
    check(lua_string_literal("hello")   == "\"hello\"",        "literal: plain string");
    check(lua_string_literal("a\"b")    == "\"a\\\"b\"",       "literal: embedded quote");
    check(lua_string_literal("a\\b")    == "\"a\\\\b\"",       "literal: backslash");
    check(lua_string_literal("a\nb")    == "\"a\\nb\"",        "literal: newline");
    check(lua_string_literal("")        == "\"\"",             "literal: empty");
}

// ---------------------------------------------------------------------------
// Test: color_to_hex / hex_to_color roundtrip
// ---------------------------------------------------------------------------
static void test_color_roundtrip() {
    // Opaque red in ABGR (0xAA BB GG RR): r=0xFF g=0x00 b=0x00 a=0xFF
    unsigned int c = 0xFF0000FFu;
    std::string hex = color_to_hex(c);
    unsigned int back = hex_to_color(hex);
    check(back == c, "color roundtrip: opaque red");

    // Semi-transparent green
    unsigned int c2 = (0x80u << 24) | (0xFFu << 8); // a=0x80 g=0xFF
    std::string hex2 = color_to_hex(c2);
    unsigned int back2 = hex_to_color(hex2);
    check(back2 == c2, "color roundtrip: semi-transparent green");
}

// ---------------------------------------------------------------------------
// Test: emit with Badge column_spec in aux_column_specs
// ---------------------------------------------------------------------------
static void test_emit_column_specs_badge() {
    State st;
    st.stages.push_back(Stage{});

    // Populate aux_column_specs[0] with a Badge spec.
    ColumnSpec cs;
    cs.id       = "status";
    cs.renderer = CellRenderer::Badge;
    cs.badges   = { BadgeRule{"ok", "#22c55e", "OK"}, BadgeRule{"error", "#ef4444", ""} };
    st.aux_column_specs.push_back({cs});

    std::vector<std::string> headers = {"status", "value"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::Float};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "column_specs = {"),    "emit column_specs badge: block");
    check(contains(out, "renderer = \"badge\""), "emit column_specs badge: renderer");
    check(contains(out, "id = \"status\""),      "emit column_specs badge: id");
    check(contains(out, "\"#22c55e\""),          "emit column_specs badge: color ok");
    check(contains(out, "\"error\""),            "emit column_specs badge: value error");
}

// ---------------------------------------------------------------------------
// Test: emit with Button column_spec in aux_column_specs
// ---------------------------------------------------------------------------
static void test_emit_column_specs_button() {
    State st;
    st.stages.push_back(Stage{});

    ColumnSpec cs;
    cs.id               = "actions";
    cs.renderer         = CellRenderer::Button;
    cs.button_action    = "cancel";
    cs.button_label     = "Cancel";
    cs.button_color_hex = "#ef4444";
    st.aux_column_specs.push_back({cs});

    std::vector<std::string> headers = {"name", "actions"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::String};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "renderer = \"button\""),       "emit column_specs button: renderer");
    check(contains(out, "button_action = \"cancel\""),  "emit column_specs button: action");
    check(contains(out, "button_label = \"Cancel\""),   "emit column_specs button: label");
    check(contains(out, "button_color = \"#ef4444\""),  "emit column_specs button: color");
}

// ---------------------------------------------------------------------------
// Test: emit with Tooltip column_spec in aux_column_specs
// ---------------------------------------------------------------------------
static void test_emit_column_specs_tooltip() {
    State st;
    st.stages.push_back(Stage{});

    ColumnSpec cs;
    cs.id              = "name";
    cs.renderer        = CellRenderer::Text;
    cs.tooltip         = "auto";
    cs.tooltip_on_hover = true;
    st.aux_column_specs.push_back({cs});

    std::vector<std::string> headers = {"name"};
    std::vector<ColumnType>  types   = {ColumnType::String};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "tooltip = \"auto\""),       "emit column_specs tooltip: auto");
    check(contains(out, "tooltip_on_hover = true"),  "emit column_specs tooltip: on_hover");
}

// ---------------------------------------------------------------------------
// Test: emit with Dots column_spec in aux_column_specs (v1.3.0)
// ---------------------------------------------------------------------------
static void test_emit_column_specs_dots() {
    State st;
    st.stages.push_back(Stage{});

    ColumnSpec cs;
    cs.id              = "recent";
    cs.renderer        = CellRenderer::Dots;
    cs.badges          = {{"ok", "#22c55e", ""}, {"error", "#ef4444", ""}};
    cs.dots_max        = 5;
    cs.dots_show_count = false;
    cs.tooltip_on_hover = true;
    st.aux_column_specs.push_back({cs});

    std::vector<std::string> headers = {"name", "recent"};
    std::vector<ColumnType>  types   = {ColumnType::String, ColumnType::String};

    std::string out = tql::emit(st, headers, types);

    check(contains(out, "renderer = \"dots\""),      "emit column_specs dots: renderer");
    check(contains(out, "id = \"recent\""),          "emit column_specs dots: id");
    check(contains(out, "dots_max = 5"),             "emit column_specs dots: dots_max");
    check(contains(out, "tooltip_on_hover = true"),  "emit column_specs dots: tooltip_on_hover");
    check(contains(out, "\"#22c55e\""),              "emit column_specs dots: badge color ok");
    check(contains(out, "\"error\""),                "emit column_specs dots: badge value error");
}

// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main() {
    test_emit_empty_state();
    test_emit_single_column();
    test_emit_filter();
    test_emit_sort();
    test_emit_grouped_stage();
    test_emit_color_rule();
    test_emit_viz_panel();
    test_emit_join();
    test_lua_string_literal();
    test_color_roundtrip();
    test_emit_column_specs_badge();
    test_emit_column_specs_button();
    test_emit_column_specs_tooltip();
    test_emit_column_specs_dots();

    std::printf("---\nResults: %d passed, %d failed\n", g_pass, g_fail);
    return g_fail == 0 ? 0 : 1;
}
