// tql_helpers.cpp — Pure token helpers for TQL. No external deps.
// See tql_helpers.h for documentation.

#include "core/tql_helpers.h"

#include <algorithm>
#include <cstdio>
#include <cstring>

namespace data_table {

// ----------------------------------------------------------------------------
// Op
// ----------------------------------------------------------------------------
const char* op_label(Op o) {
    switch (o) {
        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 "=";
}

Op op_from_label(const char* s) {
    if (!s) return Op::Eq;
    if (std::strcmp(s, "=")          == 0) return Op::Eq;
    if (std::strcmp(s, "!=")         == 0) return Op::Neq;
    if (std::strcmp(s, ">")          == 0) return Op::Gt;
    if (std::strcmp(s, ">=")         == 0) return Op::Gte;
    if (std::strcmp(s, "<")          == 0) return Op::Lt;
    if (std::strcmp(s, "<=")         == 0) return Op::Lte;
    if (std::strcmp(s, "contains")   == 0) return Op::Contains;
    if (std::strcmp(s, "!contains")  == 0) return Op::NotContains;
    if (std::strcmp(s, "starts")     == 0) return Op::StartsWith;
    if (std::strcmp(s, "ends")       == 0) return Op::EndsWith;
    return Op::Eq;
}

// ----------------------------------------------------------------------------
// ColumnType
// ----------------------------------------------------------------------------
const char* column_type_name(ColumnType t) {
    switch (t) {
        case ColumnType::Auto:   return "auto";
        case ColumnType::String: return "string";
        case ColumnType::Int:    return "int";
        case ColumnType::Float:  return "float";
        case ColumnType::Bool:   return "bool";
        case ColumnType::Date:   return "date";
        case ColumnType::Json:   return "json";
    }
    return "auto";
}

ColumnType column_type_from_string(const std::string& s) {
    if (s == "string") return ColumnType::String;
    if (s == "int")    return ColumnType::Int;
    if (s == "float")  return ColumnType::Float;
    if (s == "bool")   return ColumnType::Bool;
    if (s == "date")   return ColumnType::Date;
    if (s == "json")   return ColumnType::Json;
    return ColumnType::Auto;
}

// ----------------------------------------------------------------------------
// AggFn
// ----------------------------------------------------------------------------
const char* agg_fn_token(AggFn f) {
    switch (f) {
        case AggFn::Count:      return "count";
        case AggFn::Sum:        return "sum";
        case AggFn::Avg:        return "avg";
        case AggFn::Min:        return "min";
        case AggFn::Max:        return "max";
        case AggFn::Distinct:   return "distinct";
        case AggFn::Stddev:     return "stddev";
        case AggFn::Median:     return "median";
        case AggFn::P25:        return "p25";
        case AggFn::P75:        return "p75";
        case AggFn::P90:        return "p90";
        case AggFn::P99:        return "p99";
        case AggFn::Percentile: return "percentile";
    }
    return "count";
}

AggFn agg_fn_from_string(const std::string& s) {
    if (s == "count")      return AggFn::Count;
    if (s == "sum")        return AggFn::Sum;
    if (s == "avg")        return AggFn::Avg;
    if (s == "min")        return AggFn::Min;
    if (s == "max")        return AggFn::Max;
    if (s == "distinct")   return AggFn::Distinct;
    if (s == "stddev")     return AggFn::Stddev;
    if (s == "median")     return AggFn::Median;
    if (s == "p25")        return AggFn::P25;
    if (s == "p75")        return AggFn::P75;
    if (s == "p90")        return AggFn::P90;
    if (s == "p99")        return AggFn::P99;
    if (s == "percentile") return AggFn::Percentile;
    return AggFn::Count;
}

// ----------------------------------------------------------------------------
// ViewMode
// ----------------------------------------------------------------------------
struct ViewModeEntry { ViewMode m; const char* token; };

static const ViewModeEntry kViewModes[] = {
    { ViewMode::Table,       "table"       },
    { ViewMode::Bar,         "bar"         },
    { ViewMode::Column,      "column"      },
    { ViewMode::GroupedBar,  "grouped_bar" },
    { ViewMode::StackedBar,  "stacked_bar" },
    { ViewMode::Line,        "line"        },
    { ViewMode::Area,        "area"        },
    { ViewMode::Stairs,      "stairs"      },
    { ViewMode::Scatter,     "scatter"     },
    { ViewMode::Bubble,      "bubble"      },
    { ViewMode::Histogram,   "histogram"   },
    { ViewMode::Histogram2D, "hist2d"      },
    { ViewMode::Heatmap,     "heatmap"     },
    { ViewMode::BoxPlot,     "boxplot"     },
    { ViewMode::Stem,        "stem"        },
    { ViewMode::ErrorBars,   "errorbars"   },
    { ViewMode::Pie,         "pie"         },
    { ViewMode::Donut,       "donut"       },
    { ViewMode::Funnel,      "funnel"      },
    { ViewMode::Waterfall,   "waterfall"   },
    { ViewMode::KPI,         "kpi"         },
    { ViewMode::KPIGrid,     "kpi_grid"    },
    { ViewMode::Candlestick, "candlestick" },
    { ViewMode::Radar,       "radar"       },
};
static constexpr int kViewModesN = (int)(sizeof(kViewModes) / sizeof(kViewModes[0]));

const char* view_mode_token(ViewMode m) {
    for (int i = 0; i < kViewModesN; ++i)
        if (kViewModes[i].m == m) return kViewModes[i].token;
    return "table";
}

ViewMode view_mode_from_token(const char* s) {
    if (!s) return ViewMode::Table;
    for (int i = 0; i < kViewModesN; ++i)
        if (std::strcmp(kViewModes[i].token, s) == 0) return kViewModes[i].m;
    return ViewMode::Table;
}

// ----------------------------------------------------------------------------
// JoinStrategy
// ----------------------------------------------------------------------------
const char* join_strategy_token(JoinStrategy s) {
    switch (s) {
        case JoinStrategy::Left:  return "left";
        case JoinStrategy::Inner: return "inner";
        case JoinStrategy::Right: return "right";
        case JoinStrategy::Full:  return "full";
    }
    return "left";
}

JoinStrategy join_strategy_from_token(const char* s) {
    if (!s) return JoinStrategy::Left;
    if (std::strcmp(s, "inner") == 0) return JoinStrategy::Inner;
    if (std::strcmp(s, "right") == 0) return JoinStrategy::Right;
    if (std::strcmp(s, "full")  == 0) return JoinStrategy::Full;
    return JoinStrategy::Left;
}

// ----------------------------------------------------------------------------
// aggregation_alias
// ----------------------------------------------------------------------------
std::string aggregation_alias(const Aggregation& a) {
    if (!a.alias.empty()) return a.alias;
    if (a.fn == AggFn::Count) return "count";
    if (a.fn == AggFn::Percentile) {
        int pct = (int)(a.arg * 100.0 + 0.5);
        char buf[128];
        std::snprintf(buf, sizeof(buf), "p%d_%s", pct, a.col.c_str());
        return buf;
    }
    std::string out = agg_fn_token(a.fn);
    out += '_';
    out += a.col;
    return out;
}

// ----------------------------------------------------------------------------
// DateGranularity
// ----------------------------------------------------------------------------
const char* date_granularity_token(DateGranularity g) {
    switch (g) {
        case DateGranularity::None:  return "";
        case DateGranularity::Year:  return "year";
        case DateGranularity::Month: return "month";
        case DateGranularity::Week:  return "week";
        case DateGranularity::Day:   return "day";
        case DateGranularity::Hour:  return "hour";
    }
    return "";
}

DateGranularity date_granularity_from_token(const char* s) {
    if (!s || !*s) return DateGranularity::None;
    if (std::strcmp(s, "year")  == 0) return DateGranularity::Year;
    if (std::strcmp(s, "month") == 0) return DateGranularity::Month;
    if (std::strcmp(s, "week")  == 0) return DateGranularity::Week;
    if (std::strcmp(s, "day")   == 0) return DateGranularity::Day;
    if (std::strcmp(s, "hour")  == 0) return DateGranularity::Hour;
    return DateGranularity::None;
}

DateGranularity parse_breakout_granularity(const std::string& breakout,
                                           std::string& col_out) {
    auto pos = breakout.rfind(':');
    if (pos == std::string::npos) {
        col_out = breakout;
        return DateGranularity::None;
    }
    std::string suffix = breakout.substr(pos + 1);
    DateGranularity g = date_granularity_from_token(suffix.c_str());
    if (g == DateGranularity::None) {
        col_out = breakout;
        return DateGranularity::None;
    }
    col_out = breakout.substr(0, pos);
    return g;
}

// ----------------------------------------------------------------------------
// Lua string literal escaping
// ----------------------------------------------------------------------------
std::string lua_string_literal(const std::string& s) {
    std::string out;
    out.reserve(s.size() + 4);
    out += '"';
    for (char c : s) {
        switch (c) {
            case '\\': out += "\\\\"; break;
            case '"':  out += "\\\""; break;
            case '\n': out += "\\n";  break;
            case '\r': out += "\\r";  break;
            case '\t': out += "\\t";  break;
            default:
                if ((unsigned char)c < 0x20) {
                    char b[8];
                    std::snprintf(b, sizeof(b), "\\%d", (unsigned char)c);
                    out += b;
                } else {
                    out += c;
                }
        }
    }
    out += '"';
    return out;
}

// ----------------------------------------------------------------------------
// Color <-> hex
// ----------------------------------------------------------------------------
std::string color_to_hex(unsigned int c) {
    unsigned int r = c & 0xFF;
    unsigned int g = (c >> 8)  & 0xFF;
    unsigned int b = (c >> 16) & 0xFF;
    unsigned int a = (c >> 24) & 0xFF;
    char buf[16];
    if (a == 0xFF)
        std::snprintf(buf, sizeof(buf), "#%02x%02x%02x", r, g, b);
    else
        std::snprintf(buf, sizeof(buf), "#%02x%02x%02x%02x", r, g, b, a);
    return buf;
}

unsigned int hex_to_color(const std::string& s) {
    if (s.size() < 7 || s[0] != '#') return 0xFFFFFFFF;
    auto hex2 = [&](size_t i) -> unsigned int {
        unsigned int v = 0;
        if (i + 1 < s.size()) std::sscanf(s.c_str() + i, "%2x", &v);
        return v;
    };
    unsigned int r = hex2(1), g = hex2(3), b = hex2(5);
    unsigned int a = (s.size() >= 9) ? hex2(7) : 0xFF;
    return r | (g << 8) | (b << 16) | (a << 24);
}

} // namespace data_table
