#include "core/sql_parse.h"

#include <cctype>
#include <string>

namespace fn_ui {

namespace {

// Devuelve la version uppercase ASCII del primer token (delimitado por
// whitespace) de `s`, asumiendo que `s` empieza ya en el token.
std::string first_token_upper(const std::string& s, size_t start) {
    std::string out;
    while (start < s.size()) {
        unsigned char c = static_cast<unsigned char>(s[start]);
        if (std::isspace(c)) break;
        if (!std::isalpha(c)) break; // keywords son solo letras
        out.push_back(static_cast<char>(std::toupper(c)));
        ++start;
    }
    return out;
}

// Devuelve el indice del primer caracter "real" (no whitespace, no comentario)
// a partir de `i`. Avanza saltando -- ... \n y /* ... */.
size_t skip_ws_and_comments(const std::string& s, size_t i) {
    while (i < s.size()) {
        unsigned char c = static_cast<unsigned char>(s[i]);
        if (std::isspace(c)) { ++i; continue; }
        if (c == '-' && i + 1 < s.size() && s[i + 1] == '-') {
            // line comment hasta \n
            i += 2;
            while (i < s.size() && s[i] != '\n') ++i;
            continue;
        }
        if (c == '/' && i + 1 < s.size() && s[i + 1] == '*') {
            // block comment hasta */
            i += 2;
            while (i + 1 < s.size() && !(s[i] == '*' && s[i + 1] == '/')) ++i;
            if (i + 1 < s.size()) i += 2;
            else i = s.size();
            continue;
        }
        break;
    }
    return i;
}

std::string trim(const std::string& s) {
    size_t a = 0, b = s.size();
    while (a < b && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
    while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1]))) --b;
    return s.substr(a, b - a);
}

} // namespace

SqlStmtKind sql_classify(const std::string& stmt) {
    size_t i = skip_ws_and_comments(stmt, 0);
    if (i >= stmt.size()) return SqlStmtKind::Unknown;
    std::string head = first_token_upper(stmt, i);
    if (head == "SELECT" || head == "WITH") return SqlStmtKind::Select;
    if (head == "INSERT")                   return SqlStmtKind::Insert;
    if (head == "UPDATE")                   return SqlStmtKind::Update;
    if (head == "DELETE")                   return SqlStmtKind::Delete;
    if (head == "CREATE")                   return SqlStmtKind::Create;
    if (head == "DROP")                     return SqlStmtKind::Drop;
    if (head == "ALTER")                    return SqlStmtKind::Alter;
    if (head == "PRAGMA")                   return SqlStmtKind::Pragma;
    if (head == "EXPLAIN")                  return SqlStmtKind::Explain;
    return SqlStmtKind::Unknown;
}

std::vector<SqlStatement> sql_parse(const std::string& input) {
    std::vector<SqlStatement> out;

    enum class Mode { Normal, LineComment, BlockComment, SingleStr, DoubleStr, BackTick };
    Mode m = Mode::Normal;

    size_t stmt_start = 0;
    int    cur_line   = 1;
    int    stmt_line  = 1;
    bool   stmt_has_content = false;

    auto flush = [&](size_t end) {
        std::string raw = input.substr(stmt_start, end - stmt_start);
        std::string t   = trim(raw);
        if (!t.empty()) {
            SqlStatement s;
            s.text = t;
            s.kind = sql_classify(t);
            s.line = stmt_line;
            out.push_back(std::move(s));
        }
    };

    for (size_t i = 0; i < input.size(); ++i) {
        char c = input[i];
        char n = (i + 1 < input.size()) ? input[i + 1] : '\0';
        if (c == '\n') ++cur_line;

        switch (m) {
        case Mode::Normal:
            if (c == '-' && n == '-') {
                m = Mode::LineComment;
                ++i;
            } else if (c == '/' && n == '*') {
                m = Mode::BlockComment;
                ++i;
            } else if (!stmt_has_content && !std::isspace(static_cast<unsigned char>(c))) {
                // marca inicio "real" de un statement (despues de skip ws/comments)
                stmt_line = cur_line;
                stmt_has_content = true;
                // procesar el caracter actual abajo (no es comentario ni ws)
                if (c == '\'')      m = Mode::SingleStr;
                else if (c == '"') m = Mode::DoubleStr;
                else if (c == '`')  m = Mode::BackTick;
                else if (c == ';') {
                    flush(i);
                    stmt_start = i + 1;
                    stmt_has_content = false;
                }
            } else if (c == '\'') {
                m = Mode::SingleStr;
            } else if (c == '"') {
                m = Mode::DoubleStr;
            } else if (c == '`') {
                m = Mode::BackTick;
            } else if (c == ';') {
                flush(i);
                stmt_start = i + 1;
                stmt_has_content = false;
            }
            break;

        case Mode::LineComment:
            if (c == '\n') m = Mode::Normal;
            break;

        case Mode::BlockComment:
            if (c == '*' && n == '/') {
                m = Mode::Normal;
                ++i;
            }
            break;

        case Mode::SingleStr:
            if (c == '\'') {
                // SQL escapa '' como literal, sigue dentro de la cadena.
                if (n == '\'') { ++i; }
                else m = Mode::Normal;
            }
            break;

        case Mode::DoubleStr:
            if (c == '"') {
                if (n == '"') { ++i; }
                else m = Mode::Normal;
            }
            break;

        case Mode::BackTick:
            if (c == '`') {
                if (n == '`') { ++i; }
                else m = Mode::Normal;
            }
            break;
        }
    }

    // ultimo statement sin ';' final
    if (stmt_start < input.size()) {
        flush(input.size());
    }

    return out;
}

} // namespace fn_ui
