static void HookLuaForceExit(lua_State *L, lua_Debug *debug) {
    SDL_PumpEvents();
    int            numkeys = 0;
    const bool *keys    = SDL_GetKeyboardState(&numkeys);
    if (keys[SDL_SCANCODE_F9])
        luaL_error(L, "lua execution got interrupted");
}

API double GetStyleFloat(String name, double default_float) {
    double result = default_float;
    lua_getglobal(LuaState, "Style");
    defer { lua_pop(LuaState, 1); };
    if (lua_istable(LuaState, -1)) {
        lua_pushlstring(LuaState, name.data, name.len);
        lua_gettable(LuaState, -2);
        defer { lua_pop(LuaState, 1); };
        if (lua_isnumber(LuaState, -1) || lua_isboolean(LuaState, -1) || lua_isinteger(LuaState, -1)) {
            lua_Number num = lua_tonumber(LuaState, -1);
            result = (double)num;
        }
    }
    return result;
}

API Int GetStyleInt(String name, Int default_int) {
    Int result = default_int;
    lua_getglobal(LuaState, "Style");
    defer { lua_pop(LuaState, 1); };
    if (lua_istable(LuaState, -1)) {
        lua_pushlstring(LuaState, name.data, name.len);
        lua_gettable(LuaState, -2);
        defer { lua_pop(LuaState, 1); };
        if (lua_isnumber(LuaState, -1) || lua_isboolean(LuaState, -1) || lua_isinteger(LuaState, -1)) {
            lua_Integer num = lua_tointeger(LuaState, -1);
            result          = (Int)num;
        }
    }
    return result;
}

API String GetStyleString(String name, String default_string) {
    String result = default_string;
    lua_getglobal(LuaState, "Style");
    defer { lua_pop(LuaState, 1); };
    if (lua_istable(LuaState, -1)) {
        lua_pushlstring(LuaState, name.data, name.len);
        lua_gettable(LuaState, -2);
        defer { lua_pop(LuaState, 1); };
        if (lua_isstring(LuaState, -1)) {
            const char *string = lua_tostring(LuaState, -1);
            result             = Intern(&GlobalInternTable, string);
        }
    }
    return result;
}

API Color GetColor(String name, Color default_color) {
    Color result = default_color;
    lua_getglobal(LuaState, "Color");
    defer { lua_pop(LuaState, 1); };
    if (lua_istable(LuaState, -1)) {
        lua_pushlstring(LuaState, name.data, name.len);
        lua_gettable(LuaState, -2);
        defer { lua_pop(LuaState, 1); };
        if (lua_isnumber(LuaState, -1)) {
            lua_Integer num = lua_tointeger(LuaState, -1);
            result.r        = (uint8_t)((0xFF000000 & num) >> 24);
            result.g        = (uint8_t)((0x00FF0000 & num) >> 16);
            result.b        = (uint8_t)((0x0000FF00 & num) >> 8);
            result.a        = (uint8_t)((0x000000FF & num) >> 0);
        }
    }
    return result;
}

API String GetFieldString(lua_State *L, String name) {
    String result = {};
    if (lua_istable(L, -1)) {
        lua_pushlstring(L, name.data, name.len);
        lua_gettable(L, -2);
        defer { lua_pop(L, 1); };
        if (lua_isstring(L, -1)) {
            result = lua_tostring(L, -1);
        }
    }
    return result;
}

Int GetFieldAInt(lua_State *L, const char *name) {
    lua_getfield(L, -1, name);
    lua_Integer num = lua_tointeger(L, -1);
    lua_pop(L, 1);
    return (Int)num;
}

double GetFieldAFloat(lua_State *L, const char *name) {
    lua_getfield(L, -1, name);
    double num = lua_tonumber(L, -1);
    lua_pop(L, 1);
    return num;
}

const char *GetFieldAString(lua_State *L, const char *name) {
    lua_getfield(L, -1, name);
    const char *result = lua_tostring(L, -1);

    lua_pop(L, 1);
    return result;
}

API void PushEvent(lua_State *L, Event *event) {
    lua_createtable(L, 0, EVENT_FIELD_COUNT);
#define lua_pushInt lua_pushinteger
#define lua_pushString lua_pushstring
#define lua_pushFloat lua_pushnumber
#define X(TYPE, KIND, NAME)         \
    lua_push##KIND(L, event->NAME); \
    lua_setfield(L, -2, #NAME);
    EVENT_FIELDS
#undef X
}

// :Event
API int Lua_Play(lua_State *L) {
    if (!lua_istable(L, -1)) luaL_error(L, "expected a table of events");
    defer { lua_pop(L, 1); };

    int size = (int)lua_rawlen(L, -1);
    for (int i = 0; i < size; i += 1) {
        lua_geti(L, -1, i + 1);
        if (!lua_istable(L, -1)) luaL_error(L, "expected a table of events");
        defer { lua_pop(L, 1); };

        Event event = {};
#define X(TYPE, KIND, NAME) event.NAME = (TYPE)GetFieldA##KIND(L, #NAME);
        EVENT_FIELDS
#undef X
        Add(&EventPlayback, event);
    }

    return 0;
}

void          ReloadStyle();
extern String BaseLuaConfig;

API void LoadLuaBuffer(Buffer *lua_buffer) {
    if (!lua_buffer) return;
    ReportConsolef("reloading config: %S", lua_buffer->name);

    Scratch scratch;
    String  string = AllocCharString(scratch, lua_buffer);
    if (luaL_dostring(LuaState, string.data) == LUA_OK) {
        if (lua_isstring(LuaState, -1)) {
            const char *text = lua_tostring(LuaState, -1);
            ReportConsolef(text);
            lua_pop(LuaState, 1);
        }
    } else {
        const char *error_message = lua_tostring(LuaState, -1);
        ReportWarningf("Failed to load user config! %s", error_message);
        lua_pop(LuaState, 1);
    }
    lua_buffer->user_change_id = lua_buffer->change_id;
}

API void ReloadLuaConfigs(bool reload) {
    if (LuaConfigBuffer && !LuaConfigBuffer->dirty && LuaConfigBuffer->change_id != LuaConfigBuffer->user_change_id) {
        reload = true;
    }
    if (LuaProjectBuffer && !LuaProjectBuffer->dirty && LuaProjectBuffer->change_id != LuaProjectBuffer->user_change_id) {
        reload = true;
    }
    if (reload == false) {
        return;
    }

    LoadLuaBuffer(LuaConfigBuffer);
    LoadLuaBuffer(LuaProjectBuffer);

    ReloadStyle();
    ReloadFont(StyleFont, (U32)StyleFontSize);
    For(Windows) {
        it->draw_scrollbar    = StyleDrawScrollbar;
        it->draw_line_numbers = StyleDrawLineNumbers;
    }
}

API bool CallLuaFunc(char *func_name, int arg_count, int ret_count) {
    if (lua_pcall(LuaState, arg_count, ret_count, 0) != 0) {
        const char *error_message = lua_tostring(LuaState, -1);
        ReportWarningf("Failed to call a lua function: %s! %s", func_name, error_message);
        lua_pop(LuaState, 1);
        return false;
    }
    return true;
}

void CallLuaOnInit() {
    lua_getglobal(LuaState, "OnInit");
    CallLuaFunc("OnInit", 0, 0);
}

API void InitLuaConfig() {
    LuaState = luaL_newstate();
    luaL_openlibs(LuaState);
    lua_sethook(LuaState, HookLuaForceExit, LUA_MASKCOUNT, 100000000);

    for (int i = 0; LuaFunctions[i].name; i += 1) {
        lua_pushcfunction(LuaState, LuaFunctions[i].func);
        lua_setglobal(LuaState, LuaFunctions[i].name);
    }

#if OS_WINDOWS
    lua_pushinteger(LuaState, 0);
#else
    lua_pushinteger(LuaState, 1);
#endif
    lua_setglobal(LuaState, "OS_VALUE");

    // Init base config, test that it works and initialize the lua stuff
    if (!luaL_dostring(LuaState, BaseLuaConfig.data) == LUA_OK) {
        const char *error_message = lua_tostring(LuaState, -1);
        ReportErrorf("Failed to load base lua config! %s", error_message);
        lua_pop(LuaState, 1);
        Assert(!"Invalid codepath");
    }

    // Init user config
    Buffer *lua_buffer = NULL;

    Scratch scratch;
    String lua_config_exe = Format(scratch, "%S/init.lua", GetExeDir(scratch));
    if (FileExists(lua_config_exe)) {
        lua_buffer = BufferOpenFile(lua_config_exe);
    }

    if (lua_buffer == NULL) {
        String lua_config_remote = Format(scratch, "%S/init.lua", ConfigDir);
// #if DEBUG_BUILD
//         // WARNING! Delete config to make sure we are running this code more frequently
//         SDL_RemovePath(lua_config_remote.data);
//         ReportConsolef("deleting config for debug purposes!");
// #endif
        lua_buffer = BufferOpenFile(lua_config_remote);
        if (lua_buffer->len == 0) {
            String16 string16 = ToString16(scratch, BaseLuaConfig);
            RawReplaceText(lua_buffer, {}, string16);
            ReportConsolef("no config at: %S - creating config buffer", lua_config_remote);
        }
    }
    LuaConfigBuffer = lua_buffer;
    ReloadLuaConfigs(true);
    CallLuaOnInit();
}
