// data_table_color_rules — editor de reglas de color por columna + aplicacion.
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
//
// Responsabilidad:
//   - Helpers de color: auto_categorical_color, resolve_categorical_dot_color,
//     apply_color_rules_for_cell.
//   - UI del submenu "Conditional color" dentro del header-menu de columnas:
//     draw_color_rule_menu.
//
// hex_to_imcolor, parse_hex_color, lerp_color_along_stops son helpers de color
// puros. Se definen como static aqui (no se exportan) porque data_table.cpp
// tiene sus propias versiones static tambien — ambos TU son independientes.

#include "viz/data_table_color_rules.h"
#include "data_table/data_table_internal.h"

#include "imgui.h"
#include "core/data_table_types.h"

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <limits>
#include <vector>

namespace {

// hex_to_imcolor: "#rrggbb" -> ImVec4. Returns {-1,-1,-1,-1} on failure.
static ImVec4 hex_to_imcolor(const std::string& hex) {
    const char* p = hex.c_str();
    if (*p == '#') ++p;
    unsigned int r = 0, g = 0, b = 0;
    if (std::sscanf(p, "%02x%02x%02x", &r, &g, &b) != 3)
        return ImVec4(-1.f, -1.f, -1.f, -1.f);
    return ImVec4(r / 255.f, g / 255.f, b / 255.f, 1.f);
}

// lerp_color_along_stops: LERP entre N color stops en [0,1].
static ImU32 lerp_color_along_stops(
    const std::vector<data_table::ColorStop>& stops, float t, float alpha)
{
    static const std::vector<data_table::ColorStop> kDefault = {
        {0.0f, "#22c55e"},
        {0.5f, "#f59e0b"},
        {1.0f, "#ef4444"},
    };
    const auto& sv = stops.empty() ? kDefault : stops;
    std::vector<data_table::ColorStop> sorted_sv = sv;
    std::sort(sorted_sv.begin(), sorted_sv.end(),
              [](const data_table::ColorStop& a, const data_table::ColorStop& b){
                  return a.position < b.position;
              });
    t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
    if (t <= sorted_sv.front().position)
        return data_table::parse_hex_color(sorted_sv.front().color, alpha);
    if (t >= sorted_sv.back().position)
        return data_table::parse_hex_color(sorted_sv.back().color, alpha);
    for (size_t i = 0; i + 1 < sorted_sv.size(); ++i) {
        const auto& lo = sorted_sv[i];
        const auto& hi = sorted_sv[i + 1];
        if (t >= lo.position && t <= hi.position) {
            float span = hi.position - lo.position;
            float f = (span > 1e-6f) ? (t - lo.position) / span : 0.f;
            ImVec4 ca = hex_to_imcolor(lo.color);
            ImVec4 cb = hex_to_imcolor(hi.color);
            if (ca.x < 0.f) ca = ImVec4(0.5f, 0.5f, 0.5f, 1.f);
            if (cb.x < 0.f) cb = ImVec4(0.5f, 0.5f, 0.5f, 1.f);
            float r = ca.x + f * (cb.x - ca.x);
            float g = ca.y + f * (cb.y - ca.y);
            float b = ca.z + f * (cb.z - ca.z);
            unsigned int ri = (unsigned int)(r * 255.f + 0.5f);
            unsigned int gi = (unsigned int)(g * 255.f + 0.5f);
            unsigned int bi = (unsigned int)(b * 255.f + 0.5f);
            unsigned int ai = (unsigned int)(alpha * 255.f + 0.5f);
            return IM_COL32(ri, gi, bi, ai);
        }
    }
    return data_table::parse_hex_color(sorted_sv.back().color, alpha);
}

} // anon namespace

namespace data_table {

// ---------------------------------------------------------------------------
// parse_hex_color: "#rrggbb" / "#rrggbbaa" -> ImU32 with explicit alpha.
// Declared in data_table_color_rules.h (public API — used by data_table.cpp
// cell renderers and by apply_color_rules_for_cell).
// ---------------------------------------------------------------------------
ImU32 parse_hex_color(const std::string& hex, float alpha)
{
    const char* p = hex.c_str();
    if (*p == '#') ++p;
    unsigned int r = 0, g = 0, b = 0, a = 255;
    int parsed = std::sscanf(p, "%02x%02x%02x%02x", &r, &g, &b, &a);
    if (parsed < 3) return IM_COL32(128, 128, 128, 255);
    if (parsed == 3) {
        a = (unsigned int)(alpha * 255.f + 0.5f);
    }
    return IM_COL32(r, g, b, a);
}

// ---------------------------------------------------------------------------
// auto_categorical_color: deterministic palette for a string value.
// Hashes the value to an index in a fixed 12-color palette (Tailwind-ish).
// Same value always maps to the same color. FNV-1a 32-bit.
// ---------------------------------------------------------------------------
ImU32 auto_categorical_color(const char* value, float alpha)
{
    static const unsigned int kPalette[] = {
        0xFFef4444u, // red-500
        0xFFf97316u, // orange-500
        0xFFf59e0bu, // amber-500
        0xFF84cc16u, // lime-500
        0xFF22c55eu, // green-500
        0xFF14b8a6u, // teal-500
        0xFF06b6d4u, // cyan-500
        0xFF3b82f6u, // blue-500
        0xFF8b5cf6u, // violet-500
        0xFFa855f7u, // purple-500
        0xFFec4899u, // pink-500
        0xFFd946efu, // fuchsia-500
    };
    constexpr size_t N = sizeof(kPalette) / sizeof(kPalette[0]);
    uint32_t h = 2166136261u;
    for (const char* p = value ? value : ""; *p; ++p) {
        h ^= (uint8_t)(*p);
        h *= 16777619u;
    }
    unsigned int packed = kPalette[h % N];
    unsigned int a   = (unsigned int)(alpha * 255.f + 0.5f);
    unsigned int rgb = packed & 0x00FFFFFFu;
    return rgb | (a << 24);
}

// ---------------------------------------------------------------------------
// resolve_categorical_dot_color: look up dot color from rule's dot_map; fall
// back to auto_categorical_color if no match.
// ---------------------------------------------------------------------------
ImU32 resolve_categorical_dot_color(const ColorRule& cr, const char* value)
{
    if (!cr.dot_map.empty()) {
        for (const auto& kv : cr.dot_map) {
            if (kv.first == (value ? value : ""))
                return parse_hex_color(kv.second, cr.dot_alpha);
        }
    }
    return auto_categorical_color(value, cr.dot_alpha);
}

// parse_cell_number: parse a cell string as double. Returns NaN on failure.
// Only needed here (not shared).
static double parse_cell_number(const char* s) {
    if (!s || !*s) return std::numeric_limits<double>::quiet_NaN();
    char* end = nullptr;
    double v = std::strtod(s, &end);
    if (end == s) return std::numeric_limits<double>::quiet_NaN();
    return v;
}

// ---------------------------------------------------------------------------
// apply_color_rules_for_cell: applies all matching ColorRules for column `c`.
// Must be called AFTER ImGui::TableSetColumnIndex and BEFORE cell renderer.
// ---------------------------------------------------------------------------
void apply_color_rules_for_cell(const State& st,
                                int c, const char* cell,
                                ImVec2 cell_min, float cell_w, float cell_h,
                                float& dot_advance_px)
{
    dot_advance_px = 0.f;
    ImDrawList* draw = ImGui::GetWindowDrawList();
    for (const auto& cr : st.color_rules) {
        if (cr.col != c) continue;
        switch (cr.kind) {
        case ColorRuleKind::CellBg: {
            if (cell && cr.equals == cell) {
                ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, (ImU32)cr.color);
            }
            break;
        }
        case ColorRuleKind::NumericRange: {
            double v = parse_cell_number(cell);
            if (std::isnan(v)) break;
            double span = cr.range_max - cr.range_min;
            float  t    = (span > 1e-12) ? (float)((v - cr.range_min) / span) : 0.5f;
            ImU32 tint = lerp_color_along_stops(cr.range_stops, t, cr.range_alpha);
            ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, tint);
            break;
        }
        case ColorRuleKind::CategoricalDot: {
            ImU32 dot = resolve_categorical_dot_color(cr, cell);
            float radius = cr.dot_radius_px > 0.f ? cr.dot_radius_px : 4.0f;
            ImVec2 c1(cell_min.x + radius + 2.f,
                      cell_min.y + cell_h * 0.5f);
            draw->AddCircleFilled(c1, radius, dot, 12);
            dot_advance_px = radius * 2.f + 6.f;
            (void)cell_w;
            break;
        }
        }
    }
}

// ---------------------------------------------------------------------------
// draw_color_rule_menu: dibuja el submenu "Conditional color" para la columna
// `col`. Retorna true si el usuario hizo click en "Apply" (la regla ya fue
// anadida a st.color_rules).
//
// editor_st: ColorRuleEditorState de UiState (draft state per columna).
// col_type:  tipo efectivo de la columna (para auto-seleccion de modo).
// ---------------------------------------------------------------------------
bool draw_color_rule_menu(State& st, int col, ColumnType col_type,
                          ColorRuleEditorState& editor_st)
{
    bool applied = false;

    // Auto-pick sensible default on first open for this column.
    if (editor_st.color_rule_kind.find(col) == editor_st.color_rule_kind.end()) {
        editor_st.color_rule_kind[col] =
            (col_type == ColumnType::Int || col_type == ColumnType::Float) ? 2 : 0;
    }
    int& kind_i = editor_st.color_rule_kind[col]; // 0=CellBg, 1=CatDot, 2=NumRange

    ImGui::TextDisabled("Mode");
    ImGui::RadioButton("Cell bg",         &kind_i, 0); ImGui::SameLine();
    ImGui::RadioButton("Categorical dot", &kind_i, 1); ImGui::SameLine();
    ImGui::RadioButton("Numeric range",   &kind_i, 2);
    ImGui::Separator();

    bool apply_clicked = false;
    ColorRule draft;
    draft.col = col;

    if (kind_i == 0) {
        // ---- Cell bg ----
        auto& vbuf = editor_st.color_value_inputs[col];
        vbuf.resize(256, '\0');
        if (editor_st.color_picker_vals.find(col) == editor_st.color_picker_vals.end())
            editor_st.color_picker_vals[col] = ImVec4(0.85f, 0.40f, 0.30f, 0.60f);
        ImVec4& cv = editor_st.color_picker_vals[col];
        ImGui::SetNextItemWidth(180);
        ImGui::InputText("equals", vbuf.data(), vbuf.size());
        ImGui::ColorEdit4("color", &cv.x, ImGuiColorEditFlags_NoInputs);
        if (ImGui::Button("Apply##cellbg")) {
            draft.kind   = ColorRuleKind::CellBg;
            draft.equals = std::string(vbuf.c_str());
            draft.color  = (unsigned int)ImGui::ColorConvertFloat4ToU32(cv);
            apply_clicked = true;
        }
    }
    else if (kind_i == 1) {
        // ---- Categorical dot ----
        auto& d = editor_st.cat_dot_drafts[col];
        ImGui::TextWrapped("Dibuja un punto coloreado al inicio de cada celda.");
        ImGui::TextWrapped("Color asignado automaticamente por valor (palette de 12).");
        ImGui::SetNextItemWidth(120);
        ImGui::SliderFloat("alpha",     &d.alpha,     0.2f, 1.0f);
        ImGui::SetNextItemWidth(120);
        ImGui::SliderFloat("radius px", &d.radius_px, 2.0f, 9.0f);
        // Preview swatches (8 sample values).
        ImGui::TextDisabled("preview:");
        ImGui::SameLine();
        const char* samples[] = {"A","B","C","D","E","F","G","H"};
        for (auto* s : samples) {
            ImU32 col_u = auto_categorical_color(s, d.alpha);
            ImVec2 p = ImGui::GetCursorScreenPos();
            ImGui::GetWindowDrawList()->AddCircleFilled(
                ImVec2(p.x + d.radius_px, p.y + ImGui::GetTextLineHeight() * 0.5f),
                d.radius_px, col_u, 12);
            ImGui::Dummy(ImVec2(d.radius_px * 2.f + 2.f, ImGui::GetTextLineHeight()));
            ImGui::SameLine();
        }
        ImGui::NewLine();
        if (ImGui::Button("Apply##catdot")) {
            draft.kind          = ColorRuleKind::CategoricalDot;
            draft.dot_alpha     = d.alpha;
            draft.dot_radius_px = d.radius_px;
            apply_clicked = true;
        }
    }
    else if (kind_i == 2) {
        // ---- Numeric range (3-color gradient) ----
        auto& d = editor_st.num_range_drafts[col];
        ImGui::SetNextItemWidth(110);
        ImGui::InputDouble("min", &d.min);
        ImGui::SameLine();
        ImGui::SetNextItemWidth(110);
        ImGui::InputDouble("max", &d.max);
        ImGui::SetNextItemWidth(140);
        ImGui::SliderFloat("alpha", &d.alpha, 0.05f, 0.9f);
        ImGui::SetNextItemWidth(100);
        ImGui::InputText("lo color (hex)",  d.c_lo, sizeof(d.c_lo));
        ImGui::SetNextItemWidth(100);
        ImGui::InputText("mid color (hex)", d.c_md, sizeof(d.c_md));
        ImGui::SetNextItemWidth(100);
        ImGui::InputText("hi color (hex)",  d.c_hi, sizeof(d.c_hi));
        // Preview gradient bar (32 lerped cells).
        std::vector<ColorStop> preview_stops = {
            {0.0f, std::string("#") + d.c_lo},
            {0.5f, std::string("#") + d.c_md},
            {1.0f, std::string("#") + d.c_hi},
        };
        ImVec2 p0 = ImGui::GetCursorScreenPos();
        float W = 256.f, H = 16.f;
        for (int i = 0; i < 32; ++i) {
            float t = i / 31.f;
            ImU32 col_u = lerp_color_along_stops(preview_stops, t, 1.0f);
            ImGui::GetWindowDrawList()->AddRectFilled(
                ImVec2(p0.x + (W / 32.f) * i,       p0.y),
                ImVec2(p0.x + (W / 32.f) * (i + 1), p0.y + H),
                col_u);
        }
        ImGui::Dummy(ImVec2(W, H + 4.f));
        if (ImGui::Button("Apply##numrange")) {
            draft.kind        = ColorRuleKind::NumericRange;
            draft.range_min   = d.min;
            draft.range_max   = d.max;
            draft.range_alpha = d.alpha;
            draft.range_stops = preview_stops;
            apply_clicked = true;
        }
    }

    if (apply_clicked) {
        // Replace any existing rule of same kind on this col (one per kind).
        for (size_t i = 0; i < st.color_rules.size();) {
            if (st.color_rules[i].col == col &&
                st.color_rules[i].kind == draft.kind) {
                st.color_rules.erase(st.color_rules.begin() + (int)i);
            } else {
                ++i;
            }
        }
        st.color_rules.push_back(draft);
        ImGui::CloseCurrentPopup();
        applied = true;
    }
    ImGui::SameLine();
    if (ImGui::Button("Clear col")) {
        for (size_t i = 0; i < st.color_rules.size();) {
            if (st.color_rules[i].col == col)
                st.color_rules.erase(st.color_rules.begin() + (int)i);
            else
                ++i;
        }
    }

    return applied;
}

} // namespace data_table
