#include "ScriptComponent.h"
#include "../core/utils/Logging.h"
#include "../core/utils/ExceptionHandler.h"
#include "../utils/GameObjectsList.h"

#include "../Entitys/Object.h"
#include "../Components/SpriteComponent.h"
#include "../Components/CameraComponent.h"
#include "../Components/LightComponent.h"
#include "../Components/TextComponent.h"
#include "../Components/TilemapComponent.h"
#include "../Components/AnimationComponent.h"

#include "../core/utils/Profiler.h"
#include "../core/utils/utils.h"
#include "../core/utils/keycode.h"
#include "../core/utils/input.h"

#include "../core/types/vec2.h"

#include <lua.hpp>
#include <memory>
#include <cstring>

static bool luaDebugEnabled = false;
static bool old_state = false;

#define SET_KC(name)                                     \
    lua_pushinteger(L, static_cast<int>(Keycode::name)); \
    lua_setfield(L, -2, #name);

struct LuaObjectWrapper
{
    Object *obj;
};
#define LUA_OBJECT_MT "LuaObjectMeta"

struct LuaVector2
{
    float x, y;
};
#define LUA_VECTOR2_MT "LuaVector2Meta"

#define LUA_ANIMATION_MT "LuaAnimationMeta"

struct LuaAnimationWrapper
{
    AnimationComponent *comp;
};

ScriptComponent::ScriptComponent(Object *owner) : Component(owner), L(nullptr) {}
ScriptComponent::~ScriptComponent()
{
    if (L)
        lua_close(L);
}

void ScriptComponent::SetScriptPath(const std::string &path)
{
    scriptPath = path;
    ReloadScript();
}
const std::string &ScriptComponent::GetScriptPath() const { return scriptPath; }

void ScriptComponent::Hook(lua_State *L, lua_Debug *ar)
{
    if (!g_engineConfig.settings.profile_deep)
        return;

    // Quickly fetch component from Lua extraspace
    ScriptComponent *self = *reinterpret_cast<ScriptComponent **>(lua_getextraspace(L));
    if (!self || (ar->event != LUA_HOOKCALL && ar->event != LUA_HOOKRET))
        return;

    // Only fetch debug info if needed
    if (!lua_getinfo(L, "nS", ar))
        return;

    // Only build label if entering a function
    if (ar->event == LUA_HOOKCALL)
    {
        const char *name = ar->name;
        std::string label;

        if (name && *name)
        {
            label = name;
        }
        else if (ar->short_src && *ar->short_src)
        {
            label = GetFilenameFromPath(ar->short_src);
        }
        else
        {
            label = "unknown";
        }

        self->luaCallStack.emplace_back(label);
        profiler.BeginDeepSection(label);
    }
    else if (ar->event == LUA_HOOKRET && !self->luaCallStack.empty())
    {
        profiler.EndDeepSection();
        self->luaCallStack.pop_back();
    }
}

static int Lua_LogInfo(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Lua_LogInfo");
    Logger::LogInfo("[Lua] %s", lua_tostring(L, 1));
    return 0;
}
static int Lua_LogError(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Lua_LogError");
    Logger::LogError("[Lua] %s", lua_tostring(L, 1));
    return 0;
}
static int Lua_LogDebug(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Lua_LogDebug");
    Logger::LogDebug("[Lua] %s", lua_tostring(L, 1));
    return 0;
}
static int Lua_DebugLua(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Lua_DebugLua");
    luaDebugEnabled = lua_toboolean(L, 1);
    if (old_state != luaDebugEnabled)
        Logger::LogInfo("[Lua] DebugLua(%s)", luaDebugEnabled ? "true" : "false");
    old_state = luaDebugEnabled;
    return 0;
}

static Component *GetComponentByName(Object *obj, const std::string &type)
{
    PROFILE_DEEP_SCOPE("GetComponentByName");
    if (type == "SpriteComponent")
        return obj->GetComponent<SpriteComponent>().get();
    if (type == "CameraComponent")
        return obj->GetComponent<CameraComponent>().get();
    if (type == "LightComponent")
        return obj->GetComponent<LightComponent>().get();
    if (type == "TilemapComponent")
        return obj->GetComponent<TilemapComponent>().get();
    if (type == "TextComponent")
        return obj->GetComponent<TextComponent>().get();
    if (type == "ScriptComponent")
        return obj->GetComponent<ScriptComponent>().get();
    if (type == "AnimationComponent")
        return obj->GetComponent<AnimationComponent>().get();
    return nullptr;
}


static int Lua_GetMousePos(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Engine::GetMousePos");

    core::types::Vec2 m = Input::GetMousePosition();

    LuaVector2 *vec = (LuaVector2 *)lua_newuserdata(L, sizeof(LuaVector2));
    vec->x = m.x;
    vec->y = m.y;

    luaL_getmetatable(L, LUA_VECTOR2_MT);
    lua_setmetatable(L, -2);

    return 1;
}

static int Lua_Object_GetComponent(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Object::GetComponent");
    auto *wrapper = (LuaObjectWrapper *)luaL_checkudata(L, 1, LUA_OBJECT_MT);
    const char *type = luaL_checkstring(L, 2);
    if (strcmp(type, "AnimationComponent") == 0)
    {
        auto compPtr = wrapper->obj->GetComponent<AnimationComponent>();
        if (compPtr)
        {
            LuaAnimationWrapper *aw = (LuaAnimationWrapper *)lua_newuserdata(L, sizeof(LuaAnimationWrapper));
            aw->comp = compPtr.get();
            luaL_getmetatable(L, LUA_ANIMATION_MT);
            lua_setmetatable(L, -2);
            return 1;
        }
        lua_pushnil(L);
        return 1;
    }
    // fallback for other components
    Component *comp = GetComponentByName(wrapper->obj, type);
    lua_pushlightuserdata(L, comp ? comp : nullptr);
    return 1;
}

static int Lua_Object_GetPosition(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Object::GetPosition");
    auto *wrapper = (LuaObjectWrapper *)luaL_checkudata(L, 1, LUA_OBJECT_MT);
    glm::vec2 pos = wrapper->obj->GetLocalPosition();
    LuaVector2 *vec = (LuaVector2 *)lua_newuserdata(L, sizeof(LuaVector2));
    vec->x = pos.x;
    vec->y = pos.y;
    luaL_getmetatable(L, LUA_VECTOR2_MT);
    lua_setmetatable(L, -2);
    return 1;
}

static int Lua_Object_SetPosition(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Object::SetPosition");
    auto *wrapper = (LuaObjectWrapper *)luaL_checkudata(L, 1, LUA_OBJECT_MT);
    auto *vec = (LuaVector2 *)luaL_checkudata(L, 2, LUA_VECTOR2_MT);
    wrapper->obj->SetLocalPosition({vec->x, vec->y});
    return 0;
}

static int Lua_Object_Index(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Object::__index");
    const char *key = luaL_checkstring(L, 2);
    lua_getfield(L, lua_upvalueindex(1), key);
    return 1;
}

void RegisterObjectType(lua_State *L)
{
    PROFILE_DEEP_SCOPE("RegisterObjectType");

    luaL_newmetatable(L, LUA_OBJECT_MT);
    lua_newtable(L);
    lua_pushcfunction(L, Lua_Object_GetComponent);
    lua_setfield(L, -2, "GetComponent");
    lua_pushcfunction(L, Lua_Object_GetPosition);
    lua_setfield(L, -2, "GetPosition");
    lua_pushcfunction(L, Lua_Object_SetPosition);
    lua_setfield(L, -2, "SetPosition");
    lua_pushcclosure(L, Lua_Object_Index, 1);
    lua_setfield(L, -2, "__index");
    lua_pop(L, 1);
}

static void PushObject(lua_State *L, Object *obj)
{
    PROFILE_DEEP_SCOPE("PushObject");
    auto *wrapper = (LuaObjectWrapper *)lua_newuserdata(L, sizeof(LuaObjectWrapper));
    wrapper->obj = obj;
    luaL_getmetatable(L, LUA_OBJECT_MT);
    lua_setmetatable(L, -2);
}

static int Lua_GetObjectByTag(lua_State *L)
{
    PROFILE_DEEP_SCOPE("GetObjectByTag");
    const char *name = luaL_checkstring(L, 1);
    for (const auto &obj : objects)
    {
        auto found = FindByTagRecursive(obj, name);
        if (found)
        {
            PushObject(L, found.get());
            return 1;
        }
    }
    lua_pushnil(L);
    return 1;
}

// --- AnimationComponent Lua bindings ---
static AnimationComponent *CheckAnimationComponent(lua_State *L, int idx)
{
    void *ptr = lua_touserdata(L, idx);
    return static_cast<AnimationComponent *>(ptr);
}

static int Lua_Animation_GetCurrentFrame(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::GetCurrentFrame");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    lua_pushinteger(L, anim ? anim->GetCurrentFrame() : -1);
    return 1;
}

static int Lua_Animation_SetCurrentFrame(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::SetCurrentFrame");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    int frame = luaL_checkinteger(L, 2);
    if (anim)
        anim->SetFrame(frame);
    return 0;
}

static int Lua_Animation_IsPlaying(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::IsPlaying");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    lua_pushboolean(L, anim ? anim->IsPlaying() : false);
    return 1;
}

static int Lua_Animation_SetPlaying(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::SetPlaying");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    bool play = lua_toboolean(L, 2);
    if (anim)
        anim->SetPlaying(play);
    return 0;
}

static int Lua_Animation_GetStartFrame(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::GetStartFrame");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    lua_pushinteger(L, anim ? anim->GetStartFrame() : 0);
    return 1;
}

static int Lua_Animation_SetStartFrame(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::SetStartFrame");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    int start = luaL_checkinteger(L, 2);
    if (anim)
        anim->SetStartFrame(start);
    return 0;
}

static int Lua_Animation_GetEndFrame(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::GetEndFrame");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    lua_pushinteger(L, anim ? anim->GetEndFrame() : 0);
    return 1;
}

static int Lua_Animation_SetEndFrame(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Animation::SetEndFrame");
    AnimationComponent *anim = CheckAnimationComponent(L, 1);
    int end = luaL_checkinteger(L, 2);
    if (anim)
        anim->SetEndFrame(end);
    return 0;
}



static int Lua_Animation_Index(lua_State *L)
{
    // stack: [1]=userdata, [2]=key
    luaL_getmetatable(L, LUA_ANIMATION_MT);
    lua_pushvalue(L, 2);
    lua_rawget(L, -2);
    return 1;
}

static int keycode_loader(lua_State *L)
{
    lua_newtable(L);

    // Printable
    SET_KC(Space)
    SET_KC(Apostrophe)
    SET_KC(Comma)
    SET_KC(Minus)
    SET_KC(Period)
    SET_KC(Slash)
    SET_KC(Key0)
    SET_KC(Key1)
    SET_KC(Key2)
    SET_KC(Key3)
    SET_KC(Key4)
    SET_KC(Key5)
    SET_KC(Key6)
    SET_KC(Key7)
    SET_KC(Key8)
    SET_KC(Key9)
    SET_KC(Semicolon)
    SET_KC(Equal)
    SET_KC(A)
    SET_KC(B)
    SET_KC(C)
    SET_KC(D)
    SET_KC(E)
    SET_KC(F)
    SET_KC(G)
    SET_KC(H)
    SET_KC(I)
    SET_KC(J)
    SET_KC(K)
    SET_KC(L)
    SET_KC(M)
    SET_KC(N)
    SET_KC(O)
    SET_KC(P)
    SET_KC(Q)
    SET_KC(R)
    SET_KC(S)
    SET_KC(T)
    SET_KC(U)
    SET_KC(V)
    SET_KC(W)
    SET_KC(X)
    SET_KC(Y)
    SET_KC(Z)
    SET_KC(LeftBracket)
    SET_KC(Backslash)
    SET_KC(RightBracket)
    SET_KC(GraveAccent)
    SET_KC(World1)
    SET_KC(World2)

    // Function & Navigation
    SET_KC(Escape)
    SET_KC(Enter)
    SET_KC(Tab)
    SET_KC(Backspace)
    SET_KC(Insert)
    SET_KC(Delete)
    SET_KC(Right)
    SET_KC(Left)
    SET_KC(Down)
    SET_KC(Up)
    SET_KC(PageUp)
    SET_KC(PageDown)
    SET_KC(Home)
    SET_KC(End)
    SET_KC(CapsLock)
    SET_KC(ScrollLock)
    SET_KC(NumLock)
    SET_KC(PrintScreen)
    SET_KC(Pause)
    SET_KC(F1)
    SET_KC(F2)
    SET_KC(F3)
    SET_KC(F4)
    SET_KC(F5)
    SET_KC(F6)
    SET_KC(F7)
    SET_KC(F8)
    SET_KC(F9)
    SET_KC(F10)
    SET_KC(F11)
    SET_KC(F12)
    SET_KC(F13)
    SET_KC(F14)
    SET_KC(F15)
    SET_KC(F16)
    SET_KC(F17)
    SET_KC(F18)
    SET_KC(F19)
    SET_KC(F20)
    SET_KC(F21)
    SET_KC(F22)
    SET_KC(F23)
    SET_KC(F24)
    SET_KC(F25)

    // Keypad
    SET_KC(KP0)
    SET_KC(KP1)
    SET_KC(KP2)
    SET_KC(KP3)
    SET_KC(KP4)
    SET_KC(KP5)
    SET_KC(KP6)
    SET_KC(KP7)
    SET_KC(KP8)
    SET_KC(KP9)
    SET_KC(KPDecimal)
    SET_KC(KPDivide)
    SET_KC(KPMultiply)
    SET_KC(KPSubtract)
    SET_KC(KPAdd)
    SET_KC(KPEnter)
    SET_KC(KPEqual)

    // Modifiers
    SET_KC(LeftShift)
    SET_KC(LeftControl)
    SET_KC(LeftAlt)
    SET_KC(LeftSuper)
    SET_KC(RightShift)
    SET_KC(RightControl)
    SET_KC(RightAlt)
    SET_KC(RightSuper)
    SET_KC(Menu)

#undef SET_KC

    lua_setglobal(L, "Keycode");

    return 1;
}

static int Lua_KeyDown(lua_State *L)
{
    // match your other deep‐profile scopes
    PROFILE_DEEP_SCOPE("Engine::KeyDown");

    // grab the integer keycode from Lua
    lua_Integer code = luaL_checkinteger(L, 1);

    // cast into your strongly‐typed enum and query
    bool down = Input::IsKeyDown(static_cast<Keycode>(code));

    // return a Lua boolean
    lua_pushboolean(L, down);
    return 1;
}

static int Lua_Vector2_New(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Vector2()");
    LuaVector2 *vec = static_cast<LuaVector2 *>(lua_newuserdata(L, sizeof(LuaVector2)));
    int nargs = lua_gettop(L);
    vec->x = nargs >= 1 ? static_cast<float>(lua_tonumber(L, 1)) : 0.0f;
    vec->y = nargs >= 2 ? static_cast<float>(lua_tonumber(L, 2)) : 0.0f;
    luaL_setmetatable(L, LUA_VECTOR2_MT);
    return 1;
}

static int Lua_Vector2_Index(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Vector2::index");
    auto *vec = static_cast<LuaVector2 *>(luaL_checkudata(L, 1, LUA_VECTOR2_MT));
    const char *key = lua_tostring(L, 2); // safer and faster than luaL_checkstring for profiling edge cases

    // Fast string comparison
    if (key[0] == 'x' && key[1] == '\0')
    {
        lua_pushnumber(L, vec->x);
    }
    else if (key[0] == 'y' && key[1] == '\0')
    {
        lua_pushnumber(L, vec->y);
    }
    else
    {
        lua_pushnil(L);
    }
    return 1;
}

static int Lua_Vector2_NewIndex(lua_State *L)
{
    PROFILE_DEEP_SCOPE("Vector2::newindex");
    auto *vec = static_cast<LuaVector2 *>(luaL_checkudata(L, 1, LUA_VECTOR2_MT));
    const char *key = lua_tostring(L, 2);
    float value = static_cast<float>(luaL_checknumber(L, 3));

    if (key[0] == 'x' && key[1] == '\0')
    {
        vec->x = value;
    }
    else if (key[0] == 'y' && key[1] == '\0')
    {
        vec->y = value;
    }
    return 0;
}

void RegisterVector2Type(lua_State *L)
{
    PROFILE_DEEP_SCOPE("RegisterVector2Type");

    luaL_newmetatable(L, LUA_VECTOR2_MT);
    lua_pushcfunction(L, Lua_Vector2_Index);
    lua_setfield(L, -2, "__index");

    lua_pushcfunction(L, Lua_Vector2_NewIndex);
    lua_setfield(L, -2, "__newindex");

    lua_pop(L, 1);

    lua_pushcfunction(L, Lua_Vector2_New);
    lua_setglobal(L, "Vector2");
}

// Register the AnimationComponent metatable
void RegisterAnimationType(lua_State *L)
{
    PROFILE_DEEP_SCOPE("RegisterAnimationType");
    luaL_newmetatable(L, LUA_ANIMATION_MT);
    // methods
    lua_pushcfunction(L, Lua_Animation_GetCurrentFrame);
    lua_setfield(L, -2, "GetCurrentFrame");
    lua_pushcfunction(L, Lua_Animation_SetCurrentFrame);
    lua_setfield(L, -2, "SetCurrentFrame");
    lua_pushcfunction(L, Lua_Animation_IsPlaying);
    lua_setfield(L, -2, "IsPlaying");
    lua_pushcfunction(L, Lua_Animation_SetPlaying);
    lua_setfield(L, -2, "SetPlaying");
    lua_pushcfunction(L, Lua_Animation_GetStartFrame);
    lua_setfield(L, -2, "GetStartFrame");
    lua_pushcfunction(L, Lua_Animation_SetStartFrame);
    lua_setfield(L, -2, "SetStartFrame");
    lua_pushcfunction(L, Lua_Animation_GetEndFrame);
    lua_setfield(L, -2, "GetEndFrame");
    lua_pushcfunction(L, Lua_Animation_SetEndFrame);
    lua_setfield(L, -2, "SetEndFrame");
    
    // __index
    lua_pushcfunction(L, Lua_Animation_Index);
    lua_setfield(L, -2, "__index");
    lua_pop(L, 1);
}

void ScriptComponent::RegisterEngineBindings()
{
    PROFILE_DEEP_SCOPE("RegisterEngineBindings");

    lua_newtable(L);
    lua_pushcfunction(L, Lua_LogInfo);
    lua_setfield(L, -2, "LogInfo");
    lua_pushcfunction(L, Lua_LogError);
    lua_setfield(L, -2, "LogError");
    lua_pushcfunction(L, Lua_LogDebug);
    lua_setfield(L, -2, "LogDebug");
    lua_pushcfunction(L, Lua_GetObjectByTag);
    lua_setfield(L, -2, "GetObjectByTag");
    lua_pushcfunction(L, Lua_DebugLua);
    lua_setfield(L, -2, "DebugLua");
    lua_pushcfunction(L, Lua_KeyDown);
    lua_setfield(L, -2, "KeyDown");

    lua_pushcfunction(L, Lua_GetMousePos);
    lua_setfield(L, -2, "GetMousePos");

    lua_setglobal(L, "Engine");


}

void ScriptComponent::ReloadScript()
{
    PROFILE_DEEP_SCOPE("ScriptComponent::ReloadScript");
    if (scriptPath.empty())
        return;
    if (L)
        lua_close(L);

    L = luaL_newstate();
    *(ScriptComponent **)lua_getextraspace(L) = this;
    luaL_openlibs(L);
    lua_sethook(L, Hook, LUA_MASKCALL | LUA_MASKRET, 0);

    RegisterObjectType(L);
    RegisterVector2Type(L);
    RegisterAnimationType(L);
    RegisterEngineBindings();

    keycode_loader(L);

    Logger::LogVerbose("[Lua] Loading Script from file.");
    if (luaL_dofile(L, scriptPath.c_str()))
    {
        Logger::LogError("[Lua] %s", lua_tostring(L, -1));
        RecoverableError("Failed to load Lua script: " + scriptPath, Create::Exceptions::ComponentLoad).Handle();
        return;
    }

    if (luaDebugEnabled)
        Logger::LogVerbose("[Lua][call] OnInit()");
    lua_getglobal(L, "OnInit");
    if (lua_isfunction(L, -1))
    {
        if (lua_pcall(L, 0, 0, 0) != LUA_OK)
        {
            Logger::LogError("[Lua] %s", lua_tostring(L, -1));
            RecoverableError("OnInit failed: " + scriptPath, Create::Exceptions::ComponentLoad).Handle();
        }
    }
    else
        lua_pop(L, 1);
}

void ScriptComponent::OnUpdate(float dt)
{
    PROFILE_DEEP_SCOPE("ScriptComponent::OnUpdate");

    if (!L)
        return;
    lua_getglobal(L, "OnUpdate");
    if (lua_isfunction(L, -1))
    {
        lua_pushnumber(L, dt);
        if (lua_pcall(L, 1, 0, 0) != LUA_OK)
        {
            Logger::LogError("[Lua] %s", lua_tostring(L, -1));
            RecoverableError("OnUpdate failed in: " + scriptPath, Create::Exceptions::ComponentLoad).Handle();
        }
    }
    else
        lua_pop(L, 1);
}

void ScriptComponent::Save(YAML::Emitter &out) const
{
    out << YAML::BeginMap;
    out << YAML::Key << "type" << YAML::Value << "ScriptComponent";
    out << YAML::Key << "scriptPath" << YAML::Value << scriptPath;
    out << YAML::EndMap;
}

void ScriptComponent::Load(const YAML::Node &node)
{
    try
    {
        if (!node["scriptPath"])
        {
            RecoverableError("Missing 'scriptPath' in ScriptComponent", Create::Exceptions::MissingField).Handle();
            return;
        }

        scriptPath = node["scriptPath"].as<std::string>();
        ReloadScript();
    }
    catch (const YAML::Exception &e)
    {
        RecoverableError("YAML error in ScriptComponent::Load: " + std::string(e.what()), Create::Exceptions::ComponentLoad).Handle();
    }
}