// data_table_grid — render del grid de datos con renderers declarativos.
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
//
// Contiene:
//   - Helpers estaticos de celda: hex_to_imcolor, lerp_color_along_stops,
//     icon_name_to_glyph, column_type_icon (copia local para este TU).
//   - draw_cell_custom: renderer declarativo por ColumnSpec.
//   - draw_row_inspector_modal: modal de inspeccion de fila.
//   - render_grid_stage0: grid completo para stage == 0.
//   - render_grid_stage_n: grid para stage > 0 (datos materializados).
//
// Dependencias:
//   viz/data_table_grid.h
//   viz/data_table_drill.h         (make_drill_filter, apply_drill_step, drill_into)
//   viz/data_table_color_rules.h   (parse_hex_color, apply_color_rules_for_cell)
//   viz/data_table_chips.h         (apply_header_sort_click, draw_header_menu)
//   data_table/data_table_internal.h (UiState, ui(), reorder_column via data_table.cpp)
//   core/data_table_types.h
//   core/tql_helpers.h             (parse_breakout_granularity)
//   core/auto_detect_type.h        (parse_number)
//   imgui.h, core/icons_tabler.h

#include "viz/data_table_grid.h"
#include "viz/data_table_drill.h"
#include "viz/data_table_color_rules.h"
#include "viz/data_table_chips.h"
#include "data_table/data_table_internal.h"
#include "core/data_table_types.h"
#include "core/tql_helpers.h"
#include "core/auto_detect_type.h"
#include "imgui.h"
#include "core/icons_tabler.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
#include <unordered_map>
#include <vector>

namespace data_table {

// ---------------------------------------------------------------------------
// Static helpers (only used by this TU)
// ---------------------------------------------------------------------------

static const char* column_type_icon_g(ColumnType t) {
    switch (t) {
        case ColumnType::Auto:   return "\xef\xa4\x9d"; // TI_HELP_CIRCLE
        case ColumnType::String: return "\xef\x95\xa7"; // TI_ABC
        case ColumnType::Int:    return "\xef\x95\x94"; // TI_123
        case ColumnType::Float:  return "\xef\xa8\xa6"; // TI_DECIMAL
        case ColumnType::Bool:   return "\xee\xae\xa6"; // TI_CHECKBOX
        case ColumnType::Date:   return "\xee\xa9\x93"; // TI_CALENDAR
        case ColumnType::Json:   return "\xee\xaf\x8c"; // TI_BRACES
    }
    return "?";
}

static ImVec4 hex_to_imcolor_g(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);
}

static ImU32 lerp_color_along_stops_g(
    const std::vector<ColorStop>& stops, float t, float alpha)
{
    static const std::vector<ColorStop> kDefault = {
        {0.0f, "#22c55e"},
        {0.5f, "#f59e0b"},
        {1.0f, "#ef4444"},
    };
    const auto& sv = stops.empty() ? kDefault : stops;
    std::vector<ColorStop> sorted_sv = sv;
    std::sort(sorted_sv.begin(), sorted_sv.end(),
              [](const ColorStop& a, const 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 parse_hex_color(sorted_sv.front().color, alpha);
    if (t >= sorted_sv.back().position)
        return 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_g(lo.color);
            ImVec4 cb = hex_to_imcolor_g(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 rv = ca.x + f * (cb.x - ca.x);
            float gv = ca.y + f * (cb.y - ca.y);
            float bv = ca.z + f * (cb.z - ca.z);
            unsigned int ri = (unsigned int)(rv * 255.f + 0.5f);
            unsigned int gi = (unsigned int)(gv * 255.f + 0.5f);
            unsigned int bi = (unsigned int)(bv * 255.f + 0.5f);
            unsigned int ai = (unsigned int)(alpha * 255.f + 0.5f);
            return IM_COL32(ri, gi, bi, ai);
        }
    }
    return parse_hex_color(sorted_sv.back().color, alpha);
}

static const char* icon_name_to_glyph_g(const std::string& name) {
    static const std::unordered_map<std::string, const char*> kMap = {
        {"TI_CHECK",         TI_CHECK},
        {"TI_X",             TI_X},
        {"TI_ALERT_CIRCLE",  TI_ALERT_CIRCLE},
        {"TI_CIRCLE_DOT",    TI_CIRCLE_DOT},
        {"TI_CLOCK",         TI_CLOCK},
        {"TI_LOADER",        TI_LOADER},
        {"TI_BAN",           TI_BAN},
        {"TI_PLAYER_PLAY",   TI_PLAYER_PLAY},
        {"TI_PLAYER_PAUSE",  TI_PLAYER_PAUSE},
        {"TI_PLAYER_STOP",   TI_PLAYER_STOP},
        {"TI_DATABASE",      TI_DATABASE},
        {"TI_SETTINGS",      TI_SETTINGS},
        {"TI_USER",          TI_USER},
        {"TI_USERS",         TI_USERS},
        {"TI_FILE",          TI_FILE},
        {"TI_FOLDER",        TI_FOLDER},
        {"TI_REFRESH",       TI_REFRESH},
        {"TI_BOLT",          TI_BOLT},
        {"TI_INFO_CIRCLE",   TI_INFO_CIRCLE},
        {"TI_ARROW_UP",      TI_ARROW_UP},
        {"TI_ARROW_DOWN",    TI_ARROW_DOWN},
        {"TI_ARROW_RIGHT",   TI_ARROW_RIGHT},
        {"TI_ARROW_LEFT",    TI_ARROW_LEFT},
        {"TI_DOTS",          TI_DOTS},
        {"TI_EYE",           TI_EYE},
        {"TI_EYE_OFF",       TI_EYE_OFF},
        {"TI_EDIT",          TI_EDIT},
        {"TI_TRASH",         TI_TRASH},
        {"TI_COPY",          TI_COPY},
        {"TI_EXTERNAL_LINK", TI_EXTERNAL_LINK},
    };
    auto it = kMap.find(name);
    return it != kMap.end() ? it->second : nullptr;
}

// row_to_tsv_g: serializa una fila a header\tvalues.
static std::string row_to_tsv_g(const char* const* cells, int rows, int cols,
                                 int row_idx, const std::vector<std::string>& headers) {
    if (row_idx < 0 || row_idx >= rows || cols <= 0) return "";
    std::string out;
    for (int c = 0; c < cols; ++c) {
        if (c > 0) out += '\t';
        if (c < (int)headers.size()) out += headers[c];
    }
    out += "\r\n";
    for (int c = 0; c < cols; ++c) {
        if (c > 0) out += '\t';
        const char* v = cells[row_idx * cols + c];
        if (v) out += v;
    }
    out += "\r\n";
    return out;
}

// reorder_column_g: mueve col src a posicion de col dst en st.col_order.
static void reorder_column_g(State& st, int src, int dst) {
    if (src == dst) return;
    auto it_s = std::find(st.col_order.begin(), st.col_order.end(), src);
    auto it_d = std::find(st.col_order.begin(), st.col_order.end(), dst);
    if (it_s == st.col_order.end() || it_d == st.col_order.end()) return;
    int si = (int)(it_s - st.col_order.begin());
    int di = (int)(it_d - st.col_order.begin());
    int v  = st.col_order[si];
    st.col_order.erase(st.col_order.begin() + si);
    if (di > (int)st.col_order.size()) di = (int)st.col_order.size();
    st.col_order.insert(st.col_order.begin() + di, v);
}

// ---------------------------------------------------------------------------
// draw_cell_custom
// ---------------------------------------------------------------------------
void draw_cell_custom(const ColumnSpec& spec, const char* value,
                      int row_idx, int col_idx,
                      std::vector<TableEvent>* events_out)
{
    if (!value) value = "";

    switch (spec.renderer) {
        case CellRenderer::Badge: {
            const BadgeRule* matched = nullptr;
            for (const auto& br : spec.badges) {
                if (br.value == value) { matched = &br; break; }
            }
            if (matched) {
                ImVec4 col = hex_to_imcolor_g(matched->color_hex);
                const char* label = matched->label.empty() ? value : matched->label.c_str();
                if (col.x >= 0.f) {
                    ImGui::PushStyleColor(ImGuiCol_Header, col);
                    ImVec4 hover_col = ImVec4(
                        std::min(col.x + 0.1f, 1.f),
                        std::min(col.y + 0.1f, 1.f),
                        std::min(col.z + 0.1f, 1.f),
                        col.w);
                    ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hover_col);
                    ImGui::PushStyleColor(ImGuiCol_HeaderActive,  hover_col);
                    ImGui::Selectable(label, false);
                    ImGui::PopStyleColor(3);
                } else {
                    ImGui::TextUnformatted(label);
                }
            } else {
                ImGui::TextUnformatted(value);
            }
            break;
        }

        case CellRenderer::Progress: {
            double v_raw = 0.0;
            if (!parse_number(value, v_raw)) {
                ImGui::TextUnformatted(value);
                break;
            }
            float fv = (float)v_raw;
            if (spec.progress_scale_100) fv /= 100.f;
            fv = fv < 0.f ? 0.f : (fv > 1.f ? 1.f : fv);
            bool has_color = !spec.progress_color_hex.empty();
            ImVec4 bar_col;
            if (has_color) {
                bar_col = hex_to_imcolor_g(spec.progress_color_hex);
                if (bar_col.x < 0.f) has_color = false;
            }
            if (has_color) ImGui::PushStyleColor(ImGuiCol_PlotHistogram, bar_col);
            ImGui::ProgressBar(fv, ImVec2(-1.f, 0.f));
            if (has_color) ImGui::PopStyleColor();
            break;
        }

        case CellRenderer::Duration: {
            double v_raw = 0.0;
            if (!parse_number(value, v_raw)) {
                ImGui::TextUnformatted(value);
                break;
            }
            float ms = (float)v_raw;
            ImVec4 text_col;
            if (ms <= spec.duration_warn_ms) {
                text_col = hex_to_imcolor_g("#22c55e");
            } else if (ms <= spec.duration_error_ms) {
                text_col = hex_to_imcolor_g("#f59e0b");
            } else {
                text_col = hex_to_imcolor_g("#ef4444");
            }
            char buf[32];
            std::snprintf(buf, sizeof(buf), "%.0f ms", ms);
            ImGui::TextColored(text_col, "%s", buf);
            break;
        }

        case CellRenderer::Icon: {
            const char* glyph = nullptr;
            ImVec4 icon_col(-1.f, -1.f, -1.f, -1.f);
            for (const auto& entry : spec.icon_map) {
                if (entry.value == value) {
                    glyph    = icon_name_to_glyph_g(entry.icon_name);
                    if (!entry.color_hex.empty())
                        icon_col = hex_to_imcolor_g(entry.color_hex);
                    break;
                }
            }
            if (glyph) {
                if (icon_col.x >= 0.f)
                    ImGui::TextColored(icon_col, "%s", glyph);
                else
                    ImGui::TextUnformatted(glyph);
            } else {
                ImGui::TextUnformatted(value);
            }
            break;
        }

        case CellRenderer::Button: {
            if (value[0] == '\0') break;
            const char* label = spec.button_label.empty() ? value : spec.button_label.c_str();
            bool has_color = !spec.button_color_hex.empty();
            if (has_color) {
                ImVec4 btn_col = hex_to_imcolor_g(spec.button_color_hex);
                if (btn_col.x >= 0.f) {
                    ImGui::PushStyleColor(ImGuiCol_Button, btn_col);
                    ImVec4 hov = ImVec4(
                        std::min(btn_col.x + 0.12f, 1.f),
                        std::min(btn_col.y + 0.12f, 1.f),
                        std::min(btn_col.z + 0.12f, 1.f),
                        btn_col.w);
                    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hov);
                    ImGui::PushStyleColor(ImGuiCol_ButtonActive,  hov);
                } else {
                    has_color = false;
                }
            }
            char btn_id[128];
            std::snprintf(btn_id, sizeof(btn_id), "%s##btn_%d_%d",
                          label, row_idx, col_idx);
            if (ImGui::SmallButton(btn_id) && events_out) {
                TableEvent ev;
                ev.kind      = TableEventKind::ButtonClick;
                ev.row       = row_idx;
                ev.col       = col_idx;
                ev.column_id = spec.id;
                ev.action_id = spec.button_action;
                ev.value     = value;
                events_out->push_back(std::move(ev));
            }
            if (has_color) ImGui::PopStyleColor(3);
            break;
        }

        case CellRenderer::Dots: {
            std::string s = value;
            std::vector<std::string> tokens;
            {
                std::string cur;
                char sep = spec.dots_separator ? spec.dots_separator : ',';
                for (char c : s) {
                    if (c == sep) { tokens.push_back(cur); cur.clear(); }
                    else cur.push_back(c);
                }
                if (!cur.empty()) tokens.push_back(cur);
            }
            int limit = (spec.dots_max > 0)
                        ? std::min((int)tokens.size(), spec.dots_max)
                        : (int)tokens.size();

            float font_h    = ImGui::GetTextLineHeight();
            float radius    = (spec.dots_glyph_size > 0.f ? spec.dots_glyph_size : font_h * 0.32f);
            float spacing   = radius * 2.3f;
            ImVec2 origin   = ImGui::GetCursorScreenPos();
            float dot_y     = origin.y + font_h * 0.5f;
            ImDrawList* dl  = ImGui::GetWindowDrawList();
            ImU32 default_col = IM_COL32(110, 110, 125, 255);

            for (int t = 0; t < limit; ++t) {
                ImU32 col = default_col;
                for (const auto& br : spec.badges) {
                    if (br.value == tokens[t]) {
                        ImVec4 c = hex_to_imcolor_g(br.color_hex);
                        if (c.x >= 0.f) col = ImGui::ColorConvertFloat4ToU32(c);
                        break;
                    }
                }
                float cx = origin.x + radius + t * spacing;
                dl->AddCircleFilled(ImVec2(cx, dot_y), radius, col, 18);
            }

            float total_w = (limit > 0 ? (limit * spacing) : 0.f);
            ImGui::Dummy(ImVec2(total_w, font_h));

            if (spec.tooltip_on_hover && ImGui::IsItemHovered()) {
                ImVec2 mp = ImGui::GetMousePos();
                for (int t = 0; t < limit; ++t) {
                    float cx = origin.x + radius + t * spacing;
                    float dx = mp.x - cx, dy = mp.y - dot_y;
                    if (dx*dx + dy*dy <= radius * radius * 1.5f) {
                        ImGui::SetTooltip("%s", tokens[t].c_str());
                        break;
                    }
                }
            }

            if (spec.dots_show_count) {
                ImGui::SameLine(0, 6.0f);
                ImGui::TextDisabled("(%d)", (int)tokens.size());
            }
            break;
        }

        case CellRenderer::CategoricalChip: {
            const ChipRule* matched_chip = nullptr;
            for (const auto& cr : spec.chips) {
                if (cr.match == value) { matched_chip = &cr; break; }
            }
            if (matched_chip) {
                float font_h   = ImGui::GetTextLineHeight();
                ImVec2 cursor  = ImGui::GetCursorScreenPos();
                float radius   = 4.0f;
                float cy       = cursor.y + font_h * 0.5f;
                float cx       = cursor.x + radius;
                ImDrawList* dl = ImGui::GetWindowDrawList();
                ImU32 chip_col = parse_hex_color(matched_chip->color, 1.0f);
                dl->AddCircleFilled(ImVec2(cx, cy), radius, chip_col, 18);
                ImGui::Dummy(ImVec2(radius * 2.0f + 4.0f, font_h));
                ImGui::SameLine(0, 0);
            }
            ImGui::TextUnformatted(value);
            break;
        }

        case CellRenderer::ColorScale: {
            double v_raw = 0.0;
            if (!parse_number(value, v_raw)) {
                ImGui::TextUnformatted(value);
                break;
            }
            double span = spec.range_max - spec.range_min;
            float t = 0.f;
            if (span > 1e-12) {
                t = (float)((v_raw - spec.range_min) / span);
            }
            t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);

            ImU32 bg_col = lerp_color_along_stops_g(spec.range_stops, t, spec.range_alpha);
            ImVec2 cell_min = ImGui::GetCursorScreenPos();
            float row_h = ImGui::GetTextLineHeight();
            float col_w = ImGui::GetContentRegionAvail().x;
            ImVec2 cell_max = ImVec2(cell_min.x + col_w, cell_min.y + row_h);
            ImGui::GetWindowDrawList()->AddRectFilled(cell_min, cell_max, bg_col);

            ImGui::TextUnformatted(value);
            break;
        }

        default:
            ImGui::TextUnformatted(value);
            break;
    }

    // Tooltip (non-Dots renderers).
    if (spec.renderer != CellRenderer::Dots &&
        spec.tooltip_on_hover && ImGui::IsItemHovered()) {
        const char* tip = (spec.tooltip == "auto") ? value : spec.tooltip.c_str();
        if (tip && tip[0]) ImGui::SetTooltip("%s", tip);
    }
}

// ---------------------------------------------------------------------------
// draw_row_inspector_modal (static — solo usada por render_grid_stage_n)
// ---------------------------------------------------------------------------
static void draw_row_inspector_modal(State& st, int active,
                                      const char* const* cells, int rows, int cols,
                                      const std::vector<std::string>& headers,
                                      const std::vector<ColumnType>& types,
                                      const std::vector<std::string>& prev_input_headers)
{
    if (!st.inspect_open) return;
    if (st.inspect_row < 0 || st.inspect_row >= rows) {
        st.inspect_open = false;
        return;
    }
    ImGui::OpenPopup("##row_inspector");
    ImGui::SetNextWindowSize(ImVec2(560, 400), ImGuiCond_Appearing);
    if (ImGui::BeginPopupModal("##row_inspector", &st.inspect_open,
                                ImGuiWindowFlags_NoSavedSettings)) {
        ImGui::Text("Row %d", st.inspect_row);
        ImGui::SameLine(0, 20);
        if (ImGui::SmallButton("Copy TSV")) {
            std::string tsv = row_to_tsv_g(cells, rows, cols, st.inspect_row, headers);
            ImGui::SetClipboardText(tsv.c_str());
        }
        ImGui::SameLine();
        bool can_filter = (active > 0 && !prev_input_headers.empty());
        ImGui::BeginDisabled(!can_filter);
        if (ImGui::SmallButton("Filter prev stage by this row")) {
            int target = active - 1;
            for (int c = 0; c < cols; ++c) {
                const char* v = cells[st.inspect_row * cols + c];
                if (!v || !*v) continue;
                const std::string& h = headers[c];
                std::string h_clean;
                parse_breakout_granularity(h, h_clean);
                int ci = -1;
                for (size_t i = 0; i < prev_input_headers.size(); ++i) {
                    if (prev_input_headers[i] == h_clean) { ci = (int)i; break; }
                }
                if (ci < 0) continue;
                DrillStep step;
                step.target_stage      = target;
                step.filter_pos        = (int)st.stages[target].filters.size();
                step.prev_active_stage = st.active_stage;
                step.added             = make_drill_filter(ci, v);
                if (apply_drill_step(st, step)) {
                    st.drill_back.push_back(step);
                }
            }
            st.drill_forward.clear();
            st.inspect_open = false;
        }
        ImGui::EndDisabled();
        ImGui::Separator();
        ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg
                              | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable;
        if (ImGui::BeginTable("##inspector_tbl", 2, flags, ImVec2(-1, -1))) {
            ImGui::TableSetupColumn("col");
            ImGui::TableSetupColumn("value");
            ImGui::TableHeadersRow();
            for (int c = 0; c < cols; ++c) {
                ImGui::TableNextRow();
                ImGui::TableSetColumnIndex(0);
                ColumnType t = (c < (int)types.size()) ? types[c] : ColumnType::String;
                ImGui::Text("%s %s", column_type_icon_g(t),
                            (c < (int)headers.size()) ? headers[c].c_str() : "?");
                ImGui::TableSetColumnIndex(1);
                const char* v = cells[st.inspect_row * cols + c];
                ImGui::TextWrapped("%s", v ? v : "");
            }
            ImGui::EndTable();
        }
        ImGui::EndPopup();
    }
}

// ---------------------------------------------------------------------------
// render_grid_stage0
// ---------------------------------------------------------------------------
void render_grid_stage0(const char* id,
                        State& st,
                        const char* const* cells,
                        int row_count, int orig_cols, int eff_cols,
                        const char* const* eff_headers,
                        const ColumnType* eff_types,
                        const int* src_for_eff,
                        const std::vector<int>& visible_rows,
                        const TableInput& main_t,
                        std::vector<TableEvent>* events_out)
{
    UiState& U = ui();
    Stage& act = st.stages[0];

    // draw_header_menu takes const std::vector<ColumnType>&; build a local view.
    std::vector<ColumnType> eff_types_vec(eff_types, eff_types + eff_cols);

    int visible_cols = 0;
    for (int c = 0; c < eff_cols; ++c)
        if (st.col_visible[c]) ++visible_cols;

    if (visible_cols == 0) return;

    ImGuiTableFlags flags =
        ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
        ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY;
    // Issue 0081-O.7: disable ALL Selectable bg painting; hover + selection
    // painted via TableSetBgColor below.
    ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0));
    ImGui::PushStyleColor(ImGuiCol_HeaderActive,  ImVec4(0, 0, 0, 0));
    ImGui::PushStyleColor(ImGuiCol_Header,        ImVec4(0, 0, 0, 0));

    if (ImGui::BeginTable(id, visible_cols, flags, ImVec2(0, 0))) {

        for (int dc = 0; dc < (int)st.col_order.size(); ++dc) {
            int c = st.col_order[dc];
            if (c < 0 || c >= eff_cols) continue;
            if (!st.col_visible[c]) continue;
            ImGui::TableSetupColumn(eff_headers[c], ImGuiTableColumnFlags_None, 0.0f, (ImGuiID)c);
        }
        ImGui::TableSetupScrollFreeze(0, 1);

        // Custom header row
        ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
        for (int dc = 0; dc < (int)st.col_order.size(); ++dc) {
            int c = st.col_order[dc];
            if (c < 0 || c >= eff_cols) continue;
            if (!st.col_visible[c]) continue;
            ImGui::TableSetColumnIndex(dc);
            ImGui::PushID(c);

            int  sort_pos  = -1;
            bool sort_desc = false;
            for (size_t si = 0; si < act.sorts.size(); ++si) {
                if (act.sorts[si].col == eff_headers[c]) {
                    sort_pos = (int)si; sort_desc = act.sorts[si].desc; break;
                }
            }
            char arrow[16] = "";
            if (sort_pos == 0)     std::snprintf(arrow, sizeof(arrow), "  %s", sort_desc ? "v" : "^");
            else if (sort_pos > 0) std::snprintf(arrow, sizeof(arrow), "  %s%d", sort_desc ? "v" : "^", sort_pos + 1);
            char label[200];
            std::snprintf(label, sizeof(label), "%s %s%s",
                          column_type_icon_g(eff_types[c]), eff_headers[c], arrow);

            ImGui::PushStyleColor(ImGuiCol_Header,        IM_COL32(45, 50, 65, 200));
            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, IM_COL32(65, 75, 95, 220));
            ImGui::PushStyleColor(ImGuiCol_HeaderActive,  IM_COL32(80, 95, 130, 240));
            bool clicked = ImGui::Selectable(label, false, ImGuiSelectableFlags_DontClosePopups);
            ImGui::PopStyleColor(3);

            if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
                ImGui::SetDragDropPayload("##colreorder", &c, sizeof(int));
                ImGui::Text("Move %s", eff_headers[c]);
                ImGui::EndDragDropSource();
            }
            if (ImGui::BeginDragDropTarget()) {
                if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("##colreorder")) {
                    int src = *(const int*)p->Data;
                    reorder_column_g(st, src, c);
                }
                ImGui::EndDragDropTarget();
            }
            if (clicked) {
                bool shift = ImGui::GetIO().KeyShift;
                apply_header_sort_click(act, eff_headers[c], shift);
            }
            if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
                U.header_popup_col = c;
                ImGui::OpenPopup("##hdr_menu");
            }
            if (ImGui::BeginPopup("##hdr_menu") && U.header_popup_col == c) {
                // Restore visible hover inside popup (outer table pushes to transparent).
                ImGui::PushStyleColor(ImGuiCol_HeaderHovered, IM_COL32( 70,  95, 140, 230));
                ImGui::PushStyleColor(ImGuiCol_HeaderActive,  IM_COL32( 55,  80, 120, 240));
                ImGui::PushStyleColor(ImGuiCol_Header,        IM_COL32(  0,   0,   0,   0));
                draw_header_menu(st, act, c, eff_headers, eff_cols, eff_types_vec, orig_cols, true);
                ImGui::PopStyleColor(3);
                ImGui::EndPopup();
            }

            // Stats overlay
            if (st.stats_mode && c < (int)st.stats_cache.size()) {
                const ColStats& s = st.stats_cache[c];
                ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(170, 190, 220, 220));
                ImGui::Text("missing: %d", s.empty_count);
                ImGui::Text("uniq: %d%s", s.unique_count, s.unique_capped ? "+" : "");
                if (s.numeric) {
                    ImGui::Text("mean: %.2f", s.mean);
                    ImGui::Text("p25: %.2f",  s.p25);
                    ImGui::Text("p50: %.2f",  s.p50);
                    ImGui::Text("p75: %.2f",  s.p75);
                    if (!s.hist.empty()) {
                        char overlay[64];
                        std::snprintf(overlay, sizeof(overlay), "[%.2f..%.2f]", s.min, s.max);
                        ImGui::PushStyleColor(ImGuiCol_PlotHistogram, IM_COL32(70, 140, 220, 230));
                        ImGui::PlotHistogram("##hist", s.hist.data(), (int)s.hist.size(),
                                             0, overlay, 0.0f, FLT_MAX, ImVec2(-1, 36));
                        ImGui::PopStyleColor();
                    }
                } else if (!s.top_categories.empty()) {
                    int mx = 0;
                    for (const auto& kv : s.top_categories) if (kv.second > mx) mx = kv.second;
                    ImGui::PushStyleColor(ImGuiCol_PlotHistogram, IM_COL32(70, 140, 220, 220));
                    for (const auto& kv : s.top_categories) {
                        float frac = mx > 0 ? (float)kv.second / (float)mx : 0.f;
                        char ovl[96];
                        std::snprintf(ovl, sizeof(ovl), "%s (%d)", kv.first.c_str(), kv.second);
                        ImGui::ProgressBar(frac, ImVec2(-1, 12), ovl);
                    }
                    ImGui::PopStyleColor();
                }
                ImGui::PopStyleColor();
            }
            ImGui::PopID();
        }

        int sel_rmin = std::min(st.sel_anchor_row, st.sel_end_row);
        int sel_rmax = std::max(st.sel_anchor_row, st.sel_end_row);
        int sel_cmin = std::min(st.sel_anchor_col, st.sel_end_col);
        int sel_cmax = std::max(st.sel_anchor_col, st.sel_end_col);

        ImGuiListClipper clipper;
        clipper.Begin((int)visible_rows.size());
        while (clipper.Step()) {
            for (int ri = clipper.DisplayStart; ri < clipper.DisplayEnd; ++ri) {
                int r = visible_rows[ri];
                ImGui::TableNextRow();
                int draw_idx = 0;
                for (int oc = 0; oc < (int)st.col_order.size(); ++oc) {
                    int c = st.col_order[oc];
                    if (c < 0 || c >= eff_cols) continue;
                    if (!st.col_visible[c]) continue;
                    ImGui::TableSetColumnIndex(draw_idx++);

                    // Capture cell rect before render (for per-cell hover overlay).
                    ImVec2 cell_min = ImGui::GetCursorScreenPos();
                    float  cell_w   = ImGui::GetContentRegionAvail().x;
                    float  cell_h   = ImGui::GetTextLineHeight();
                    ImVec2 cell_max(cell_min.x + cell_w, cell_min.y + cell_h);

                    int src = src_for_eff[c];
                    // NOTE: Lua-derived cell evaluation is intentionally NOT done here.
                    // The caller (data_table.cpp entrypoint) evaluates derived cells and
                    // passes a pre-materialized `cells` array to render_grid_stage0, OR
                    // this function operates on the raw cells with src mapping.
                    // For stage 0, derived cols are evaluated inline in the entrypoint
                    // and NOT passed through this function path — the entrypoint calls
                    // render_grid_stage0 only for the raw-cell + src_for_eff path, while
                    // derived evaluation stays in the entrypoint context.
                    // This avoids passing Lua state through the grid API boundary.
                    const char* cell = cells[r * orig_cols + src];

                    float dot_advance = 0.f;
                    apply_color_rules_for_cell(st, c, cell,
                                               cell_min, cell_w, cell_h,
                                               dot_advance);
                    if (dot_advance > 0.f) {
                        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + dot_advance);
                    }
                    bool in_sel = (st.sel_active &&
                                   ri >= sel_rmin && ri <= sel_rmax &&
                                   oc >= sel_cmin && oc <= sel_cmax);
                    ImGui::PushID(r * eff_cols + c);
                    {
                        bool custom_rendered = false;
                        const ColumnSpec* cell_cs = nullptr;
                        if (!main_t.column_specs.empty() &&
                            c < (int)main_t.column_specs.size()) {
                            cell_cs = &main_t.column_specs[(size_t)c];
                            if (cell_cs->renderer != CellRenderer::Text) {
                                draw_cell_custom(*cell_cs, cell, r, c, events_out);
                                custom_rendered = true;
                            }
                        }
                        if (!custom_rendered) {
                            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0));
                            ImGui::Selectable(cell ? cell : "", in_sel,
                                              ImGuiSelectableFlags_AllowDoubleClick,
                                              ImVec2(0, ImGui::GetTextLineHeight()));
                            ImGui::PopStyleColor();
                            if (cell_cs && cell_cs->tooltip_on_hover &&
                                ImGui::IsItemHovered()) {
                                const char* tip = (cell_cs->tooltip == "auto")
                                                  ? (cell ? cell : "")
                                                  : cell_cs->tooltip.c_str();
                                if (tip && tip[0]) ImGui::SetTooltip("%s", tip);
                            }
                        }
                    }
                    // Per-cell bg via TableSetBgColor (edge-to-edge, no Selectable padding).
                    {
                        ImVec2 mp  = ImGui::GetMousePos();
                        ImVec2 pad = ImGui::GetStyle().CellPadding;
                        bool is_hovered =
                            mp.x >= cell_min.x - pad.x && mp.x < cell_min.x + cell_w + pad.x &&
                            mp.y >= cell_min.y - pad.y && mp.y < cell_min.y + cell_h + pad.y &&
                            ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
                        if (in_sel) {
                            ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,
                                                    is_hovered
                                                    ? IM_COL32(102, 140, 217, 80)
                                                    : IM_COL32(102, 140, 217, 60));
                        } else if (is_hovered) {
                            ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,
                                                    IM_COL32(255, 255, 255, 22));
                        }
                    }
                    bool hovered = ImGui::IsItemHovered(
                        ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
                    if (hovered) {
                        if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
                            st.sel_anchor_row = ri; st.sel_anchor_col = oc;
                            st.sel_end_row    = ri; st.sel_end_col    = oc;
                            st.sel_active     = true;
                            st.sel_dragging   = true;
                        } else if (st.sel_dragging) {
                            st.sel_end_row = ri; st.sel_end_col = oc;
                        }
                        if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)
                            && events_out) {
                            TableEvent ev;
                            ev.kind  = TableEventKind::RowDoubleClick;
                            ev.row   = r;
                            ev.col   = c;
                            ev.value = cell ? cell : "";
                            if (!main_t.column_specs.empty() &&
                                c < (int)main_t.column_specs.size())
                                ev.column_id = main_t.column_specs[(size_t)c].id;
                            events_out->push_back(std::move(ev));
                        }
                        if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
                            U.pending_col     = c;
                            U.pending_value   = cell ? cell : "";
                            U.open_cell_popup = true;
                            if (events_out) {
                                TableEvent ev;
                                ev.kind  = TableEventKind::RowRightClick;
                                ev.row   = r;
                                ev.col   = c;
                                ev.value = cell ? cell : "";
                                if (!main_t.column_specs.empty() &&
                                    c < (int)main_t.column_specs.size())
                                    ev.column_id = main_t.column_specs[(size_t)c].id;
                                events_out->push_back(std::move(ev));
                            }
                        }
                    }
                    ImGui::PopID();
                }
            }
        }
        if (st.sel_dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
            st.sel_dragging = false;
        }
        ImGui::EndTable();
    }
    ImGui::PopStyleColor(3); // HeaderHovered/HeaderActive/Header

    // Ctrl+C -> TSV copy
    if (st.sel_active && ImGui::GetIO().KeyCtrl &&
        ImGui::IsKeyPressed(ImGuiKey_C, false))
    {
        int rmin = std::min(st.sel_anchor_row, st.sel_end_row);
        int rmax = std::max(st.sel_anchor_row, st.sel_end_row);
        int cmin = std::min(st.sel_anchor_col, st.sel_end_col);
        int cmax = std::max(st.sel_anchor_col, st.sel_end_col);
        std::string out;
        bool first = true;
        for (int oc = cmin; oc <= cmax; ++oc) {
            if (oc < 0 || oc >= (int)st.col_order.size()) continue;
            int c = st.col_order[oc];
            if (c < 0 || c >= eff_cols) continue;
            if (!st.col_visible[c]) continue;
            if (!first) out += '\t';
            out += eff_headers[c];
            first = false;
        }
        out += '\n';
        for (int ri = rmin; ri <= rmax; ++ri) {
            if (ri < 0 || ri >= (int)visible_rows.size()) continue;
            int r = visible_rows[ri];
            first = true;
            for (int oc = cmin; oc <= cmax; ++oc) {
                if (oc < 0 || oc >= (int)st.col_order.size()) continue;
                int c = st.col_order[oc];
                if (c < 0 || c >= eff_cols) continue;
                if (!st.col_visible[c]) continue;
                int src = src_for_eff[c];
                const char* v = cells[r * orig_cols + src];
                std::string sv = v ? v : "";
                for (char& ch : sv) if (ch == '\t' || ch == '\n') ch = ' ';
                if (!first) out += '\t';
                out += sv;
                first = false;
            }
            out += '\n';
        }
        ImGui::SetClipboardText(out.c_str());
    }
}

// ---------------------------------------------------------------------------
// render_grid_stage_n
// ---------------------------------------------------------------------------
void render_grid_stage_n(const char* id,
                         State& st,
                         const char* const* cur_cells,
                         int cur_rows, int cur_cols_n,
                         const std::vector<std::string>& cur_headers,
                         const std::vector<ColumnType>& cur_types,
                         const std::vector<std::string>& input_headers_active,
                         int n_breakouts,
                         const TableInput& main_t,
                         std::vector<TableEvent>* events_out)
{
    UiState& U = ui();
    int active = st.active_stage;
    Stage& act = st.stages[active];

    ImGuiTableFlags flags =
        ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
        ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY;
    // Issue 0081-O.7: tone down row hover.
    ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.05f));
    ImGui::PushStyleColor(ImGuiCol_HeaderActive,  ImVec4(1.0f, 1.0f, 1.0f, 0.08f));
    ImGui::PushStyleColor(ImGuiCol_Header,        ImVec4(0.40f, 0.55f, 0.85f, 0.20f));

    if (cur_cols_n > 0 && ImGui::BeginTable(id, cur_cols_n, flags, ImVec2(0, 0))) {
        for (int c = 0; c < cur_cols_n; ++c) {
            ImGui::TableSetupColumn(cur_headers[c].c_str(),
                                    ImGuiTableColumnFlags_None, 0.0f, (ImGuiID)c);
        }
        ImGui::TableSetupScrollFreeze(0, 1);

        // Custom header row: icon + name + sort indicator + stats overlay.
        ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
        for (int c = 0; c < cur_cols_n; ++c) {
            ImGui::TableSetColumnIndex(c);
            int  sort_pos  = -1;
            bool sort_desc = false;
            for (size_t si = 0; si < act.sorts.size(); ++si) {
                if (act.sorts[si].col == cur_headers[c]) {
                    sort_pos = (int)si; sort_desc = act.sorts[si].desc; break;
                }
            }
            char arrow[16] = "";
            if (sort_pos == 0)     std::snprintf(arrow, sizeof(arrow), "  %s", sort_desc ? "v" : "^");
            else if (sort_pos > 0) std::snprintf(arrow, sizeof(arrow), "  %s%d", sort_desc ? "v" : "^", sort_pos + 1);

            ImGui::PushStyleColor(ImGuiCol_Header,        IM_COL32(45, 50, 65, 200));
            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, IM_COL32(65, 75, 95, 220));
            ImGui::PushStyleColor(ImGuiCol_HeaderActive,  IM_COL32(80, 95, 130, 240));
            char lbl[200];
            std::snprintf(lbl, sizeof(lbl), "%s %s%s",
                          column_type_icon_g(cur_types[c]),
                          cur_headers[c].c_str(), arrow);
            bool h_clicked = ImGui::Selectable(lbl, false, ImGuiSelectableFlags_DontClosePopups);
            ImGui::PopStyleColor(3);
            if (h_clicked) {
                bool shift = ImGui::GetIO().KeyShift;
                apply_header_sort_click(act, cur_headers[c], shift);
            }

            if (st.stats_mode && c < (int)st.stats_cache.size()) {
                const ColStats& s = st.stats_cache[c];
                ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(170, 190, 220, 220));
                ImGui::Text("missing: %d", s.empty_count);
                ImGui::Text("uniq: %d%s", s.unique_count, s.unique_capped ? "+" : "");
                if (s.numeric) {
                    ImGui::Text("mean: %.2f", s.mean);
                    ImGui::Text("p25: %.2f",  s.p25);
                    ImGui::Text("p50: %.2f",  s.p50);
                    ImGui::Text("p75: %.2f",  s.p75);
                    if (!s.hist.empty()) {
                        char overlay[64];
                        std::snprintf(overlay, sizeof(overlay), "[%.2f..%.2f]", s.min, s.max);
                        ImGui::PushStyleColor(ImGuiCol_PlotHistogram, IM_COL32(70, 140, 220, 230));
                        ImGui::PlotHistogram("##hist", s.hist.data(), (int)s.hist.size(),
                                             0, overlay, 0.0f, FLT_MAX, ImVec2(-1, 36));
                        ImGui::PopStyleColor();
                    }
                } else if (!s.top_categories.empty()) {
                    int mx = 0;
                    for (const auto& kv : s.top_categories) if (kv.second > mx) mx = kv.second;
                    ImGui::PushStyleColor(ImGuiCol_PlotHistogram, IM_COL32(70, 140, 220, 220));
                    for (const auto& kv : s.top_categories) {
                        float frac = mx > 0 ? (float)kv.second / (float)mx : 0.f;
                        char ovl[96];
                        std::snprintf(ovl, sizeof(ovl), "%s (%d)", kv.first.c_str(), kv.second);
                        ImGui::ProgressBar(frac, ImVec2(-1, 12), ovl);
                    }
                    ImGui::PopStyleColor();
                }
                ImGui::PopStyleColor();
            }
        }

        for (int r = 0; r < cur_rows; ++r) {
            ImGui::TableNextRow();
            for (int c = 0; c < cur_cols_n; ++c) {
                ImGui::TableSetColumnIndex(c);
                const char* cell = cur_cells[r * cur_cols_n + c];
                ImGui::PushID(r * cur_cols_n + c);
                {
                    bool custom_rendered = false;
                    const ColumnSpec* cell_cs2 = nullptr;
                    if (!main_t.column_specs.empty() &&
                        c < (int)main_t.column_specs.size()) {
                        cell_cs2 = &main_t.column_specs[(size_t)c];
                        if (cell_cs2->renderer != CellRenderer::Text) {
                            draw_cell_custom(*cell_cs2, cell, r, c, events_out);
                            custom_rendered = true;
                        }
                    }
                    if (!custom_rendered) {
                        ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0));
                        ImGui::Selectable(cell ? cell : "", false, 0,
                                          ImVec2(0, ImGui::GetTextLineHeight()));
                        ImGui::PopStyleColor();
                        if (cell_cs2 && cell_cs2->tooltip_on_hover &&
                            ImGui::IsItemHovered()) {
                            const char* tip = (cell_cs2->tooltip == "auto")
                                              ? (cell ? cell : "")
                                              : cell_cs2->tooltip.c_str();
                            if (tip && tip[0]) ImGui::SetTooltip("%s", tip);
                        }
                    }
                }
                if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
                    U.pending_col   = c;
                    U.pending_value = cell ? cell : "";
                    st.inspect_row   = r;
                    ImGui::OpenPopup("##drill_popup");
                    if (events_out) {
                        TableEvent ev;
                        ev.kind  = TableEventKind::RowRightClick;
                        ev.row   = r;
                        ev.col   = c;
                        ev.value = cell ? cell : "";
                        if (!main_t.column_specs.empty() &&
                            c < (int)main_t.column_specs.size())
                            ev.column_id = main_t.column_specs[(size_t)c].id;
                        events_out->push_back(std::move(ev));
                    }
                }
                if (ImGui::BeginPopup("##drill_popup")) {
                    if (c < n_breakouts) {
                        char lbl[256];
                        std::snprintf(lbl, sizeof(lbl), "Drill into: %s = %s",
                                      cur_headers[c].c_str(), cell ? cell : "");
                        if (ImGui::MenuItem(lbl)) {
                            drill_into(st, active, cur_headers[c],
                                       cell ? std::string(cell) : "",
                                       input_headers_active);
                            ImGui::CloseCurrentPopup();
                        }
                        ImGui::Separator();
                    }
                    if (ImGui::MenuItem("Inspect row...")) {
                        st.inspect_row  = r;
                        st.inspect_open = true;
                        ImGui::CloseCurrentPopup();
                    }
                    ImGui::EndPopup();
                }
                ImGui::PopID();
            }
        }
        ImGui::EndTable();
    }
    ImGui::PopStyleColor(3); // HeaderHovered/HeaderActive/Header

    // Row inspector modal
    draw_row_inspector_modal(st, active, cur_cells, cur_rows, cur_cols_n,
                              cur_headers, cur_types, input_headers_active);
}

} // namespace data_table
