#include "lua.h"
#include "lua.wrappers.h"
#include "lua.debug.h"
#include "lua.utils.h"
#include "external/lua/lua.hpp"
#include "mini/file/file.h"
#include "other/log.h"

#include <algorithm>
#include <unistd.h>

namespace mini
{
    namespace lua
    {
        lua_State *L;
        bool is_playing = false;
        bool init_exists = false;
        bool update_exists = false;

        bool running() {
            debug::process_commands(L);
            return is_playing;
        }

        std::string assemble_error_message(const char *lua_error_string) {
            std::string error = lua_error_string;   // tu cadena original

            std::string nombre;
            int linea = 0;
            std::string mensaje;

            // 1. Buscar el nombre entre comillas
            auto p1 = error.find('"');
            auto p2 = error.find('"', p1 + 1);
            if (p1 != std::string::npos && p2 != std::string::npos)
                nombre = error.substr(p1 + 1, p2 - (p1 + 1));

            // 2. Buscar la línea después de "]:"
            auto p3 = error.find("]:", p2);
            if (p3 != std::string::npos) {
                size_t start = p3 + 2;
                linea = std::stoi(error.substr(start));
            }

            // 3. Buscar el mensaje después del siguiente ':'
            auto p4 = error.find(':', p3 + 2);
            if (p4 != std::string::npos)
                mensaje = error.substr(p4 + 2); // saltar ": "

            // Ahora tienes:
            // nombre  → "main"
            // linea   → 18
            // mensaje → "unexpected symbol near '+'"
            return debug::chunkToPath(nombre) + ":" + std::to_string(linea) + ":" + mensaje;
        }

        int MiniLoader(lua_State *L) {
            const char *name = luaL_checkstring(L, 1);

            // 1. Convertir puntos en barras
            std::string path(name);
            std::string regpath(name);
            std::replace(path.begin(), path.end(), '.', '/');

            // 2. Detectar comodín "/*"
            bool load_all = false;
            if (path.size() >= 2 && path.substr(path.size()-2) == "/*") {
                load_all = true;
                path = path.substr(0, path.size()-2); // quitar "/*"
                regpath = regpath.substr(0, regpath.size()-2); // quitar "/*"
            }

            if (load_all) {
                std::vector<std::string> files = file::listresourcesdir(path.c_str(), "lua");

                // Ejecutar todos los módulos
                for (auto &f : files) {
                    std::string fullpath = path + "/" + f;
                    std::string registerpath = std::string(regpath + "." + f.substr(0,f.size()-4));

                    int size;
                    char* buffer = file::getfilebuffer(fullpath.c_str(), size);
                    if (!buffer) continue;

                    if (luaL_loadbuffer(L, buffer, size, registerpath.c_str()) == LUA_OK) {
                        lua_pcall(L, 0, 0, 0); // ejecutar módulo, sin devolver nada
                        lua_getglobal(L, "package");
                        lua_getfield(L, -1, "loaded");
                        lua_pushboolean(L, 1);
                        lua_setfield(L, -2, registerpath.c_str());
                        lua_pop(L, 2);
                    } else {
                        log_msg(LOG_LUALD, "Error cargando %s: %s\n", fullpath.c_str(), assemble_error_message(lua_tostring(L, -1)).c_str());
                        lua_pop(L, 1);
                    }

                    free(buffer);
                }

                // Devolver un loader vacío
                lua_pushcfunction(L, [](lua_State* L) -> int {
                    lua_pushboolean(L, 1); // require devuelve true
                    return 1;
                });

                return 1;
            }

            // 3. Cargar un único archivo
            std::string filename = path + ".lua";

            int size;
            char* buffer = file::getfilebuffer(filename.c_str(), size);
            if (!buffer) {
                lua_pushnil(L);
                return 1;
            }

            if (luaL_loadbuffer(L, buffer, size, name)) {
                log_msg(LOG_LUALD, "%s\n", assemble_error_message(lua_tostring(L, -1)).c_str());
                lua_pop(L, 1);
                free(buffer);
                lua_pushnil(L);
                return 1;
            }

            free(buffer);
            return 1;
        }

        void init(const char *main_lua_file) {
            L = luaL_newstate();
            luaL_openlibs(L);

            push_functions(L);
            lua_register(L, "mini_loader", MiniLoader);
            luaL_dostring(L, "table.insert(package.searchers,2,mini_loader)\n");

            int size;
            char* buffer = file::getfilebuffer(main_lua_file, size);
            if (luaL_loadbuffer(L, buffer, size, "main")) {
                log_msg(LOG_LUALD, "%s\n", assemble_error_message(lua_tostring(L, -1)).c_str());
                lua_pop(L,1);
                return;
            }
            free(buffer);
            if (lua_pcall(L,0, LUA_MULTRET, 0)) {
                //luaL_traceback(L, L, NULL, 1);
                log_msg(LOG_LUART, "%s\n", assemble_error_message(lua_tostring(L, -1)).c_str());
                lua_pop(L,1);
                return;
            }

            // Check if _init and _update exist
            lua_getglobal(L, "mini");
            lua_getfield(L, -1, "init");
            if (lua_isfunction(L,-1)) init_exists = true;
            lua_pop(L,1);
            lua_pop(L,1);

            lua_getglobal(L, "mini");
            lua_getfield(L, -1, "update");
            if (lua_isfunction(L,-1)) update_exists = true;
            lua_pop(L,1);
            lua_pop(L,1);

            debug::init(L);
            
            //std::thread(debugCommandThread).detach();
            //printf("stdin isatty: %d\n", isatty(0));
            is_playing = true;
        }

        void quit() {
            if (!is_playing) return;
            is_playing = false;
            lua_close(L);
        }

        void cleanup() {
            debug::kill_thread();
        }

        namespace callbacks
        {
            void init() {
                if (!init_exists) return;
                debug::process_commands(L);
                lua_getglobal(L, "mini");
                lua_getfield(L, -1, "init");

                if (debug::is_enabled()) {
                    is_playing = debug::call_and_handle_exceptions(L);
                } else {
                    if (lua_pcall(L, 0, 0, 0)) {
                        log_msg(LOG_LUART, "%s\n", assemble_error_message(lua_tostring(L, -1)).c_str());
                        lua_pop(L,1);
                        is_playing = false;
                    }
                }
            }

            void update() {
                if (!update_exists) return;
                lua_getglobal(L, "mini");
                lua_getfield(L, -1, "update");

                if (debug::is_enabled()) {
                    is_playing = debug::call_and_handle_exceptions(L);
                } else {
                    if (lua_pcall(L, 0, 0, 0)) {
                        log_msg(LOG_LUART, "%s\n", assemble_error_message(lua_tostring(L, -1)).c_str());
                        lua_pop(L,1);
                        is_playing = false;
                    }
                }
            }

        }
    }
}
