#pragma once
// data_table_internal — contrato compartido entre las 6 sub-funciones del modulo.
// NO publico para apps: las apps incluyen `data_table/data_table.h` y nada mas.
// Este header lo incluyen SOLO los .cpp del modulo (data_table.cpp + sus 6 sub-funciones).
//
// Issue 0107c — split de data_table.cpp (4777 LOC) en 6 sub-funciones del registry.
// Issue 0133 — columnar snapshot + string interning (Changes 1+2).
//
// Provee:
//   1. `UiState` agregador (composicion de sub-states declarados en los .h de
//      cada sub-funcion + estado compartido por >1 sub-funcion).
//   2. `ui()` accessor para singleton thread_local — mismo lifetime que el
//      original UiState del playground.
//   3. Helpers compartidos como `inline` puros: `ops_for_type`, `op_label`,
//      `effective_type`, `view_mode_label`, `join_strategy_label`, etc.
//   4. Forward refs de funciones internas que cruzan sub-funciones (ej. el
//      draw_header_menu de chips llama draw_color_rule_menu de color_rules).
//   5. `ColumnSnapshot` / `SnapshotCache` — snapshot columnar interno (issue 0133).
//
// Politica:
//   - Si un helper se usa SOLO dentro de UNA sub-funcion -> queda `static` en su .cpp.
//   - Si se usa en >1 sub-funcion -> aqui como `inline`.
//   - Si manipula estado -> miembro de UiState (o sub-state).
//
// API publica externa = data_table/data_table.h (intacta tras refactor).
// API interna del modulo = este header.
//
// ---------------------------------------------------------------------------
// DISEÑO: Snapshot columnar + String Interning (issue 0133)
// ---------------------------------------------------------------------------
//
// MOTIVACION
//   El layout de datos de entrada es row-major: `cells[row * cols + col]`
//   (punteros a C-strings en el `TableInput` del caller).  Acceder a una
//   columna entera (para filtrar, ordenar, colorear, calcular stats) requiere
//   saltar `cols` posiciones en memoria por fila — mal para cache a 10M+ filas.
//   El snapshot convierte a column-major una sola vez por frame y amortiza el
//   coste entre filter / sort / color_rules / stats.
//
// SNAPSHOT COLUMNAR — `ColumnSnapshot` / `SnapshotCache`
//   - `SnapshotCache` vive en `UiState` (singleton thread_local de este modulo).
//   - Contiene un vector de `ColumnSnapshot`, uno por columna efectiva de la
//     tabla DESPUES de joins pero ANTES de stages (= stage-0 input).
//   - `SnapshotCache::last_cells_ptr` guarda el puntero de `cells` del ultimo
//     rebuild.  Si el puntero cambia (o es la primera llamada), se reconstruye
//     el snapshot completo.  Esto sigue exactamente el patron de `stats_last_cells`
//     en `State` (data_table_types.h:399).
//   - String columns (ColumnType::String / Auto sin numero) almacenan indices
//     uint32_t al `StringPool` del `State` correspondiente.
//   - Int columns almacenan int64_t parseados una sola vez via `parse_number`.
//   - Float columns almacenan double parseados una sola vez via `parse_number`.
//   - Si `parse_number` falla en una celda que deberia ser Int o Float, la celda
//     se trata como 0 / 0.0.  Este comportamiento es consistente con `compare()`
//     y el sort actual que ya llaman `parse_number` per-compare.
//
// STRING INTERNING — `StringPool` en `State`
//   - `StringPool` vive en `State` (NOT global, NOT singleton) para que cada
//     instancia de tabla tenga su propio pool sin interferencia.
//   - `intern(sv)` inserta la cadena si no esta y devuelve su indice uint32_t.
//     Usa `unordered_map<string_view, uint32_t>` con la `string_view` apuntando
//     al `strings[i]` del vector (el vector se reserva antes del rebuild para
//     evitar reallocs que invaliden los string_views del mapa).
//   - En datasets tipicos (60-70% de strings repetidos) la reduccion de RAM
//     es de 60-70% en la columna interned vs copias planas.
//
// INVARIANTES
//   1. Pointer-identity: si `cells == last_cells_ptr`, el snapshot es valido
//      para este frame.  Cambio de puntero => rebuild completo.
//   2. row→snapshot_row: el indice de fila en el snapshot es IDENTICO al indice
//      de fila del `TableInput` original (no hay reordenacion en el snapshot).
//      `TableEvent.row` del caller sigue siendo indice en `TableInput`.
//   3. El snapshot es input de stage-0.  Los stages sucesivos (compute_stage)
//      operan sobre `StageOutput` materializado y NO consultan el snapshot
//      directamente — el snapshot solo alimenta la ruta stage-0 de render().
//   4. `stats_last_cells` y snapshot son independientes: `stats_last_cells`
//      ya existia antes de issue 0133 y permanece como sentinel propio del
//      cache de stats.  El snapshot tiene su propio `last_cells_ptr`.
//      Ambos pueden diferir temporalmente si `stats_cache` invalida por
//      filtro (hash de filtros) pero el snapshot sigue valido por puntero.
//   5. `StringPool` se limpia (clear()) en cada rebuild del snapshot para
//      mantener coherencia: los indices del snapshot siempre corresponden
//      al pool del mismo frame.
// ---------------------------------------------------------------------------

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

// Sub-states declarados en los .h de cada sub-funcion. Se incluyen aqui para
// que `UiState` los pueda componer por valor.
#include "viz/data_table_ai_panel.h"      // AskAiState
#include "viz/data_table_chips.h"         // TqlBarState
#include "viz/data_table_color_rules.h"   // ColorRuleEditorState

#include "imgui.h"

#include <string>
#include <unordered_map>
#include <vector>

namespace data_table {

// ---------------------------------------------------------------------------
// ColumnSnapshot — snapshot de una columna en memoria columnar.
// Creado una vez por frame al detectar cambio de puntero en `cells`.
// ---------------------------------------------------------------------------
struct ColumnSnapshot {
    ColumnType            type;       // tipo efectivo inferido (post auto_detect)
    std::vector<uint32_t> str_ids;   // para String/Auto: indices al StringPool
    std::vector<int64_t>  i64;       // para Int: valores parseados
    std::vector<double>   f64;       // para Float: valores parseados
};

// ---------------------------------------------------------------------------
// SnapshotCache — vive en UiState (thread_local singleton).
// Un snapshot cubre TODAS las columnas efectivas de la tabla activa
// (post-join, pre-stages).  Se invalida por pointer-identity de `cells`.
// ---------------------------------------------------------------------------
struct SnapshotCache {
    const char* const*          last_cells_ptr  = nullptr; // sentinel de invalidacion por ptr
    uint32_t                    pool_size_built = 0;       // strings.size() cuando se construyo
    std::vector<ColumnSnapshot> cols;                      // un entry por columna efectiva
};

// ---------------------------------------------------------------------------
// UiState — singleton thread_local del modulo. Agrupa:
//   (a) Sub-states declarados en headers de sub-funciones (AskAiState etc.).
//   (b) Estado compartido por >1 sub-funcion que no encaja en ningun sub-state
//       individual.
// El entrypoint thin (data_table.cpp) mantiene el UiState via `ui()`.
// Cada sub-funcion recibe `UiState&` o solo el sub-state que necesita.
// ---------------------------------------------------------------------------

struct UiState {
    // ----- Sub-states de sub-funciones (composicion) -----
    AskAiState           ask_ai;        // data_table_ai_panel
    TqlBarState          tql_bar;       // data_table_chips
    ColorRuleEditorState color_rules;   // data_table_color_rules

    // ----- Cell popup (grid + chips, drill popup en cell ctx menu) -----
    int         pending_col     = -1;
    std::string pending_value;
    bool        open_cell_popup = false;

    // ----- Header popup (chips draw_header_menu + color_rules + grid header) -----
    int                                   header_popup_col = -1;
    std::unordered_map<int, std::string>  filter_inputs;
    // color_value_inputs + color_picker_vals viven en color_rules (ColorRuleEditorState).

    // ----- Add-filter popup state (chips draw_add_filter_popup) -----
    int         addf_col   = 0;
    std::string addf_val;
    bool        addf_range = false;
    std::string addf_lo;
    std::string addf_hi;

    // ----- Custom column modal (formula editor — vive en data_table.cpp entrypoint) -----
    bool        cf_open          = false;
    bool        cf_editing       = false;
    int         cf_edit_idx      = -1;
    int         cf_target_stage  = 0;
    std::string cf_formula;
    std::string cf_name;
    ColumnType  cf_type          = ColumnType::String;
    std::string cf_error;
    bool        cf_ac_open       = false;
    int         cf_ac_start      = -1;
    int         cf_ac_cursor     = -1;
    std::string cf_ac_filter;
    bool        cf_force_cursor  = false;
    int         cf_target_cursor = -1;

    // ----- Add-breakout / add-aggregation / edit chip popups (chips) -----
    int    brk_picker_col   = 0;
    int    agg_picker_fn    = (int)AggFn::Count;
    int    agg_picker_col   = 0;
    double agg_picker_arg   = 0.95;
    int    edit_chip_kind   = 0;   // 0=none, 1=filter, 2=breakout, 3=agg, 4=sort
    int    edit_chip_idx    = -1;
    int    edit_col_idx     = 0;
    int    edit_op          = (int)Op::Eq;
    int    edit_agg_fn      = (int)AggFn::Count;
    double edit_agg_arg     = 0.5;
    bool   edit_sort_desc   = false;
    std::string edit_value;
    int    sort_picker_col  = 0;
    bool   sort_picker_desc = false;

    // ----- Snapshot del active stage output/input (viz_panels config popup) -----
    std::vector<std::string> active_headers;
    std::vector<ColumnType>  active_types;
    std::vector<std::string> input_headers_active;
    std::vector<ColumnType>  input_types_active;

    // ----- Re-fit triggers para viz panels -----
    ViewMode prev_viz_display = ViewMode::Table;
    int      prev_viz_stage   = 0;
    std::size_t prev_viz_cfg_h = 0;

    // ----- Toggle Table <-> View (viz_panels) -----
    ViewMode last_non_table_main = ViewMode::Bar;

    // ----- Export path (chips export action) -----
    std::string last_export_path;

    // ----- Columnar snapshot (issue 0133, Change 1) -----
    // Invalida cuando cells pointer cambia entre frames.
    // Usado en render() stage-0 path para filter/sort/color_rules/stats.
    SnapshotCache snapshot;
};

// Singleton accessor. Definido en data_table.cpp (entrypoint).
UiState& ui();

// ---------------------------------------------------------------------------
// ViewMode table — fuente de verdad para label/needs_agg/all_modes.
// Compartida por: data_table.cpp (re-fit trigger) y data_table_viz_panels.cpp.
// ---------------------------------------------------------------------------

struct ViewModeInfo {
    ViewMode     m;
    const char*  token;
    const char*  label;
    int          min_cols;
    bool         needs_num;
    bool         needs_cat;
    bool         needs_agg;
};

static const ViewModeInfo kViewModes[] = {
    { ViewMode::Table,       "table",       "Table",                 1, false, false, false },
    { ViewMode::Bar,         "bar",         "Bar (horizontal)",      2, true,  true,  true  },
    { ViewMode::Column,      "column",      "Column (vertical)",     2, true,  true,  true  },
    { ViewMode::GroupedBar,  "grouped_bar", "Grouped bar",           2, true,  true,  true  },
    { ViewMode::StackedBar,  "stacked_bar", "Stacked bar",           2, true,  true,  true  },
    { ViewMode::Line,        "line",        "Line",                  1, true,  false, false },
    { ViewMode::Area,        "area",        "Area",                  1, true,  false, false },
    { ViewMode::Stairs,      "stairs",      "Stairs",                1, true,  false, false },
    { ViewMode::Scatter,     "scatter",     "Scatter",               2, true,  false, false },
    { ViewMode::Bubble,      "bubble",      "Bubble",                3, true,  false, false },
    { ViewMode::Histogram,   "histogram",   "Histogram",             1, true,  false, false },
    { ViewMode::Histogram2D, "hist2d",      "Histogram 2D",          2, true,  false, false },
    { ViewMode::Heatmap,     "heatmap",     "Heatmap",               1, true,  false, false },
    { ViewMode::BoxPlot,     "boxplot",     "Box plot",              2, true,  true,  false },
    { ViewMode::Stem,        "stem",        "Stem",                  1, true,  false, false },
    { ViewMode::ErrorBars,   "errorbars",   "Error bars",            2, true,  false, false },
    { ViewMode::Pie,         "pie",         "Pie",                   2, true,  true,  true  },
    { ViewMode::Donut,       "donut",       "Donut",                 2, true,  true,  true  },
    { ViewMode::Funnel,      "funnel",      "Funnel",                2, true,  true,  true  },
    { ViewMode::Waterfall,   "waterfall",   "Waterfall",             1, true,  false, true  },
    { ViewMode::KPI,         "kpi",         "KPI (single)",          1, true,  false, true  },
    { ViewMode::KPIGrid,     "kpi_grid",    "KPI grid",              1, true,  false, true  },
    { ViewMode::Candlestick, "candlestick", "Candlestick (OHLC)",    4, true,  false, false },
    { ViewMode::Radar,       "radar",       "Radar",                 2, true,  true,  false },
};
static const int kViewModesN = (int)(sizeof(kViewModes) / sizeof(kViewModes[0]));

inline const char* view_mode_label(ViewMode m) {
    for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].label;
    return "Table";
}

inline bool view_mode_needs_aggregation(ViewMode m) {
    for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].needs_agg;
    return false;
}

inline const ViewMode* all_view_modes(int* n_out) {
    static ViewMode arr[64];
    static bool init = false;
    if (!init) {
        for (int i = 0; i < kViewModesN; ++i) arr[i] = kViewModes[i].m;
        init = true;
    }
    if (n_out) *n_out = kViewModesN;
    return arr;
}

// filters_hash: FNV-1a hash de filtros activos para detectar cambios en maybe_recompute_stats.
inline size_t filters_hash(const std::vector<Filter>& f) {
    size_t h = 0xcbf29ce484222325ULL;
    for (const auto& x : f) {
        h ^= (size_t)x.col;       h *= 0x100000001b3ULL;
        h ^= (size_t)x.op;        h *= 0x100000001b3ULL;
        for (char ch : x.value) { h ^= (size_t)(unsigned char)ch; h *= 0x100000001b3ULL; }
    }
    return h;
}

// ColInfo: nombre + tipo de columna. Usado por draw_viz_config_popup para
// construir las listas de seleccion X/Y/Cat.
struct ColInfo { std::string name; ColumnType type; };

// collect_active_col_info: devuelve snapshot de columnas activas del active_stage
// (pobladas por render() en UiState::active_headers / active_types).
// Compartida por: data_table.cpp (forward decl) y data_table_viz_panels.cpp.
inline std::vector<ColInfo> collect_active_col_info(const State& /*st*/) {
    auto& U = ui();
    std::vector<ColInfo> r;
    int n = (int)std::min(U.active_headers.size(), U.active_types.size());
    r.reserve(n);
    for (int i = 0; i < n; ++i) r.push_back({U.active_headers[i], U.active_types[i]});
    return r;
}

// auto_promote_aggregated: si user en stage 0 elige una viz que necesita
// agrupacion, crea stage 1 con breakout=primera cat + agg=sum(primera num) o count.
// Compartida por: data_table.cpp y data_table_viz_panels.cpp.
inline void auto_promote_aggregated(State& st) {
    auto& U = ui();
    if (st.active_stage != 0)        return;
    if (st.stages.size() != 1)       return;

    std::string cat_name;
    std::string num_name;
    for (size_t i = 0; i < U.active_headers.size() && i < U.active_types.size(); ++i) {
        ColumnType t = U.active_types[i];
        if (cat_name.empty() &&
            (t == ColumnType::String || t == ColumnType::Date ||
             t == ColumnType::Bool   || t == ColumnType::Json)) {
            cat_name = U.active_headers[i];
        }
        if (num_name.empty() &&
            (t == ColumnType::Int || t == ColumnType::Float)) {
            num_name = U.active_headers[i];
        }
    }

    Stage s1;
    if (!cat_name.empty()) s1.breakouts.push_back(cat_name);
    Aggregation a;
    if (!num_name.empty()) {
        a.fn  = AggFn::Sum;
        a.col = num_name;
    } else {
        a.fn  = AggFn::Count;
    }
    s1.aggregations.push_back(a);
    st.stages.push_back(std::move(s1));
    st.active_stage = (int)st.stages.size() - 1;
}

// ---------------------------------------------------------------------------
// Helpers compartidos (inline, puros). Usados por >1 sub-funcion.
// ---------------------------------------------------------------------------

// effective_type: si declared==Auto, llama auto_detect_type sobre el rango.
// Usado por: chips (filter popups), grid (cell render), viz_panels (stats).
inline ColumnType effective_type(ColumnType declared,
                                 const char* const* cells, int rows, int cols, int col) {
    if (declared != ColumnType::Auto) return declared;
    return auto_detect_type(cells, rows, cols, col);
}

// ops_for_type: lista de operadores validos para un ColumnType.
// Usado por: chips (filter popups, edit popups), grid (cell ctx menu).
inline std::vector<Op> ops_for_type(ColumnType t) {
    switch (t) {
        case ColumnType::Int:
        case ColumnType::Float:
        case ColumnType::Date:
            return {Op::Eq, Op::Neq, Op::Gt, Op::Gte, Op::Lt, Op::Lte};
        case ColumnType::Bool:
            return {Op::Eq, Op::Neq};
        case ColumnType::Json:
            return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains};
        case ColumnType::String:
            return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains, Op::StartsWith, Op::EndsWith};
        case ColumnType::Auto:
        default:
            return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains};
    }
}

// op_label: nombre humano del operador para chips + cell ctx menu.
// Usado por: chips, grid.
// NOTE: `static inline` para evitar clash de simbolos con la definicion
// canonica de `op_label` en tql_helpers.cpp (issue 0107c follow-up). MinGW
// linker es estricto con inline-no-static vs out-of-line definition.
static inline const char* op_label(Op op) {
    switch (op) {
        case Op::Eq:           return "=";
        case Op::Neq:          return "!=";
        case Op::Gt:           return ">";
        case Op::Gte:          return ">=";
        case Op::Lt:           return "<";
        case Op::Lte:          return "<=";
        case Op::Contains:     return "contains";
        case Op::NotContains:  return "!contains";
        case Op::StartsWith:   return "starts";
        case Op::EndsWith:     return "ends";
    }
    return "?";
}

// join_strategy_label: nombre humano de la estrategia de join.
// Usado por: chips (joins chips), data_table.cpp (render setup).
inline const char* join_strategy_label(JoinStrategy s) {
    switch (s) {
        case JoinStrategy::Left:  return "left-join";
        case JoinStrategy::Inner: return "inner-join";
        case JoinStrategy::Right: return "right-join";
        case JoinStrategy::Full:  return "full-join";
    }
    return "left-join";
}

// resolve_main_idx: encuentra el indice de tables[] que coincide con main_source.
// Usado por: data_table.cpp render entrypoint.
inline int resolve_main_idx(const std::vector<TableInput>& tables, const std::string& main_source) {
    if (tables.empty()) return -1;
    if (main_source.empty()) return 0;
    for (std::size_t i = 0; i < tables.size(); ++i) {
        if (tables[i].name == main_source) return (int)i;
    }
    return 0;
}

// ---------------------------------------------------------------------------
// Forward refs de funciones internas que cruzan sub-funciones.
// Cada cual definida en SU .cpp; aqui solo declarada para que otros .cpp del
// modulo la puedan llamar sin doble include.
// ---------------------------------------------------------------------------

// draw_color_rule_menu: llamado desde draw_header_menu (submenu "Conditional color").
// Definido en data_table_color_rules.cpp.
// Retorna true si el usuario hizo click en "Apply".
bool draw_color_rule_menu(State& st, int col, ColumnType col_type,
                          ColorRuleEditorState& editor_st);

} // namespace data_table
