// tql_emit.cpp — Pure TQL serialization. See tql_emit.h.
// Promoted from primitives_gallery/playground/tables/tql.cpp (emit()).

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

#include <cstdio>
#include <unordered_map>

namespace tql {

using namespace data_table;

std::string emit(const State& state,
                 const std::vector<std::string>& headers,
                 const std::vector<ColumnType>& types)
{
    int orig_cols = (int)headers.size();
    const Stage& raw = state.stages.empty() ? Stage{} : state.stages[0];
    int eff_cols = orig_cols + (int)raw.derived.size();

    // Build effective headers + types (same indexing as col_visible/order).
    std::vector<std::string> eff_headers(eff_cols);
    std::vector<ColumnType>  eff_types(eff_cols);
    for (int c = 0; c < orig_cols; ++c) {
        eff_headers[c] = headers[c];
        eff_types[c]   = (c < (int)types.size()) ? types[c] : ColumnType::Auto;
    }
    for (int k = 0; k < (int)raw.derived.size(); ++k) {
        eff_headers[orig_cols + k] = raw.derived[k].name;
        eff_types[orig_cols + k]   = raw.derived[k].type;
    }

    // Build order positions: col_idx -> visual order (1-based).
    std::unordered_map<int, int> order_pos;
    for (size_t i = 0; i < state.col_order.size(); ++i)
        order_pos[(int)state.col_order[i]] = (int)i + 1;

    // ---- Lambda helpers ----
    auto emit_filter_block = [&](const std::vector<Filter>& filters,
                                 const std::vector<std::string>& stage_headers,
                                 const char* indent) -> std::string {
        if (filters.empty()) return {};
        std::string s;
        s += indent; s += "filter = {\n";
        for (const auto& f : filters) {
            std::string col_name = (f.col >= 0 && f.col < (int)stage_headers.size())
                                   ? stage_headers[f.col] : "";
            s += indent; s += "  {";
            s += lua_string_literal(op_label(f.op));
            s += ", ";
            s += lua_string_literal(col_name);
            s += ", ";
            s += lua_string_literal(f.value);
            s += "},\n";
        }
        s += indent; s += "},\n";
        return s;
    };

    auto emit_sort_block = [&](const std::vector<SortClause>& sorts,
                               const char* indent) -> std::string {
        if (sorts.empty()) return {};
        std::string s;
        s += indent; s += "sort = {\n";
        for (const auto& sc : sorts) {
            s += indent; s += "  {";
            s += lua_string_literal(sc.desc ? "desc" : "asc");
            s += ", ";
            s += lua_string_literal(sc.col);
            s += "},\n";
        }
        s += indent; s += "},\n";
        return s;
    };

    auto emit_view = [&](const VizPanel& p) -> std::string {
        std::string s = "    {";
        s += "display = " + lua_string_literal(view_mode_token(p.display));
        if (!p.config.x_col.empty())
            s += ", x_col = "   + lua_string_literal(p.config.x_col);
        if (!p.config.cat_col.empty())
            s += ", cat_col = " + lua_string_literal(p.config.cat_col);
        if (!p.config.size_col.empty())
            s += ", size_col = "+ lua_string_literal(p.config.size_col);
        if (!p.config.y_cols.empty()) {
            s += ", y_cols = {";
            for (size_t i = 0; i < p.config.y_cols.size(); ++i) {
                if (i) s += ", ";
                s += lua_string_literal(p.config.y_cols[i]);
            }
            s += "}";
        }
        if (p.config.primary_color != 0)
            s += ", color = " + lua_string_literal(color_to_hex(p.config.primary_color));
        if (p.config.hist_bins > 0)
            s += ", hist_bins = " + std::to_string(p.config.hist_bins);
        if (p.config.pie_radius > 0.0f)
            s += ", pie_radius = " + std::to_string(p.config.pie_radius);
        if (!p.config.show_legend)  s += ", show_legend = false";
        if (p.config.show_markers)  s += ", show_markers = true";
        if (p.config.locked)        s += ", locked = true";
        s += "},\n";
        return s;
    };

    // ---- Build output ----
    std::string out;
    out += "-- TQL v1 (Table Query Language). Round-trip de State <-> Lua.\n";
    out += "-- Schema:\n";
    out += "--   version = 1                       -- bump si breaking change\n";
    out += "--   display = \"table\"                 -- table|bar|line|pie (futuro)\n";
    out += "--   stages = { stage0, stage1, ... }  -- pipeline; stage 0 = Raw\n";
    out += "--   columns = { {name,type,visible,order,color_rules}, ... }\n";
    out += "--\n";
    out += "-- Stage 0 (Raw):     filter + expressions + sort\n";
    out += "-- Stage N (Grouped): filter + breakout + aggregation + sort\n";
    out += "--\n";
    out += "-- filter: {{op, col, val}, ...}        op in =,!=,>,>=,<,<=,contains,!contains,starts,ends\n";
    out += "-- expressions: {[name] = \"lua_body\"}  ej: [\"total\"] = \"return [a] + [b]\"\n";
    out += "-- breakout: {\"col1\", \"col2\"}          group by\n";
    out += "-- aggregation: {{fn, col, arg?}, ...}  fn in count,sum,avg,min,max,distinct,stddev,median,p25,p75,p90,p99,percentile\n";
    out += "-- sort: {{dir, col}, ...}              dir in asc,desc\n";
    out += "return {\n";
    out += "  version = 1,\n";
    out += "  display = ";
    out += lua_string_literal(view_mode_token(state.display));
    out += ",\n";
    if (!state.main_source.empty()) {
        out += "  main_source = ";
        out += lua_string_literal(state.main_source);
        out += ",\n";
    }

    // joins
    if (!state.joins.empty()) {
        out += "  joins = {\n";
        for (const auto& jn : state.joins) {
            out += "    {alias = "    + lua_string_literal(jn.alias);
            out += ", source = "     + lua_string_literal(jn.source);
            out += ", strategy = "   + lua_string_literal(join_strategy_token(jn.strategy));
            out += ", on = {";
            for (size_t i = 0; i < jn.on.size(); ++i) {
                if (i) out += ", ";
                out += "{" + lua_string_literal(jn.on[i].first) + ", "
                           + lua_string_literal(jn.on[i].second) + "}";
            }
            out += "}";
            if (!jn.fields.empty()) {
                out += ", fields = {";
                for (size_t i = 0; i < jn.fields.size(); ++i) {
                    if (i) out += ", ";
                    out += lua_string_literal(jn.fields[i]);
                }
                out += "}";
            }
            out += "},\n";
        }
        out += "  },\n";
    }

    out += "  stages = {\n";

    // Cur_headers tracks the output headers of each stage for filter/sort col name resolution.
    std::vector<std::string> cur_headers = headers;

    for (int si = 0; si < (int)state.stages.size(); ++si) {
        const Stage& stg = state.stages[si];
        out += "    {\n";

        if (si == 0) {
            // Stage 0 (Raw): filter + expressions + sort.
            std::vector<std::string> s0_headers = headers;
            out += emit_filter_block(stg.filters, s0_headers, "      ");

            if (!stg.derived.empty()) {
                bool any_formula = false;
                for (const auto& d : stg.derived) if (!d.formula.empty()) { any_formula = true; break; }
                if (any_formula) {
                    out += "      expressions = {\n";
                    for (const auto& d : stg.derived) {
                        if (d.formula.empty()) continue;
                        out += "        [";
                        out += lua_string_literal(d.name);
                        out += "] = ";
                        out += lua_string_literal(d.formula);
                        out += ",\n";
                    }
                    out += "      },\n";
                }
            }

            out += emit_sort_block(stg.sorts, "      ");

            // Advance cur_headers: orig + derived.
            for (const auto& d : stg.derived) cur_headers.push_back(d.name);

        } else {
            // Stage 1+ (Grouped): filter + breakout + aggregation + sort.
            out += emit_filter_block(stg.filters, cur_headers, "      ");

            if (!stg.breakouts.empty()) {
                out += "      breakout = {";
                for (size_t i = 0; i < stg.breakouts.size(); ++i) {
                    if (i > 0) out += ", ";
                    out += lua_string_literal(stg.breakouts[i]);
                }
                out += "},\n";
            }

            if (!stg.aggregations.empty()) {
                out += "      aggregation = {\n";
                for (const auto& a : stg.aggregations) {
                    out += "        {";
                    out += lua_string_literal(agg_fn_token(a.fn));
                    if (a.fn != AggFn::Count) {
                        out += ", ";
                        out += lua_string_literal(a.col);
                    }
                    if (a.fn == AggFn::Percentile) {
                        char buf[32];
                        std::snprintf(buf, sizeof(buf), "%g", a.arg);
                        out += ", "; out += buf;
                    }
                    out += "},\n";
                }
                out += "      },\n";
            }

            out += emit_sort_block(stg.sorts, "      ");

            // Advance cur_headers: breakouts + agg aliases.
            std::vector<std::string> next;
            for (const auto& b : stg.breakouts)    next.push_back(b);
            for (const auto& a : stg.aggregations) next.push_back(aggregation_alias(a));
            cur_headers = std::move(next);
        }

        out += "    },\n";
    }
    out += "  },\n";

    // columns — per-col render config (effective cols of stage 0).
    out += "  columns = {\n";
    for (int c = 0; c < eff_cols; ++c) {
        out += "    {";
        out += "name = " + lua_string_literal(eff_headers[c]);
        out += ", type = " + lua_string_literal(column_type_name(eff_types[c]));
        bool vis = (c < (int)state.col_visible.size()) ? state.col_visible[c] : true;
        out += std::string(", visible = ") + (vis ? "true" : "false");
        int order = order_pos.count(c) ? order_pos.at(c) : c + 1;
        out += ", order = " + std::to_string(order);
        // color_rules for this col
        bool first = true;
        for (const auto& cr : state.color_rules) {
            if (cr.col != c) continue;
            if (first) { out += ", color_rules = {"; first = false; }
            else       { out += ", "; }
            out += "{equals = " + lua_string_literal(cr.equals);
            out += ", color = " + lua_string_literal(color_to_hex(cr.color)) + "}";
        }
        if (!first) out += "}";
        out += "},\n";
    }
    out += "  },\n";

    // views — main viz panel (index 0) + extra_panels.
    out += "  views = {\n";
    VizPanel main_p;
    main_p.display = state.display;
    main_p.config  = state.viz_config;
    out += emit_view(main_p);
    for (const auto& p : state.extra_panels) out += emit_view(p);
    out += "  },\n";

    out += "  visualization_settings = {},\n";

    // column_specs (Phase 2, v1.2.0): TQL-persisted declarative renderer specs.
    // Only emitted when state.aux_column_specs is non-empty.
    // Format: column_specs = { { id="col_id", renderer="badge|progress|...", ... }, ... }
    if (!state.aux_column_specs.empty() && !state.aux_column_specs[0].empty()) {
        const auto& specs = state.aux_column_specs[0];
        // Emit the block only if at least one spec has a non-default renderer OR tooltip.
        bool any_renderable = false;
        for (const auto& cs : specs) {
            if (cs.renderer != data_table::CellRenderer::Text || cs.tooltip_on_hover) {
                any_renderable = true; break;
            }
        }
        if (any_renderable) {
            out += "  column_specs = {\n";
            for (const auto& cs : specs) {
                if (cs.renderer == data_table::CellRenderer::Text && !cs.tooltip_on_hover)
                    continue;  // skip pure-text cols without extras
                out += "    { id = " + lua_string_literal(cs.id);
                // renderer
                const char* rname = "text";
                switch (cs.renderer) {
                    case data_table::CellRenderer::Badge:            rname = "badge"; break;
                    case data_table::CellRenderer::Progress:         rname = "progress"; break;
                    case data_table::CellRenderer::Duration:         rname = "duration"; break;
                    case data_table::CellRenderer::Icon:             rname = "icon"; break;
                    case data_table::CellRenderer::Button:           rname = "button"; break;
                    case data_table::CellRenderer::Dots:             rname = "dots"; break;
                    case data_table::CellRenderer::CategoricalChip:  rname = "categorical_chip"; break;
                    case data_table::CellRenderer::ColorScale:       rname = "color_scale"; break;
                    default: break;
                }
                out += ", renderer = " + lua_string_literal(rname);
                // Badge rules
                if (!cs.badges.empty()) {
                    out += ", badges = {\n";
                    for (const auto& br : cs.badges) {
                        out += "      { value = " + lua_string_literal(br.value);
                        out += ", color = "       + lua_string_literal(br.color_hex);
                        if (!br.label.empty())
                            out += ", label = "  + lua_string_literal(br.label);
                        out += " },\n";
                    }
                    out += "    }";
                }
                // Progress
                if (cs.renderer == data_table::CellRenderer::Progress) {
                    if (cs.progress_scale_100)
                        out += ", progress_scale_100 = true";
                    if (!cs.progress_color_hex.empty())
                        out += ", progress_color = " + lua_string_literal(cs.progress_color_hex);
                }
                // Duration
                if (cs.renderer == data_table::CellRenderer::Duration) {
                    char buf[64];
                    std::snprintf(buf, sizeof(buf), "%g", (double)cs.duration_warn_ms);
                    out += std::string(", warn_ms = ") + buf;
                    std::snprintf(buf, sizeof(buf), "%g", (double)cs.duration_error_ms);
                    out += std::string(", error_ms = ") + buf;
                }
                // Icon map
                if (!cs.icon_map.empty()) {
                    out += ", icon_map = {\n";
                    for (const auto& ie : cs.icon_map) {
                        out += "      { value = " + lua_string_literal(ie.value);
                        out += ", icon = "        + lua_string_literal(ie.icon_name);
                        if (!ie.color_hex.empty())
                            out += ", color = "  + lua_string_literal(ie.color_hex);
                        out += " },\n";
                    }
                    out += "    }";
                }
                // Button
                if (cs.renderer == data_table::CellRenderer::Button) {
                    if (!cs.button_action.empty())
                        out += ", button_action = " + lua_string_literal(cs.button_action);
                    if (!cs.button_label.empty())
                        out += ", button_label = "  + lua_string_literal(cs.button_label);
                    if (!cs.button_color_hex.empty())
                        out += ", button_color = "  + lua_string_literal(cs.button_color_hex);
                }
                // Dots (Phase 2.5, v1.3.0)
                if (cs.renderer == data_table::CellRenderer::Dots) {
                    if (cs.dots_separator != ',') {
                        char sep_str[2] = {cs.dots_separator, '\0'};
                        out += ", dots_separator = " + lua_string_literal(sep_str);
                    }
                    if (cs.dots_max > 0)
                        out += ", dots_max = " + std::to_string(cs.dots_max);
                    if (cs.dots_show_count)
                        out += ", dots_show_count = true";
                    if (cs.dots_glyph_size > 0.0f) {
                        char buf[32];
                        std::snprintf(buf, sizeof(buf), "%g", (double)cs.dots_glyph_size);
                        out += std::string(", dots_glyph_size = ") + buf;
                    }
                }
                // CategoricalChip (v1.4.0)
                if (cs.renderer == data_table::CellRenderer::CategoricalChip) {
                    if (!cs.chips.empty()) {
                        out += ", chips = {\n";
                        for (const auto& cr : cs.chips) {
                            out += "      { match = " + lua_string_literal(cr.match);
                            out += ", color = "       + lua_string_literal(cr.color);
                            out += " },\n";
                        }
                        out += "    }";
                    }
                }
                // ColorScale (v1.4.0)
                if (cs.renderer == data_table::CellRenderer::ColorScale) {
                    char buf[64];
                    std::snprintf(buf, sizeof(buf), "%g", cs.range_min);
                    out += std::string(", range_min = ") + buf;
                    std::snprintf(buf, sizeof(buf), "%g", cs.range_max);
                    out += std::string(", range_max = ") + buf;
                    if (cs.range_alpha != 0.25f) {
                        std::snprintf(buf, sizeof(buf), "%g", (double)cs.range_alpha);
                        out += std::string(", range_alpha = ") + buf;
                    }
                    if (!cs.range_stops.empty()) {
                        out += ", range_stops = {\n";
                        for (const auto& stop : cs.range_stops) {
                            std::snprintf(buf, sizeof(buf), "%g", (double)stop.position);
                            out += "      { position = ";
                            out += buf;
                            out += ", color = " + lua_string_literal(stop.color);
                            out += " },\n";
                        }
                        out += "    }";
                    }
                }
                // Tooltip
                if (cs.tooltip_on_hover) {
                    out += ", tooltip = " + lua_string_literal(cs.tooltip.empty() ? "auto" : cs.tooltip);
                    out += ", tooltip_on_hover = true";
                }
                out += " },\n";
            }
            out += "  },\n";
        }
    }

    out += "}\n";
    return out;
}

} // namespace tql
