#if defined(DEBUG) && defined(__linux__)
#include "lua.debug.h"
#include "lua.utils.h"

#include "external/lua/lua.hpp"
#include "mini/win/win.h"

#include "other/json.hpp"

#include <stack>
#include <set>
#include <string>
#include <queue>
#include <thread>
#include <iostream>
#include <poll.h>
#include <mutex>

namespace mini
{
    namespace lua
    {
        namespace debug
        {
            struct Breakpoint {
                int line;
                std::string condition; // puede estar vacío
                std::string logMessage;  // "" si no es logpoint
                std::string hitCondition; // "" si no hay hit count
            };

            struct FrameInfo {
                int level;               // nivel en lua_getstack
                std::string source;
                int line;
                //std::vector<Variable> locals;
                //std::vector<Variable> upvalues;
            };

            struct StackFrameInfo {
                std::string file;
                int line;
                std::string function;
            };
            std::string lastExceptionTraceback;
            std::vector<StackFrameInfo> lastExceptionStack;
            std::string lastExceptionMessage;
            bool hasException = false;

            std::set<std::string> exceptionFilters;// {"all"};
            std::vector<FrameInfo> g_frames;

            //std::unordered_map<std::string, std::vector<int>> g_breakpoints;
            std::unordered_map<std::string, std::vector<Breakpoint>> g_breakpoints;
            std::mutex g_breakMutex;

            std::queue<std::string> g_debugCommands;
            std::mutex g_cmdMutex;

            std::atomic<bool> g_running = true;
            std::atomic<bool> g_paused = false;

            std::string g_pauseFile;
            int g_pauseLine = 0;
            bool function_has_breakpoints = false;
            std::stack<bool> funBreakStack;
            bool debug_enabled = false;

            enum StepMode {
                STEP_NONE,
                STEP_INTO,
                STEP_OVER,
                STEP_OUT
            };

            StepMode g_stepMode = STEP_NONE;
            int g_stepDepth = 0;

            using json = nlohmann::json;

            int getStackDepth(lua_State* L) {
                lua_Debug ar;
                int depth = 0;

                while (lua_getstack(L, depth, &ar)) {
                    depth++;
                }

                return depth;
            }

            json getStackTrace(lua_State* L) {
                json frames = json::array();
                lua_Debug ar;

                int depth = 0;
                while (lua_getstack(L, depth, &ar)) {
                    lua_getinfo(L, "nSl", &ar);

                    //const char* src = chunkToPath(ar.source).c_str();

                    json frame = {
                        { "id", depth + 1 },
                        { "name", ar.name ? ar.name : "?" },
                        { "line", ar.currentline },
                        { "column", 1 },
                        { "source", {
                            { "path", chunkToPath(ar.source) }
                        }}
                    };

                    frames.push_back(frame);
                    depth++;
                }

                return json{
                    { "stackFrames", frames },
                    { "totalFrames", depth }
                };
            }

            int allocateRefForTable(lua_State* L, int index) {
                lua_pushvalue(L, index);           // copia la tabla
                int ref = luaL_ref(L, LUA_REGISTRYINDEX);
                return ref;
            }

            bool pushTableFromRef(lua_State* L, int ref) {
                lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

                if (!lua_istable(L, -1)) {
                    lua_pop(L, 1);
                    return false;
                }

                return true;
            }

            json getLocals(lua_State* L, int frame) {
                json vars = json::array();

                lua_Debug ar;
                // Obtenemos el frame solicitado
                if (!lua_getstack(L, frame, &ar)) {
                    printf("INVALID STACK FRAME\n");
                    return json{ {"variables", vars} };
                }

                // Pedimos info del frame
                lua_getinfo(L, "nSl", &ar);

                int i = 1;
                const char* name;

                while ((name = lua_getlocal(L, &ar, i)) != NULL) {

                    if (strcmp(name, "(temporary)") == 0) {
                        lua_pop(L, 1);
                        i++;
                        continue;
                    }

                    json v;
                    v["name"] = name;

                    int type = lua_type(L, -1);

                    switch (type) {
                        case LUA_TNUMBER:
                            v["value"] = std::to_string(lua_tonumber(L, -1));
                            v["type"] = "number";
                            v["ref"] = 0;
                            break;

                        case LUA_TBOOLEAN:
                            v["value"] = lua_toboolean(L, -1) ? "true" : "false";
                            v["type"] = "boolean";
                            v["ref"] = 0;
                            break;

                        case LUA_TSTRING:
                            v["value"] = lua_tostring(L, -1);
                            v["type"] = "string";
                            v["ref"] = 0;
                            break;

                        case LUA_TTABLE:
                            v["value"] = "{table}";
                            v["type"] = "table";
                            v["ref"] = allocateRefForTable(L, -1);
                            break;

                        case LUA_TFUNCTION:
                            v["value"] = "function";
                            v["type"] = "function";
                            v["ref"] = 0;
                            break;

                        case LUA_TNIL:
                            v["value"] = "nil";
                            v["type"] = "nil";
                            v["ref"] = 0;
                            break;

                        default:
                            v["value"] = lua_typename(L, type);
                            v["type"] = lua_typename(L, type);
                            v["ref"] = 0;
                            break;
                    }

                    vars.push_back(v);

                    // Quitamos el valor de la pila
                    lua_pop(L, 1);

                    i++;
                }

                return json{
                    { "variables", vars }
                };
            }

            json getUpvalues(lua_State* L, int frame) {
                json vars = json::array();

                lua_Debug ar;
                if (!lua_getstack(L, frame, &ar)) {
                    return json{ {"variables", vars} };
                }

                // Pedimos info del frame, incluyendo la función ("f")
                lua_getinfo(L, "nSlf", &ar);

                // La función está ahora en la pila
                int funcIndex = lua_gettop(L);

                int i = 1;
                const char* name;

                while ((name = lua_getupvalue(L, funcIndex, i)) != NULL) {
                    json v;
                    v["name"] = name;

                    int type = lua_type(L, -1);

                    switch (type) {
                        case LUA_TNUMBER:
                            v["value"] = std::to_string(lua_tonumber(L, -1));
                            v["type"] = "number";
                            v["ref"] = 0;
                            break;

                        case LUA_TBOOLEAN:
                            v["value"] = lua_toboolean(L, -1) ? "true" : "false";
                            v["type"] = "boolean";
                            v["ref"] = 0;
                            break;

                        case LUA_TSTRING:
                            v["value"] = lua_tostring(L, -1);
                            v["type"] = "string";
                            v["ref"] = 0;
                            break;

                        case LUA_TTABLE:
                            v["value"] = "{table}";
                            v["type"] = "table";
                            v["ref"] = allocateRefForTable(L, -1);
                            break;

                        case LUA_TNIL:
                            v["value"] = "nil";
                            v["type"] = "nil";
                            v["ref"] = 0;
                            break;

                        default:
                            v["value"] = lua_typename(L, type);
                            v["type"] = lua_typename(L, type);
                            v["ref"] = 0;
                            break;
                    }

                    vars.push_back(v);

                    lua_pop(L, 1); // quitamos el valor del upvalue
                    i++;
                }

                // Quitamos la función del stack
                lua_pop(L, 1);

                return json{
                    { "variables", vars }
                };
            }

            json getGlobals(lua_State* L) {
                json vars = json::array();

                // Empujamos _G a la pila
                lua_pushglobaltable(L);  // equivalente a lua_getglobal(L, "_G")

                lua_pushnil(L);  // primera clave
                while (lua_next(L, -2) != 0) {
                    // Ahora en la pila:
                    // -1 → valor
                    // -2 → clave
                    // -3 → _G

                    // Solo aceptamos claves string
                    if (lua_type(L, -2) == LUA_TSTRING) {
                        const char* name = lua_tostring(L, -2);

                        json v;
                        v["name"] = name;

                        int type = lua_type(L, -1);

                        switch (type) {
                            case LUA_TNUMBER:
                                v["value"] = std::to_string(lua_tonumber(L, -1));
                                v["type"] = "number";
                                v["ref"] = 0;
                                break;

                            case LUA_TBOOLEAN:
                                v["value"] = lua_toboolean(L, -1) ? "true" : "false";
                                v["type"] = "boolean";
                                v["ref"] = 0;
                                break;

                            case LUA_TSTRING:
                                v["value"] = lua_tostring(L, -1);
                                v["type"] = "string";
                                v["ref"] = 0;
                                break;

                            case LUA_TTABLE:
                                v["value"] = "{table}";
                                v["type"] = "table";
                                v["ref"] = allocateRefForTable(L, -1);
                                break;

                            case LUA_TFUNCTION:
                                v["value"] = "function";
                                v["type"] = "function";
                                v["ref"] = 0;
                                break;

                            case LUA_TNIL:
                                v["value"] = "nil";
                                v["type"] = "nil";
                                v["ref"] = 0;
                                break;

                            default:
                                v["value"] = lua_typename(L, type);
                                v["type"] = lua_typename(L, type);
                                v["ref"] = 0;
                                break;
                        }

                        vars.push_back(v);
                    }

                    lua_pop(L, 1); // pop valor, deja clave para lua_next
                }

                lua_pop(L, 1); // pop _G

                return json{
                    { "variables", vars }
                };
            }

            json expandTable(lua_State* L, int ref) {
                json vars = json::array();

                // Recuperamos la tabla del registry
                lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

                lua_pushnil(L);
                while (lua_next(L, -2) != 0) {
                    json v;

                    // clave
                    if (lua_type(L, -2) == LUA_TSTRING) {
                        v["name"] = lua_tostring(L, -2);
                    } else if (lua_type(L, -2) == LUA_TNUMBER) {
                        v["name"] = std::to_string(lua_tonumber(L, -2));
                    } else {
                        v["name"] = "<non-string-key>";
                    }

                    // valor
                    int type = lua_type(L, -1);

                    switch (type) {
                        case LUA_TNUMBER:
                            v["value"] = std::to_string(lua_tonumber(L, -1));
                            v["type"] = "number";
                            v["ref"] = 0;
                            break;

                        case LUA_TBOOLEAN:
                            v["value"] = lua_toboolean(L, -1) ? "true" : "false";
                            v["type"] = "boolean";
                            v["ref"] = 0;
                            break;

                        case LUA_TSTRING:
                            v["value"] = lua_tostring(L, -1);
                            v["type"] = "string";
                            v["ref"] = 0;
                            break;

                        case LUA_TTABLE:
                            v["value"] = "{table}";
                            v["type"] = "table";
                            v["ref"] = allocateRefForTable(L, -1);
                            break;

                        default:
                            v["value"] = lua_typename(L, type);
                            v["type"] = lua_typename(L, type);
                            v["ref"] = 0;
                            break;
                    }

                    vars.push_back(v);

                    lua_pop(L, 1); // pop valor
                }

                lua_pop(L, 1); // pop tabla

                return json{
                    { "kind", "expand" },
                    { "variables", vars }
                };
            }

            json setVariable(lua_State* L, int frame, const std::string& scope,
                            const std::string& name, const std::string& valueStr)
            {
                lua_Debug ar;

                if (!lua_getstack(L, frame, &ar)) {
                    return { {"error", "invalid frame"} };
                }

                lua_getinfo(L, "nSl", &ar);

                // Convertir valueStr a valor Lua
                auto pushValue = [&](const std::string& s) {
                    // número
                    char* end;
                    double num = strtod(s.c_str(), &end);
                    if (*end == '\0') {
                        lua_pushnumber(L, num);
                        return;
                    }

                    // boolean
                    if (s == "true") { lua_pushboolean(L, 1); return; }
                    if (s == "false") { lua_pushboolean(L, 0); return; }

                    // nil
                    if (s == "nil") { lua_pushnil(L); return; }

                    // string
                    lua_pushstring(L, s.c_str());
                };

                if (scope == "locals") {
                    int i = 1;
                    const char* localName;
                    while ((localName = lua_getlocal(L, &ar, i)) != NULL) {
                        lua_pop(L, 1); // pop old value

                        if (name == localName) {
                            pushValue(valueStr);
                            lua_setlocal(L, &ar, i);
                            return { {"value", valueStr} };
                        }

                        i++;
                    }
                }

                if (scope == "upvalues") {
                    lua_getinfo(L, "f", &ar); // push function

                    int i = 1;
                    const char* upName;
                    while ((upName = lua_getupvalue(L, -1, i)) != NULL) {
                        lua_pop(L, 1); // pop old value

                        if (name == upName) {
                            pushValue(valueStr);
                            lua_setupvalue(L, -1, i);
                            lua_pop(L, 1); // pop function
                            return { {"value", valueStr} };
                        }

                        i++;
                    }

                    lua_pop(L, 1); // pop function
                }

                if (scope == "globals") {
                    pushValue(valueStr);
                    lua_setglobal(L, name.c_str());
                    return { {"value", valueStr} };
                }

                return { {"error", "variable not found"} };
            }

            json setTableField(lua_State* L, int ref, const std::string& key, const std::string& valueStr)
            {
                // Recuperar la tabla desde tu sistema de referencias
                if (!pushTableFromRef(L, ref)) {
                    return { {"error", "invalid table ref"} };
                }

                // Convertir el valor
                auto pushValue = [&](const std::string& s) {
                    char* end;
                    double num = strtod(s.c_str(), &end);
                    if (*end == '\0') { lua_pushnumber(L, num); return; }
                    if (s == "true") { lua_pushboolean(L, 1); return; }
                    if (s == "false") { lua_pushboolean(L, 0); return; }
                    if (s == "nil") { lua_pushnil(L); return; }
                    lua_pushstring(L, s.c_str());
                };

                pushValue(valueStr);

                // tabla[key] = valor
                lua_setfield(L, -2, key.c_str());

                lua_pop(L, 1); // pop tabla

                return { {"value", valueStr} };
            }

            json evalExpression(lua_State* L, const std::string& expr) {
                lua_Debug ar;
                if (!lua_getstack(L, 0, &ar)) {
                    return { {"error", "no stack"} };
                }

                lua_getinfo(L, "nSl", &ar);

                // Creamos un entorno para la evaluación
                lua_newtable(L);              // env
                int envIndex = lua_gettop(L);

                // 1. Copiar locals al env
                int i = 1;
                const char* name;
                while ((name = lua_getlocal(L, &ar, i)) != NULL) {
                    lua_setfield(L, envIndex, name);
                    i++;
                }

                // 2. Copiar upvalues al env
                lua_getinfo(L, "f", &ar);
                int funcIndex = lua_gettop(L);

                i = 1;
                while ((name = lua_getupvalue(L, funcIndex, i)) != NULL) {
                    lua_setfield(L, envIndex, name);
                    i++;
                }

                lua_pop(L, 1); // pop función

                // 3. Copiar globals (_G)
                lua_pushglobaltable(L);
                lua_setfield(L, envIndex, "_G");
                // mt = { __index = _G }
                lua_newtable(L);                 // mt
                lua_pushglobaltable(L);          // _G
                lua_setfield(L, -2, "__index");  // mt.__index = _G

                // setmetatable(env, mt)
                lua_setmetatable(L, envIndex);

                // 4. Construir código: función que recibe _ENV
                std::string code = "return function(_ENV) return " + expr + " end";

                if (luaL_loadbuffer(L, code.c_str(), code.size(), "eval") != LUA_OK) {
                    std::string err = lua_tostring(L, -1);
                    lua_pop(L, 1);
                    return { {"error", err} };
                }

                // Ejecutar para obtener la función interna
                if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
                    std::string err = lua_tostring(L, -1);
                    lua_pop(L, 1);
                    return { {"error", err} };
                }

                // Ahora en la pila está la función interna
                // Le pasamos env como argumento
                lua_pushvalue(L, envIndex);

                // Llamamos a la función(env)
                if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
                    std::string err = lua_tostring(L, -1);
                    lua_pop(L, 1);
                    return { {"error", err} };
                }

                // Convertir el resultado
                json result;

                int type = lua_type(L, -1);
                switch (type) {
                    case LUA_TNUMBER:
                        result["value"] = std::to_string(lua_tonumber(L, -1));
                        result["type"] = "number";
                        result["ref"] = 0;
                        break;

                    case LUA_TBOOLEAN:
                        result["value"] = lua_toboolean(L, -1) ? "true" : "false";
                        result["type"] = "boolean";
                        result["ref"] = 0;
                        break;

                    case LUA_TSTRING:
                        result["value"] = lua_tostring(L, -1);
                        result["type"] = "string";
                        result["ref"] = 0;
                        break;

                    case LUA_TTABLE:
                        result["value"] = "{table}";
                        result["type"] = "table";
                        result["ref"] = allocateRefForTable(L, -1);
                        break;

                    default:
                        result["value"] = lua_typename(L, type);
                        result["type"] = lua_typename(L, type);
                        result["ref"] = 0;
                        break;
                }

                lua_pop(L, 1); // pop result
                lua_pop(L, 1); // pop env

                return result;
            }

            json evalAssign(lua_State* L, int frame, const std::string& expr) {
                // 1. Separar LHS y RHS
                size_t eq = expr.find('=');
                if (eq == std::string::npos) {
                    return { {"error", "not an assignment"} };
                }

                std::string lhs = expr.substr(0, eq);
                std::string rhs = expr.substr(eq + 1);

                // limpiar espacios
                auto trim = [](std::string& s) {
                    size_t a = s.find_first_not_of(" \t");
                    size_t b = s.find_last_not_of(" \t");
                    if (a == std::string::npos) { s = ""; return; }
                    s = s.substr(a, b - a + 1);
                };

                trim(lhs);
                trim(rhs);

                // 2. Evaluar RHS usando tu evalExpression
                json rhsValue = evalExpression(L, rhs);
                if (rhsValue.contains("error")) {
                    return rhsValue;
                }

                std::string rhsStr = rhsValue["value"];

                // 3. Determinar si LHS es:
                //    - variable simple: x
                //    - acceso tabla: player.health
                //    - acceso profundo: a.b.c
                if (lhs.find('.') == std::string::npos) {
                    // variable simple → locals, upvalues o globals
                    // Intentar locals
                    json r = setVariable(L, frame, "locals", lhs, rhsStr);
                    if (!r.contains("error")) return r;

                    // Intentar upvalues
                    r = setVariable(L, frame, "upvalues", lhs, rhsStr);
                    if (!r.contains("error")) return r;

                    // Intentar globals
                    r = setVariable(L, frame, "globals", lhs, rhsStr);
                    return r;
                }

                // 4. Acceso tabla: a.b.c
                // separar en partes
                std::vector<std::string> parts;
                {
                    std::stringstream ss(lhs);
                    std::string item;
                    while (std::getline(ss, item, '.')) {
                        trim(item);
                        parts.push_back(item);
                    }
                }

                if (parts.size() < 2) {
                    return { {"error", "invalid table assignment"} };
                }

                // La clave final
                std::string finalKey = parts.back();
                parts.pop_back();

                // 5. Evaluar la ruta de tabla (a.b.c → obtener tabla c)
                //    usando tu evalExpression
                std::string tableExpr;
                for (size_t i = 0; i < parts.size(); i++) {
                    if (i > 0) tableExpr += ".";
                    tableExpr += parts[i];
                }

                json tableValue = evalExpression(L, tableExpr);
                if (tableValue.contains("error")) {
                    return tableValue;
                }

                if (tableValue["type"] != "table") {
                    return { {"error", "LHS is not a table"} };
                }

                int ref = tableValue["ref"];

                // 6. Asignar dentro de la tabla
                return setTableField(L, ref, finalKey, rhsStr);
            }

            void sendDebugResponse(const std::string& type, const json& payload) {
                json msg = {
                    { "type", type },
                    { "payload", payload }
                };
                //printf("STACKTRACE: %s", msg.dump().c_str());
                std::cout << "@@DEBUG@@" << msg.dump() << std::endl;
            }

            void parseLuaTraceback(const char* tb, std::vector<StackFrameInfo>& out) {
                out.clear();
                if (!tb) return;

                std::stringstream ss(tb);
                std::string line;

                bool inFrames = false;

                while (std::getline(ss, line)) {
                    // Quitar espacios iniciales y tabs
                    while (!line.empty() && (line[0] == ' ' || line[0] == '\t'))
                        line.erase(line.begin());

                    // Saltar la primera línea (mensaje de error)
                    if (!inFrames) {
                        if (line.rfind("stack traceback:", 0) == 0) {
                            inFrames = true;
                        }
                        continue;
                    }

                    // Formato esperado:
                    // [string "modules.ia.hero"]:189: in field 'ia'
                    //
                    // O:
                    // [string "main"]:34: in function <[string "main"]:32>

                    if (line.rfind("[string \"", 0) != 0)
                        continue;

                    // Extraer chunk name
                    size_t q1 = line.find('"');
                    size_t q2 = line.find('"', q1 + 1);
                    if (q1 == std::string::npos || q2 == std::string::npos)
                        continue;

                    std::string chunk = line.substr(q1 + 1, q2 - q1 - 1);

                    // Extraer línea
                    size_t colon1 = line.find(':', q2 + 1);
                    if (colon1 == std::string::npos)
                        continue;

                    size_t colon2 = line.find(':', colon1 + 1);
                    if (colon2 == std::string::npos)
                        continue;

                    int lineNumber = std::stoi(line.substr(colon1 + 1, colon2 - colon1 - 1));

                    // Extraer nombre de función (si existe)
                    std::string func = "unknown";

                    size_t inField = line.find("in field '");
                    if (inField != std::string::npos) {
                        size_t start = inField + 10;
                        size_t end = line.find("'", start);
                        func = line.substr(start, end - start);
                    }

                    size_t inFunc = line.find("in function <");
                    if (inFunc != std::string::npos) {
                        func = "anonymous";
                    }

                    // Convertir chunk → ruta real
                    std::string filePath = chunkToPath(chunk);

                    out.push_back({ filePath, lineNumber, func });
                }
            }

            void processDebugCommand(lua_State* L, const std::string& line) {
                //printf("COMANDO PROCESADO: %s\n", line.c_str());
                if (!line.starts_with("@@DEBUGCMD@@")) {
                    disable(L);
                    return;
                }
                enable(L);

                json j = json::parse(line.substr(12));

                std::string cmd = j["cmd"];

                if (cmd == "setBreakpoints") {
                    std::string file = j["file"];
                    std::string chunk = pathToChunk(file);
                    std::lock_guard<std::mutex> lock(g_breakMutex);

                    g_breakpoints[chunk].clear();

                    for (auto& bp : j["breakpoints"]) {
                        Breakpoint b;
                        b.line = bp["line"];

                        if (bp.contains("condition") && bp["condition"].is_string())
                            b.condition = bp["condition"];
                        else
                            b.condition = "";

                        if (bp.contains("logMessage") && bp["logMessage"].is_string())
                            b.logMessage = bp["logMessage"];
                        else
                            b.logMessage = "";

                        if (bp.contains("hitCondition") && bp["hitCondition"].is_string())
                            b.hitCondition = bp["hitCondition"];
                        else
                            b.hitCondition = "";

                        g_breakpoints[chunk].push_back(b);
                    }
                }
                else if (cmd == "continue") {
                    printf("CONTINUA\n\n");
                    g_stepMode = STEP_NONE;
                    g_paused = false;
                    mini::win::raise();
                }
                else if (cmd == "pause") {
                    g_stepMode = STEP_INTO;
                    g_paused = false;
                }
                else if (cmd == "stepOver") {
                    g_stepMode = STEP_OVER;
                    g_stepDepth = getStackDepth(L);
                    g_paused = false;
                }
                else if (cmd == "stepInto") {
                    g_stepMode = STEP_INTO;
                    g_paused = false;
                }
                else if (cmd == "stepOut") {
                    g_stepMode = STEP_OUT;
                    g_stepDepth = getStackDepth(L);
                    g_paused = false;
                }
                else if (cmd == "stackTrace") {
                    if (hasException) {
                        parseLuaTraceback(lastExceptionTraceback.c_str(), lastExceptionStack);

                        json frames = json::array();

                        for (size_t i = 0; i < lastExceptionStack.size(); i++) {
                            frames.push_back({
                                { "id", (int)i + 1 },
                                { "name", lastExceptionStack[i].function },
                                { "line", lastExceptionStack[i].line },
                                { "column", 1 },
                                { "source", {
                                    { "path", lastExceptionStack[i].file }
                                }}
                            });
                        }

                        json result = {
                            { "stackFrames", frames },
                            { "totalFrames", frames.size() }
                        };

                        sendDebugResponse("stackTrace", result);
                    } else {
                        json result = getStackTrace(L);
                        sendDebugResponse("stackTrace", result);
                    }
                }
                else if (cmd == "locals") {
                    int frame = j.value("frame", 0);
                    json result = getLocals(L, frame);
                    json payload = {
                        { "kind", "locals" },
                        { "variables", result["variables"] }
                    };
                    sendDebugResponse("variables", payload);
                }
                else if (cmd == "upvalues") {
                    int frame = j.value("frame", 0);
                    json result = getUpvalues(L, frame);
                    json payload = {
                        { "kind", "upvalues" },
                        { "variables", result["variables"] }
                    };
                    sendDebugResponse("variables", payload);
                }
                else if (cmd == "globals") {
                    json result = getGlobals(L);
                    json payload = {
                        { "kind", "globals" },
                        { "variables", result["variables"] }
                    };
                    sendDebugResponse("variables", payload);
                }
                else if (cmd == "expand") {
                    int ref = j["ref"];
                    json result = expandTable(L, ref);
                    sendDebugResponse("variables", result);
                }
                else if (cmd == "eval") {
                    std::string expr = j["expr"];
                    json result = evalExpression(L, expr);

                    json payload = {
                        { "kind", "eval" },
                        { "result", result }
                    };
                    sendDebugResponse("eval", payload);
                }
                else if (cmd == "evalAssign") {
                    int frame = j.value("frame", 0);
                    std::string expr = j["expr"];

                    json result = evalAssign(L, frame, expr);

                    json payload = {
                        { "kind", "eval" },
                        { "result", result }
                    };

                    sendDebugResponse("eval", payload);
                }
                else if (cmd == "setVariable") {
                    int frame = j.value("frame", 0);
                    std::string scope = j["scope"];
                    std::string name = j["name"];
                    std::string value = j["value"];

                    json result = setVariable(L, frame, scope, name, value);

                    sendDebugResponse("setVariable", result);
                }
                else if (cmd == "setTableField") {
                    int ref = j["ref"];
                    std::string key = j["key"];
                    std::string valueStr = j["value"];

                    json result = setTableField(L, ref, key, valueStr);

                    sendDebugResponse("setVariable", result);
                }
                else if (cmd == "setExceptionFilters") {
                    exceptionFilters.clear();
                    for (auto& f : j["filters"]) {
                        exceptionFilters.insert(f.get<std::string>());
                    }
                }
            }

            bool checkBreakpointCondition(lua_State* L, const Breakpoint& bp) {
                if (bp.condition.empty()) return true;

                json result = evalExpression(L, bp.condition);
                if (result.contains("error")) return false;
                if (result["type"] == "boolean") return result["value"] == "true";

                // Si la condición no devuelve booleano, se considera false
                return false;
            }

            std::string expandLogMessage(lua_State* L, const std::string& msg) {
                std::string out;
                size_t i = 0;

                while (i < msg.size()) {
                    if (msg[i] == '{') {
                        size_t j = msg.find('}', i + 1);
                        if (j == std::string::npos) break;

                        std::string expr = msg.substr(i + 1, j - i - 1);

                        json r = evalExpression(L, expr);
                        out += r.value("value", "nil");

                        i = j + 1;
                    } else {
                        out += msg[i++];
                    }
                }

                return out;
            }

            void sendLogOutput(const std::string& text) {
                json payload = {
                    { "kind", "log" },
                    { "text", text }
                };
                sendDebugResponse("log", payload);
            }

            bool isBreakpoint(lua_State* L, const std::string& file, int line) {
                std::lock_guard<std::mutex> lock(g_breakMutex);

                auto it = g_breakpoints.find(file);
                if (it == g_breakpoints.end())
                    return false;

                for (auto& bp : it->second) {
                    if (bp.line == line) {
                        if (!bp.logMessage.empty()) {
                            std::string msg = expandLogMessage(L, bp.logMessage);
                            sendLogOutput(msg);
                            return false; // NO parar
                        } else if (checkBreakpointCondition(L, bp)) {
                            return true;
                        }

                    }
                }

                return false;
            }

            void sendBreakEvent(const std::string& file, int line) {
                json j = {
                    {"type", "break"},
                    {"file", file},
                    {"line", line}
                };

                std::cout << "@@DEBUG@@" << j.dump() << std::endl;
                std::cout.flush();
            }

            std::string waitForDebugCommand() {
                //printf("HOLA");
                while (true) {
                    {
                        std::lock_guard<std::mutex> lock(g_cmdMutex);
                        if (!g_debugCommands.empty()) {
                            std::string cmd = g_debugCommands.front();
                            g_debugCommands.pop();
                            return cmd;
                        }
                    }

                    std::this_thread::sleep_for(std::chrono::milliseconds(1));
                }
            }

            bool shouldPauseForStepping(lua_State* L, lua_Debug* ar) {
                int depth = getStackDepth(L);

                switch (g_stepMode) {
                    case STEP_INTO:
                        return true; // siempre parar en la siguiente línea

                    case STEP_OVER:
                        return depth <= g_stepDepth;

                    case STEP_OUT:
                        return depth < g_stepDepth;

                    default:
                        return false;
                }
            }

            void captureStack(lua_State* L) {
                g_frames.clear();

                lua_Debug ar;
                int level = 0;

                while (lua_getstack(L, level, &ar)) {
                    lua_getinfo(L, "nSl", &ar);

                    FrameInfo fi;
                    fi.level = level;
                    fi.source = ar.source;
                    fi.line = ar.currentline;

                    g_frames.push_back(fi);
                    level++;
                }
            }

            void pauseHere(lua_State* L, const std::string& src, int line) {
                g_paused = true;
                g_pauseFile = src;
                g_pauseLine = line;
                g_stepMode = STEP_NONE;

                captureStack(L);

                sendBreakEvent(chunkToPath(src), line);

                while (g_paused) {
                    std::string cmd = waitForDebugCommand();
                    processDebugCommand(L, cmd);
                }
            }

            extern "C" void luaHook(lua_State* L, lua_Debug* ar) {
                lua_getinfo(L, "Sl", ar);
                
                const char* src = ar->source;
                if (src[0]=='=') return;

                if (ar->event == LUA_HOOKCALL) {
                    funBreakStack.push(function_has_breakpoints);
                    bool new_function_has_breakpoints = false;
                    {
                        std::lock_guard<std::mutex> lock(g_breakMutex);
                        auto it = g_breakpoints.find(src);
                        new_function_has_breakpoints = (it != g_breakpoints.end() && !it->second.empty());
                    }
                    if (new_function_has_breakpoints || g_stepMode != STEP_NONE) {
                        if (!function_has_breakpoints)
                            lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKLINE | LUA_MASKRET, 0);
                    } else {
                        //if (function_has_breakpoints)
                            lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0);
                    }
                    function_has_breakpoints = new_function_has_breakpoints;
                }
                else if (ar->event == LUA_HOOKRET) {
                    // Siempre volver al hook base
                    bool new_function_has_breakpoints = funBreakStack.empty() ? false : funBreakStack.top();
                    funBreakStack.pop();
                    if (new_function_has_breakpoints || g_stepMode != STEP_NONE) {
                        if (!function_has_breakpoints)
                            lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKLINE | LUA_MASKRET, 0);
                    } else {
                        //if (function_has_breakpoints)
                            lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0);
                    }
                    function_has_breakpoints = new_function_has_breakpoints;
                }
                else if (ar->event == LUA_HOOKLINE) {
                    if (ar->currentline <= 0) return;
                    const char* src = ar->source;
                    int line = ar->currentline;

                    if (isBreakpoint(L, src, line)) {
                        pauseHere(L, src, line);
                        return;
                    }

                    // 2. Stepping
                    if (g_stepMode != STEP_NONE) {
                        if (shouldPauseForStepping(L, ar)) {
                            pauseHere(L, src, line);
                            return;
                        }
                    }
                }
            }

            void sendExceptionStoppedEvent(const std::string& msg) {
                json payload = {
                    { "reason", "exception" },
                    { "text", msg }
                };
                sendDebugResponse("stopped", payload);
            }

            int luaErrorHandler(lua_State* L) {
                const char* msg = lua_tostring(L, 1);
                if (!msg) msg = "Unknown error";

                // Construir traceback
                luaL_traceback(L, L, msg, 1);

                const char* tb = lua_tostring(L, -1);
                if (tb) {
                    lastExceptionTraceback = tb;
                } else {
                    lastExceptionTraceback = msg;
                }

                // Devolver el traceback a Lua (aunque luego Lua lo sustituya)
                return 1;
            }

            bool call_and_handle_exceptions(lua_State* L)
            {
                lua_pushcfunction(L, luaErrorHandler);
                lua_insert(L, -2); // poner handler debajo de la función
                int status = lua_pcall(L, 0, 0, -2);
                lua_pop(L, 1); // quitar handler

                if (status != LUA_OK) {
                    lastExceptionMessage = lastExceptionTraceback;
                    lua_pop(L, 1);
                    hasException = true;

                    if (exceptionFilters.count("all") || exceptionFilters.count("uncaught")) {
                        sendExceptionStoppedEvent(lastExceptionMessage);
                        return false;
                    }

                    sendLogOutput(lastExceptionMessage);
                    //SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Mini Runtime Exception", lastExceptionTraceback.c_str(), NULL);
                    return false;
                }
                return true;
            }

            std::jthread stdinThread([](std::stop_token st) {
                std::string line;

                struct pollfd pfd;
                pfd.fd = 0;          // stdin
                pfd.events = POLLIN; // queremos saber si hay datos

                while (!st.stop_requested()) {
                    int ret = poll(&pfd, 1, 10); // timeout 10 ms

                    if (ret > 0 && (pfd.revents & POLLIN)) {
                        if (!std::getline(std::cin, line)) {
                            break; // EOF
                        }

                        {
                            std::lock_guard<std::mutex> lock(g_cmdMutex);
                            g_debugCommands.push(line);
                            printf("COMANDO RECIBIDO: %s\n", line.c_str());
                        }
                    } else {
                        std::this_thread::sleep_for(std::chrono::milliseconds(1));
                    }
                }
            });

            void process_commands(lua_State* L) {
                function_has_breakpoints = false;
                while (!funBreakStack.empty()) funBreakStack.pop();

                while (true) {
                    std::string cmd;

                    {
                        std::lock_guard<std::mutex> lock(g_cmdMutex);
                        if (g_debugCommands.empty())
                            break;

                        cmd = g_debugCommands.front();
                        g_debugCommands.pop();
                    }
                    
                    processDebugCommand(L, cmd);
                }
            }

            void init(lua_State* L) {
                //lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0);
            }

            void kill_thread() {
                g_running = false;
                stdinThread.request_stop();
            }

            void toggle(lua_State* L) {
                if (debug_enabled) {
                    debug_enabled = false;
                    lua_sethook(L, NULL,0, 0);
                } else {
                    debug_enabled = true;
                    lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0);
                }
            }

            void enable(lua_State* L) {
                if (debug_enabled) return;
                debug_enabled = true;
                lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0);
            }

            void disable(lua_State* L) {
                if (!debug_enabled) return;
                debug_enabled = false;
                lua_sethook(L, NULL,0, 0);
            }

            bool is_enabled() { return debug_enabled; }
        }
    }
}

#endif
