/**
 * @file lua_test_stubs.cpp
 * @brief Stub implementations of Lua API registration functions for standalone tests.
 *
 * These stubs provide minimal implementations that work with the
 * flecs stubs and ogre_stub.h defined in the test files.
 * They are used instead of the real Lua API .cpp files which
 * require the full OGRE SDK.
 *
 * The stubs maintain state (entity IDs, names, components) so that
 * the tests can verify correct behavior.
 */

#include "ogre_stub.h"
#include "../components/EventParams.hpp"
#include <lua.hpp>
#include <cstdio>
#include <cassert>
#include <string>
#include <unordered_map>
#include <vector>
#include <map>
#include <algorithm>

// ---------------------------------------------------------------------------
// Shared component storage (used by both entity and component API stubs)
// ---------------------------------------------------------------------------

namespace editScene
{

// A value can be a number, string, boolean, array of numbers, array of strings,
// or a nested table (map of string->value)
struct ComponentFieldValue {
	enum Type { NIL, NUMBER, STRING, BOOLEAN, NUM_ARRAY, STR_ARRAY, TABLE };
	Type type = NIL;
	double numVal = 0;
	std::string strVal;
	bool boolVal = false;
	std::vector<double> numArr;
	std::vector<std::string> strArr;
	std::map<std::string, ComponentFieldValue> tableVal;
};

using ComponentData = std::unordered_map<std::string, ComponentFieldValue>;
std::unordered_map<int, std::unordered_map<std::string, ComponentData> >
	s_components;

// ---------------------------------------------------------------------------
// Stub: LuaGameModeApi
// ---------------------------------------------------------------------------

// Game mode state for stubs
static int s_stubGameMode = 0; // 0 = editor, 1 = game
static int s_stubPlayState = 0; // 0 = menu, 1 = playing, 2 = paused

void registerLuaGameModeApi(lua_State *L)
{
	// Get or create the "ecs" global table
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	// is_editor_mode
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, s_stubGameMode == 0 ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "is_editor_mode");

	// is_game_mode
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, s_stubGameMode == 1 ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "is_game_mode");

	// is_game_playing
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(
			L,
			(s_stubGameMode == 1 && s_stubPlayState == 1) ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "is_game_playing");

	// is_game_menu
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(
			L,
			(s_stubGameMode == 1 && s_stubPlayState == 0) ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "is_game_menu");

	// is_game_paused
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(
			L,
			(s_stubGameMode == 1 && s_stubPlayState == 2) ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "is_game_paused");

	// get_game_mode
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushstring(L, s_stubGameMode == 0 ? "editor" : "game");
		return 1;
	});
	lua_setfield(L, -2, "get_game_mode");

	// get_game_play_state
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *state = "menu";
		if (s_stubPlayState == 1)
			state = "playing";
		else if (s_stubPlayState == 2)
			state = "paused";
		lua_pushstring(L, state);
		return 1;
	});
	lua_setfield(L, -2, "get_game_play_state");

	// debug_crash (stub: just logs, does not actually crash)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *msg = lua_tostring(L, 1);
		if (!msg)
			msg = "debug_crash called";
		fprintf(stderr, "Lua debug_crash (stub): %s\n", msg);
		// In test stubs, we do NOT actually abort/crash
		return 0;
	});
	lua_setfield(L, -2, "debug_crash");

	lua_setglobal(L, "ecs");
}

// Stub: LuaDialogueApi (DialogueSystem singleton)
void registerLuaDialogueApi(lua_State *L)
{
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	lua_newtable(L);

	// show(text, choices, speaker)
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "show");

	// hide()
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "hide");

	// is_active()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "is_active");

	// select_choice(index)
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "select_choice");

	// progress()
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "progress");

	// get_settings()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		lua_pushstring(L, "Jupiteroid-Regular.ttf");
		lua_setfield(L, -2, "font_name");
		lua_pushnumber(L, 24.0);
		lua_setfield(L, -2, "font_size");
		lua_pushnumber(L, 20.0);
		lua_setfield(L, -2, "speaker_font_size");
		lua_pushnumber(L, 0.85);
		lua_setfield(L, -2, "background_opacity");
		lua_pushnumber(L, 0.25);
		lua_setfield(L, -2, "box_height_fraction");
		lua_pushnumber(L, 0.75);
		lua_setfield(L, -2, "box_position_fraction");
		return 1;
	});
	lua_setfield(L, -2, "get_settings");

	// set_settings(table)
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "set_settings");

	// save_settings(path)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, 1);
		return 1;
	});
	lua_setfield(L, -2, "save_settings");

	// load_settings(path)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, 1);
		return 1;
	});
	lua_setfield(L, -2, "load_settings");

	lua_setfield(L, -2, "dialogue");

	lua_setglobal(L, "ecs");
}

// ---------------------------------------------------------------------------
// Stub: LuaCharacterClassApi
// ---------------------------------------------------------------------------

void registerLuaCharacterClassApi(lua_State *L)
{
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	lua_newtable(L);

	// get_class_names()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		return 1;
	});
	lua_setfield(L, -2, "get_class_names");

	// get_stat_names()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		return 1;
	});
	lua_setfield(L, -2, "get_stat_names");

	// get_skill_names()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		return 1;
	});
	lua_setfield(L, -2, "get_skill_names");

	// get_need_names()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		return 1;
	});
	lua_setfield(L, -2, "get_need_names");

	// get_class(name)
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "get_class");

	// get_stat_kind(name)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *name = lua_tostring(L, 1);
		if (!name) {
			lua_pushstring(L, "unknown");
			return 1;
		}
		lua_pushstring(L, "unknown");
		return 1;
	});
	lua_setfield(L, -2, "get_stat_kind");

	// get_level(entity_id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_level");

	// get_xp(entity_id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_xp");

	// add_xp(entity_id, amount)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "add_xp");

	// get_stat(entity_id, stat_name)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_stat");

	// get_skill(entity_id, skill_name)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_skill");

	// get_need(entity_id, need_name)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_need");

	// get_available_points(entity_id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_available_points");

	// set_need(entity_id, need_name, value)
	lua_pushcfunction(L, [](lua_State *L) -> int { return 0; });
	lua_setfield(L, -2, "set_need");

	// get_pool_current(entity_id, pool_name)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_pool_current");

	// get_pool_max(entity_id, pool_name)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushinteger(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "get_pool_max");

	// set_pool_current(entity_id, pool_name, value)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushboolean(L, 0);
		return 1;
	});
	lua_setfield(L, -2, "set_pool_current");

	lua_setfield(L, -2, "character_class");

	lua_setglobal(L, "ecs");
}

// ---------------------------------------------------------------------------
// Stub: LuaItemApi
// ---------------------------------------------------------------------------

struct StubItemDef {
	std::string itemId;
	std::string itemName;
	std::string itemType;
	int maxStackSize = 99;
	float weight = 0.1f;
	int value = 1;
	std::string useActionName;
	bool unique = false;
};

static std::unordered_map<std::string, StubItemDef> s_stubItems;

struct StubInvSlot {
	std::string itemId;
	int stackSize = 0;
};

static std::unordered_map<int, std::vector<StubInvSlot> > s_stubInventories;
static std::unordered_map<std::string, std::vector<StubInvSlot> >
	s_stubContainerStates;

void registerLuaItemApi(lua_State *L)
{
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	// ecs.items
	lua_newtable(L);

	// items.register
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 2 || !lua_isstring(L, 1) ||
		    !lua_istable(L, 2))
			return 0;
		std::string itemId = lua_tostring(L, 1);
		StubItemDef def;
		def.itemId = itemId;
		if (lua_getfield(L, 2, "itemName"), lua_isstring(L, -1))
			def.itemName = lua_tostring(L, -1);
		lua_pop(L, 1);
		if (lua_getfield(L, 2, "itemType"), lua_isstring(L, -1))
			def.itemType = lua_tostring(L, -1);
		lua_pop(L, 1);
		if (lua_getfield(L, 2, "maxStackSize"), lua_isnumber(L, -1))
			def.maxStackSize = (int)lua_tointeger(L, -1);
		lua_pop(L, 1);
		if (lua_getfield(L, 2, "weight"), lua_isnumber(L, -1))
			def.weight = (float)lua_tonumber(L, -1);
		lua_pop(L, 1);
		if (lua_getfield(L, 2, "value"), lua_isnumber(L, -1))
			def.value = (int)lua_tointeger(L, -1);
		lua_pop(L, 1);
		if (lua_getfield(L, 2, "useActionName"), lua_isstring(L, -1))
			def.useActionName = lua_tostring(L, -1);
		lua_pop(L, 1);
		if (lua_getfield(L, 2, "unique"), lua_isboolean(L, -1))
			def.unique = lua_toboolean(L, -1) != 0;
		lua_pop(L, 1);
		s_stubItems[itemId] = def;
		return 0;
	});
	lua_setfield(L, -2, "register");

	// items.find
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
			return 0;
		auto it = s_stubItems.find(lua_tostring(L, 1));
		if (it == s_stubItems.end())
			return 0;
		lua_newtable(L);
		lua_pushstring(L, it->second.itemId.c_str());
		lua_setfield(L, -2, "itemId");
		lua_pushstring(L, it->second.itemName.c_str());
		lua_setfield(L, -2, "itemName");
		lua_pushstring(L, it->second.itemType.c_str());
		lua_setfield(L, -2, "itemType");
		lua_pushinteger(L, it->second.maxStackSize);
		lua_setfield(L, -2, "maxStackSize");
		lua_pushnumber(L, it->second.weight);
		lua_setfield(L, -2, "weight");
		lua_pushinteger(L, it->second.value);
		lua_setfield(L, -2, "value");
		lua_pushstring(L, it->second.useActionName.c_str());
		lua_setfield(L, -2, "useActionName");
		lua_pushboolean(L, it->second.unique ? 1 : 0);
		lua_setfield(L, -2, "unique");
		return 1;
	});
	lua_setfield(L, -2, "find");

	// items.list
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		int idx = 1;
		for (const auto &pair : s_stubItems) {
			lua_pushstring(L, pair.first.c_str());
			lua_rawseti(L, -2, idx++);
		}
		return 1;
	});
	lua_setfield(L, -2, "list");

	// items.is_unique
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
			return 0;
		auto it = s_stubItems.find(lua_tostring(L, 1));
		lua_pushboolean(
			L,
			(it != s_stubItems.end() && it->second.unique) ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "is_unique");

	lua_setfield(L, -2, "items");

	// ecs.inventory
	lua_newtable(L);

	// inventory.add
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 3 || !lua_isnumber(L, 1) ||
		    !lua_isstring(L, 2) || !lua_isnumber(L, 3))
			return 0;
		int entityId = (int)lua_tointeger(L, 1);
		std::string itemId = lua_tostring(L, 2);
		int count = (int)lua_tointeger(L, 3);
		auto &inv = s_stubInventories[entityId];
		bool found = false;
		for (auto &slot : inv) {
			if (slot.itemId == itemId) {
				slot.stackSize += count;
				found = true;
				break;
			}
		}
		if (!found)
			inv.push_back({ itemId, count });
		lua_pushboolean(L, 1);
		return 1;
	});
	lua_setfield(L, -2, "add");

	// inventory.remove
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 3 || !lua_isnumber(L, 1) ||
		    !lua_isstring(L, 2) || !lua_isnumber(L, 3))
			return 0;
		int entityId = (int)lua_tointeger(L, 1);
		std::string itemId = lua_tostring(L, 2);
		int count = (int)lua_tointeger(L, 3);
		int removed = 0;
		auto it = s_stubInventories.find(entityId);
		if (it != s_stubInventories.end()) {
			for (auto &slot : it->second) {
				if (slot.itemId == itemId) {
					int rem =
						std::min(count, slot.stackSize);
					slot.stackSize -= rem;
					count -= rem;
					removed += rem;
				}
			}
			it->second.erase(
				std::remove_if(it->second.begin(),
					       it->second.end(),
					       [](const StubInvSlot &s) {
						       return s.stackSize <= 0;
					       }),
				it->second.end());
		}
		lua_pushinteger(L, removed);
		return 1;
	});
	lua_setfield(L, -2, "remove");

	// inventory.has
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 2 || !lua_isnumber(L, 1) ||
		    !lua_isstring(L, 2))
			return 0;
		int entityId = (int)lua_tointeger(L, 1);
		std::string itemId = lua_tostring(L, 2);
		bool has = false;
		auto it = s_stubInventories.find(entityId);
		if (it != s_stubInventories.end()) {
			for (const auto &slot : it->second) {
				if (slot.itemId == itemId &&
				    slot.stackSize > 0) {
					has = true;
					break;
				}
			}
		}
		lua_pushboolean(L, has ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "has");

	// inventory.count
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 2 || !lua_isnumber(L, 1) ||
		    !lua_isstring(L, 2))
			return 0;
		int entityId = (int)lua_tointeger(L, 1);
		std::string itemId = lua_tostring(L, 2);
		int count = 0;
		auto it = s_stubInventories.find(entityId);
		if (it != s_stubInventories.end()) {
			for (const auto &slot : it->second) {
				if (slot.itemId == itemId)
					count += slot.stackSize;
			}
		}
		lua_pushinteger(L, count);
		return 1;
	});
	lua_setfield(L, -2, "count");

	// inventory.get_slots
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 1 || !lua_isnumber(L, 1))
			return 0;
		int entityId = (int)lua_tointeger(L, 1);
		lua_newtable(L);
		auto it = s_stubInventories.find(entityId);
		if (it != s_stubInventories.end()) {
			int idx = 1;
			for (const auto &slot : it->second) {
				if (slot.stackSize <= 0)
					continue;
				lua_newtable(L);
				lua_pushstring(L, slot.itemId.c_str());
				lua_setfield(L, -2, "itemId");
				lua_pushinteger(L, slot.stackSize);
				lua_setfield(L, -2, "stackSize");
				lua_rawseti(L, -2, idx++);
			}
		}
		return 1;
	});
	lua_setfield(L, -2, "get_slots");

	// inventory.set_slots
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 2 || !lua_isnumber(L, 1) ||
		    !lua_istable(L, 2))
			return 0;
		int entityId = (int)lua_tointeger(L, 1);
		std::vector<StubInvSlot> slots;
		int len = (int)lua_rawlen(L, 2);
		for (int i = 1; i <= len; i++) {
			lua_rawgeti(L, 2, i);
			if (lua_istable(L, -1)) {
				StubInvSlot slot;
				if (lua_getfield(L, -1, "itemId"),
				    lua_isstring(L, -1))
					slot.itemId = lua_tostring(L, -1);
				lua_pop(L, 1);
				if (lua_getfield(L, -1, "stackSize"),
				    lua_isnumber(L, -1))
					slot.stackSize =
						(int)lua_tointeger(L, -1);
				lua_pop(L, 1);
				if (!slot.itemId.empty())
					slots.push_back(slot);
			}
			lua_pop(L, 1);
		}
		s_stubInventories[entityId] = slots;
		return 0;
	});
	lua_setfield(L, -2, "set_slots");

	lua_setfield(L, -2, "inventory");

	// ecs.container
	lua_newtable(L);

	// container.get_state
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
			return 0;
		std::string containerId = lua_tostring(L, 1);
		lua_newtable(L);
		auto it = s_stubContainerStates.find(containerId);
		if (it != s_stubContainerStates.end()) {
			int idx = 1;
			for (const auto &slot : it->second) {
				lua_newtable(L);
				lua_pushstring(L, slot.itemId.c_str());
				lua_setfield(L, -2, "itemId");
				lua_pushinteger(L, slot.stackSize);
				lua_setfield(L, -2, "stackSize");
				lua_rawseti(L, -2, idx++);
			}
		}
		return 1;
	});
	lua_setfield(L, -2, "get_state");

	// container.set_state
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 2 || !lua_isstring(L, 1) ||
		    !lua_istable(L, 2))
			return 0;
		std::string containerId = lua_tostring(L, 1);
		std::vector<StubInvSlot> slots;
		int len = (int)lua_rawlen(L, 2);
		for (int i = 1; i <= len; i++) {
			lua_rawgeti(L, 2, i);
			if (lua_istable(L, -1)) {
				StubInvSlot slot;
				if (lua_getfield(L, -1, "itemId"),
				    lua_isstring(L, -1))
					slot.itemId = lua_tostring(L, -1);
				lua_pop(L, 1);
				if (lua_getfield(L, -1, "stackSize"),
				    lua_isnumber(L, -1))
					slot.stackSize =
						(int)lua_tointeger(L, -1);
				lua_pop(L, 1);
				if (!slot.itemId.empty())
					slots.push_back(slot);
			}
			lua_pop(L, 1);
		}
		s_stubContainerStates[containerId] = slots;
		return 0;
	});
	lua_setfield(L, -2, "set_state");

	// container.clear_state
	lua_pushcfunction(L, [](lua_State *L) -> int {
		if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
			return 0;
		s_stubContainerStates.erase(lua_tostring(L, 1));
		return 0;
	});
	lua_setfield(L, -2, "clear_state");

	lua_setfield(L, -2, "container");

	lua_setglobal(L, "ecs");
}

} // namespace editScene

// ---------------------------------------------------------------------------
// Stub: LuaCharacterApi
// ---------------------------------------------------------------------------

namespace editScene
{

struct StubCharacter {
	uint64_t id = 0;
	std::string firstName;
	std::string lastName;
	std::string sex = "male";
	bool persistent = true;
	uint64_t pregnantByFatherId = 0;
	float pregnancyProgress = 0.0f;
	float pregnancyMaxProgress = 0.0f;
	bool spawned = false;
	int entityId = -1;
};

static std::unordered_map<uint64_t, StubCharacter> s_stubCharacters;
static std::unordered_map<uint64_t, std::vector<uint64_t> > s_stubParents;
static std::unordered_map<uint64_t, std::vector<uint64_t> > s_stubChildren;
static uint64_t s_stubNextCharId = 1;
static int s_stubNextEntityId = 5000;

void clearStubCharacters()
{
	s_stubCharacters.clear();
	s_stubParents.clear();
	s_stubChildren.clear();
	s_stubNextCharId = 1;
	s_stubNextEntityId = 5000;
}

void registerLuaCharacterApi(lua_State *L)
{
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	lua_newtable(L); // ecs.character

	// create(firstName, lastName, templatePath, persistent)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *fn = lua_tostring(L, 1);
		const char *ln = lua_tostring(L, 2);
		bool persistent = true;
		if (lua_gettop(L) >= 4)
			persistent = lua_toboolean(L, 4) != 0;
		uint64_t id = s_stubNextCharId++;
		StubCharacter c;
		c.id = id;
		c.firstName = fn ? fn : "";
		c.lastName = ln ? ln : "";
		c.persistent = persistent;
		s_stubCharacters[id] = c;
		lua_pushinteger(L, static_cast<lua_Integer>(id));
		return 1;
	});
	lua_setfield(L, -2, "create");

	// delete(id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
		s_stubCharacters.erase(id);
		return 0;
	});
	lua_setfield(L, -2, "delete");

	// find(id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(id);
		if (it == s_stubCharacters.end()) {
			lua_pushnil(L);
			return 1;
		}
		lua_newtable(L);
		lua_pushinteger(L, static_cast<lua_Integer>(it->second.id));
		lua_setfield(L, -2, "id");
		lua_pushstring(L, it->second.firstName.c_str());
		lua_setfield(L, -2, "firstName");
		lua_pushstring(L, it->second.lastName.c_str());
		lua_setfield(L, -2, "lastName");
		lua_pushstring(L, it->second.sex.c_str());
		lua_setfield(L, -2, "sex");
		lua_pushboolean(L, it->second.persistent ? 1 : 0);
		lua_setfield(L, -2, "persistent");
		lua_pushinteger(L, static_cast<lua_Integer>(
					   it->second.pregnantByFatherId));
		lua_setfield(L, -2, "pregnantByFatherId");
		lua_pushnumber(L, it->second.pregnancyProgress);
		lua_setfield(L, -2, "pregnancyProgress");
		lua_pushnumber(L, it->second.pregnancyMaxProgress);
		lua_setfield(L, -2, "pregnancyMaxProgress");
		return 1;
	});
	lua_setfield(L, -2, "find");

	// get_all()
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		int idx = 1;
		for (auto &pair : s_stubCharacters) {
			lua_pushinteger(L,
					static_cast<lua_Integer>(pair.first));
			lua_rawseti(L, -2, idx++);
		}
		return 1;
	});
	lua_setfield(L, -2, "get_all");

	// set_name(id, firstName, lastName)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(id);
		if (it != s_stubCharacters.end()) {
			const char *fn = lua_tostring(L, 2);
			const char *ln = lua_tostring(L, 3);
			if (fn)
				it->second.firstName = fn;
			if (ln)
				it->second.lastName = ln;
		}
		return 0;
	});
	lua_setfield(L, -2, "set_name");

	// spawn(id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(id);
		if (it == s_stubCharacters.end()) {
			lua_pushnil(L);
			return 1;
		}
		it->second.spawned = true;
		it->second.entityId = s_stubNextEntityId++;
		lua_pushinteger(L, it->second.entityId);
		return 1;
	});
	lua_setfield(L, -2, "spawn");

	// despawn(id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(id);
		if (it != s_stubCharacters.end()) {
			it->second.spawned = false;
			lua_pushboolean(L, 1);
		} else {
			lua_pushboolean(L, 0);
		}
		return 1;
	});
	lua_setfield(L, -2, "despawn");

	// is_spawned(id)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t id = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(id);
		lua_pushboolean(L, (it != s_stubCharacters.end() &&
				    it->second.spawned) ?
					   1 :
					   0);
		return 1;
	});
	lua_setfield(L, -2, "is_spawned");

	// conceive(motherId, fatherId)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t motherId = static_cast<uint64_t>(lua_tointeger(L, 1));
		uint64_t fatherId = static_cast<uint64_t>(lua_tointeger(L, 2));
		auto it = s_stubCharacters.find(motherId);
		if (it == s_stubCharacters.end()) {
			lua_pushboolean(L, 0);
			return 1;
		}
		it->second.pregnantByFatherId = fatherId;
		it->second.pregnancyProgress = 0.0f;
		it->second.pregnancyMaxProgress = 300.0f;
		lua_pushboolean(L, 1);
		return 1;
	});
	lua_setfield(L, -2, "conceive");

	// abort_pregnancy(motherId)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t motherId = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(motherId);
		if (it != s_stubCharacters.end()) {
			it->second.pregnantByFatherId = 0;
			it->second.pregnancyProgress = 0.0f;
			it->second.pregnancyMaxProgress = 0.0f;
		}
		return 0;
	});
	lua_setfield(L, -2, "abort_pregnancy");

	// is_pregnant(motherId)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t motherId = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(motherId);
		lua_pushboolean(L, (it != s_stubCharacters.end() &&
				    it->second.pregnantByFatherId != 0) ?
					   1 :
					   0);
		return 1;
	});
	lua_setfield(L, -2, "is_pregnant");

	// get_pregnancy_progress(motherId)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t motherId = static_cast<uint64_t>(lua_tointeger(L, 1));
		auto it = s_stubCharacters.find(motherId);
		if (it == s_stubCharacters.end() ||
		    it->second.pregnantByFatherId == 0) {
			lua_pushnil(L);
			return 1;
		}
		lua_newtable(L);
		lua_pushnumber(L, it->second.pregnancyProgress);
		lua_setfield(L, -2, "progress");
		lua_pushnumber(L, it->second.pregnancyMaxProgress);
		lua_setfield(L, -2, "maxProgress");
		lua_pushnumber(L,
			       it->second.pregnancyMaxProgress > 0.0f ?
				       it->second.pregnancyProgress /
					       it->second.pregnancyMaxProgress :
				       0.0f);
		lua_setfield(L, -2, "ratio");
		return 1;
	});
	lua_setfield(L, -2, "get_pregnancy_progress");

	// create_child(parentA, parentB)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t parentA = static_cast<uint64_t>(lua_tointeger(L, 1));
		uint64_t parentB = static_cast<uint64_t>(lua_tointeger(L, 2));
		uint64_t childId = s_stubNextCharId++;
		StubCharacter c;
		c.id = childId;
		c.firstName = "Baby";
		c.lastName = "Smith";
		c.sex = "female";
		c.persistent = false;
		s_stubCharacters[childId] = c;
		auto &parents = s_stubParents[childId];
		parents.clear();
		parents.push_back(parentA);
		parents.push_back(parentB);
		s_stubChildren[parentA].push_back(childId);
		s_stubChildren[parentB].push_back(childId);
		lua_pushinteger(L, static_cast<lua_Integer>(childId));
		return 1;
	});
	lua_setfield(L, -2, "create_child");

	// get_parents(childId)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t childId = static_cast<uint64_t>(lua_tointeger(L, 1));
		lua_newtable(L);
		auto it = s_stubParents.find(childId);
		if (it != s_stubParents.end()) {
			for (size_t i = 0; i < it->second.size(); i++) {
				lua_pushinteger(L, static_cast<lua_Integer>(
							   it->second[i]));
				lua_rawseti(L, -2, static_cast<int>(i + 1));
			}
		}
		return 1;
	});
	lua_setfield(L, -2, "get_parents");

	// get_children(parentId)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		uint64_t parentId = static_cast<uint64_t>(lua_tointeger(L, 1));
		lua_newtable(L);
		auto it = s_stubChildren.find(parentId);
		if (it != s_stubChildren.end()) {
			for (size_t i = 0; i < it->second.size(); i++) {
				lua_pushinteger(L, static_cast<lua_Integer>(
							   it->second[i]));
				lua_rawseti(L, -2, static_cast<int>(i + 1));
			}
		}
		return 1;
	});
	lua_setfield(L, -2, "get_children");

	lua_setfield(L, -2, "character"); // ecs.character = { ... }
	lua_setglobal(L, "ecs");
}

} // namespace editScene

// ---------------------------------------------------------------------------
// Stub: LuaSaveLoadApi
// ---------------------------------------------------------------------------

namespace editScene
{

// Save/load callback storage
static std::unordered_map<std::string, int> s_stubSaveCallbacks;
static std::unordered_map<std::string, int> s_stubLoadCallbacks;

void registerLuaSaveLoadApi(lua_State *L)
{
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	// register_save_callback(name, function)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *name = luaL_checkstring(L, 1);
		luaL_checktype(L, 2, LUA_TFUNCTION);

		// Unregister existing callback with same name
		auto it = s_stubSaveCallbacks.find(name);
		if (it != s_stubSaveCallbacks.end()) {
			luaL_unref(L, LUA_REGISTRYINDEX, it->second);
		}

		// Store new callback in registry
		lua_pushvalue(L, 2);
		int ref = luaL_ref(L, LUA_REGISTRYINDEX);
		s_stubSaveCallbacks[name] = ref;
		return 0;
	});
	lua_setfield(L, -2, "register_save_callback");

	// register_load_callback(name, function)
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *name = luaL_checkstring(L, 1);
		luaL_checktype(L, 2, LUA_TFUNCTION);

		auto it = s_stubLoadCallbacks.find(name);
		if (it != s_stubLoadCallbacks.end()) {
			luaL_unref(L, LUA_REGISTRYINDEX, it->second);
		}

		lua_pushvalue(L, 2);
		int ref = luaL_ref(L, LUA_REGISTRYINDEX);
		s_stubLoadCallbacks[name] = ref;
		return 0;
	});
	lua_setfield(L, -2, "register_load_callback");

	lua_setglobal(L, "ecs");
}

} // namespace editScene

// ---------------------------------------------------------------------------
// Stub: LuaEntityApi
// ---------------------------------------------------------------------------

namespace editScene
{

// Entity state for stubs
struct EntityState {
	bool alive;
	std::string name;
};

static std::unordered_map<int, EntityState> s_entities;
static int s_nextId = 1000;

static int createEntity()
{
	int id = s_nextId++;
	s_entities[id] = { true, "" };
	// Auto-add EditorMarker component (matching real behavior)
	s_components[id]["EditorMarker"];
	return id;
}

static void destroyEntity(int id)
{
	auto it = s_entities.find(id);
	if (it != s_entities.end())
		it->second.alive = false;
}

static bool entityExists(int id)
{
	auto it = s_entities.find(id);
	return it != s_entities.end() && it->second.alive;
}

static void setEntityName(int id, const std::string &name)
{
	auto it = s_entities.find(id);
	if (it != s_entities.end())
		it->second.name = name;
}

static std::string getEntityName(int id)
{
	auto it = s_entities.find(id);
	if (it != s_entities.end())
		return it->second.name;
	return "";
}

static int findEntityByName(const std::string &name)
{
	for (auto &[id, state] : s_entities) {
		if (state.alive && state.name == name)
			return id;
	}
	return -1;
}

void registerLuaEntityApi(lua_State *L)
{
	// Create the "ecs" global table
	lua_newtable(L);

	// create_entity
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int id = createEntity();
		lua_pushinteger(L, id);
		return 1;
	});
	lua_setfield(L, -2, "create_entity");

	// destroy_entity
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int id = lua_tointeger(L, 1);
		destroyEntity(id);
		return 0;
	});
	lua_setfield(L, -2, "destroy_entity");

	// entity_exists
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int id = lua_tointeger(L, 1);
		lua_pushboolean(L, entityExists(id) ? 1 : 0);
		return 1;
	});
	lua_setfield(L, -2, "entity_exists");

	// get_player_entity
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int id = findEntityByName("player");
		if (id >= 0) {
			lua_pushinteger(L, id);
		} else {
			lua_pushnil(L);
		}
		return 1;
	});
	lua_setfield(L, -2, "get_player_entity");

	// get_entity_by_name
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *name = lua_tostring(L, 1);
		int id = findEntityByName(name ? name : "");
		if (id >= 0) {
			lua_pushinteger(L, id);
		} else {
			lua_pushnil(L);
		}
		return 1;
	});
	lua_setfield(L, -2, "get_entity_by_name");

	// set_entity_name
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int id = lua_tointeger(L, 1);
		const char *name = lua_tostring(L, 2);
		setEntityName(id, name ? name : "");
		return 0;
	});
	lua_setfield(L, -2, "set_entity_name");

	// get_entity_name
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int id = lua_tointeger(L, 1);
		std::string name = getEntityName(id);
		if (!name.empty()) {
			lua_pushstring(L, name.c_str());
		} else {
			lua_pushnil(L);
		}
		return 1;
	});
	lua_setfield(L, -2, "get_entity_name");

	// parent
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_pushnil(L);
		return 1;
	});
	lua_setfield(L, -2, "parent");

	// children
	lua_pushcfunction(L, [](lua_State *L) -> int {
		lua_newtable(L);
		return 1;
	});
	lua_setfield(L, -2, "children");

	lua_setglobal(L, "ecs");
}

} // namespace editScene

// ---------------------------------------------------------------------------
// Stub: LuaComponentApi
// ---------------------------------------------------------------------------

namespace editScene
{

static ComponentData &getOrCreateComponent(int entityId,
					   const std::string &compName)
{
	return s_components[entityId][compName];
}

static bool hasComponent(int entityId, const std::string &compName)
{
	auto eit = s_components.find(entityId);
	if (eit == s_components.end())
		return false;
	return eit->second.find(compName) != eit->second.end();
}

static void removeComponent(int entityId, const std::string &compName)
{
	auto eit = s_components.find(entityId);
	if (eit != s_components.end())
		eit->second.erase(compName);
}

// Forward declaration
static void pushFieldValueToLua(lua_State *L, const ComponentFieldValue &fv);

static void pushFieldValueToLua(lua_State *L, const ComponentFieldValue &fv)
{
	switch (fv.type) {
	case ComponentFieldValue::NIL:
		lua_pushnil(L);
		break;
	case ComponentFieldValue::NUMBER:
		lua_pushnumber(L, fv.numVal);
		break;
	case ComponentFieldValue::STRING:
		lua_pushstring(L, fv.strVal.c_str());
		break;
	case ComponentFieldValue::BOOLEAN:
		lua_pushboolean(L, fv.boolVal ? 1 : 0);
		break;
	case ComponentFieldValue::NUM_ARRAY: {
		lua_newtable(L);
		for (size_t i = 0; i < fv.numArr.size(); i++) {
			lua_pushnumber(L, fv.numArr[i]);
			lua_rawseti(L, -2, i + 1);
		}
		break;
	}
	case ComponentFieldValue::STR_ARRAY: {
		lua_newtable(L);
		for (size_t i = 0; i < fv.strArr.size(); i++) {
			lua_pushstring(L, fv.strArr[i].c_str());
			lua_rawseti(L, -2, i + 1);
		}
		break;
	}
	case ComponentFieldValue::TABLE: {
		lua_newtable(L);
		for (auto &[k, v] : fv.tableVal) {
			pushFieldValueToLua(L, v);
			lua_setfield(L, -2, k.c_str());
		}
		break;
	}
	}
}

// Forward declaration
static ComponentFieldValue readLuaValue(lua_State *L, int idx);

static ComponentFieldValue readLuaValue(lua_State *L, int idx)
{
	ComponentFieldValue fv;
	// Convert to absolute index to be safe with stack changes
	int absIdx = lua_absindex(L, idx);
	int type = lua_type(L, absIdx);
	if (type == LUA_TNIL) {
		fv.type = ComponentFieldValue::NIL;
	} else if (type == LUA_TNUMBER) {
		fv.type = ComponentFieldValue::NUMBER;
		fv.numVal = lua_tonumber(L, absIdx);
	} else if (type == LUA_TSTRING) {
		fv.type = ComponentFieldValue::STRING;
		fv.strVal = lua_tostring(L, absIdx);
	} else if (type == LUA_TBOOLEAN) {
		fv.type = ComponentFieldValue::BOOLEAN;
		fv.boolVal = lua_toboolean(L, absIdx) != 0;
	} else if (type == LUA_TTABLE) {
		// Check if it's an array (all integer keys) or a map (string keys)
		bool isArray = true;
		bool isStringArray = false;
		bool isNumArray = false;
		int maxKey = 0;

		lua_pushnil(L);
		while (lua_next(L, absIdx) != 0) {
			if (lua_type(L, -2) == LUA_TNUMBER) {
				int k = lua_tointeger(L, -2);
				if (k > maxKey)
					maxKey = k;
				if (lua_type(L, -1) == LUA_TSTRING)
					isStringArray = true;
				else if (lua_type(L, -1) == LUA_TNUMBER)
					isNumArray = true;
			} else {
				isArray = false;
			}
			lua_pop(L, 1);
		}

		if (isArray && maxKey > 0) {
			if (isStringArray) {
				fv.type = ComponentFieldValue::STR_ARRAY;
				for (int i = 1; i <= maxKey; i++) {
					lua_rawgeti(L, absIdx, i);
					if (lua_type(L, -1) == LUA_TSTRING)
						fv.strArr.push_back(
							lua_tostring(L, -1));
					lua_pop(L, 1);
				}
			} else if (isNumArray) {
				fv.type = ComponentFieldValue::NUM_ARRAY;
				for (int i = 1; i <= maxKey; i++) {
					lua_rawgeti(L, absIdx, i);
					if (lua_type(L, -1) == LUA_TNUMBER)
						fv.numArr.push_back(
							lua_tonumber(L, -1));
					lua_pop(L, 1);
				}
			}
		} else {
			fv.type = ComponentFieldValue::TABLE;
			lua_pushnil(L);
			while (lua_next(L, absIdx) != 0) {
				if (lua_type(L, -2) == LUA_TSTRING) {
					const char *key = lua_tostring(L, -2);
					if (key)
						fv.tableVal[key] =
							readLuaValue(L, -1);
				}
				lua_pop(L, 1);
			}
		}
	}
	return fv;
}

void registerLuaComponentApi(lua_State *L)
{
	// Get or create the "ecs" global table
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	// has_component
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		lua_pushboolean(L, hasComponent(entityId,
						compName ? compName : "") ?
					   1 :
					   0);
		return 1;
	});
	lua_setfield(L, -2, "has_component");

	// add_component
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		if (compName)
			getOrCreateComponent(entityId, compName);
		return 0;
	});
	lua_setfield(L, -2, "add_component");

	// remove_component
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		if (compName)
			removeComponent(entityId, compName);
		return 0;
	});
	lua_setfield(L, -2, "remove_component");

	// get_component
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		if (!compName || !hasComponent(entityId, compName)) {
			lua_pushnil(L);
			return 1;
		}
		ComponentData &cd = s_components[entityId][compName];
		lua_newtable(L);
		for (auto &[k, v] : cd) {
			pushFieldValueToLua(L, v);
			lua_setfield(L, -2, k.c_str());
		}
		return 1;
	});
	lua_setfield(L, -2, "get_component");

	// set_component
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		if (!compName)
			return 0;
		ComponentData &cd = getOrCreateComponent(entityId, compName);
		if (lua_istable(L, 3)) {
			cd.clear();
			lua_pushnil(L);
			while (lua_next(L, 3) != 0) {
				if (lua_type(L, -2) == LUA_TSTRING) {
					const char *key = lua_tostring(L, -2);
					if (key)
						cd[key] = readLuaValue(L, -1);
				}
				lua_pop(L, 1);
			}
		}
		return 0;
	});
	lua_setfield(L, -2, "set_component");

	// get_field
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		const char *fieldName = lua_tostring(L, 3);
		if (!compName || !fieldName ||
		    !hasComponent(entityId, compName)) {
			lua_pushnil(L);
			return 1;
		}
		ComponentData &cd = s_components[entityId][compName];
		auto it = cd.find(fieldName);
		if (it != cd.end()) {
			pushFieldValueToLua(L, it->second);
		} else {
			lua_pushnil(L);
		}
		return 1;
	});
	lua_setfield(L, -2, "get_field");

	// set_field
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int entityId = lua_tointeger(L, 1);
		const char *compName = lua_tostring(L, 2);
		const char *fieldName = lua_tostring(L, 3);
		if (!compName || !fieldName)
			return 0;
		ComponentData &cd = getOrCreateComponent(entityId, compName);
		cd[fieldName] = readLuaValue(L, 4);
		return 0;
	});
	lua_setfield(L, -2, "set_field");

	lua_setglobal(L, "ecs");
}

} // namespace editScene

// ---------------------------------------------------------------------------
// Stub: LuaEventApi (EventParams-based)
// ---------------------------------------------------------------------------

namespace editScene
{

// Event subscription storage
struct EventSubscription {
	int id;
	std::string eventName;
	int callbackRef; // Lua registry reference
};

static std::vector<EventSubscription> s_subscriptions;
static int s_nextSubId = 1;

// Helper: push EventParams type metadata to Lua table
static void pushEventParamsTypes(lua_State *L,
				 const editScene::EventParams &params)
{
	lua_newtable(L);
	for (editScene::EventParams::ConstIterator it = params.begin();
	     it != params.end(); ++it) {
		const std::string &key = it->first;
		const editScene::EventValue &val = it->second;
		switch (val.getType()) {
		case editScene::EventValue::INT:
			lua_pushstring(L, "int");
			break;
		case editScene::EventValue::FLOAT:
			lua_pushstring(L, "float");
			break;
		case editScene::EventValue::DOUBLE:
			lua_pushstring(L, "double");
			break;
		case editScene::EventValue::STRING:
			lua_pushstring(L, "string");
			break;
		case editScene::EventValue::ENTITY_ID:
			lua_pushstring(L, "entity");
			break;
		case editScene::EventValue::INT_ARRAY:
			lua_pushstring(L, "int_array");
			break;
		case editScene::EventValue::FLOAT_ARRAY:
			lua_pushstring(L, "float_array");
			break;
		case editScene::EventValue::DOUBLE_ARRAY:
			lua_pushstring(L, "double_array");
			break;
		case editScene::EventValue::STRING_ARRAY:
			lua_pushstring(L, "string_array");
			break;
		case editScene::EventValue::ENTITY_ID_ARRAY:
			lua_pushstring(L, "entity_array");
			break;
		default:
			lua_pushstring(L, "unknown");
			break;
		}
		lua_setfield(L, -2, key.c_str());
	}
}

// Helper: push EventParams values to Lua table
static void pushEventParamsToLua(lua_State *L,
				 const editScene::EventParams &params)
{
	lua_newtable(L);
	for (editScene::EventParams::ConstIterator it = params.begin();
	     it != params.end(); ++it) {
		const std::string &key = it->first;
		const editScene::EventValue &val = it->second;
		switch (val.getType()) {
		case editScene::EventValue::INT:
			lua_pushinteger(L, val.getInt());
			break;
		case editScene::EventValue::FLOAT:
			lua_pushnumber(L, (lua_Number)val.getFloat());
			break;
		case editScene::EventValue::DOUBLE:
			lua_pushnumber(L, (lua_Number)val.getDouble());
			break;
		case editScene::EventValue::STRING:
			lua_pushstring(L, val.getString().c_str());
			break;
		case editScene::EventValue::ENTITY_ID:
			lua_pushinteger(L, (lua_Integer)val.getEntityId());
			break;
		case editScene::EventValue::INT_ARRAY: {
			lua_newtable(L);
			const auto &arr = val.getIntArray();
			for (size_t i = 0; i < arr.size(); i++) {
				lua_pushinteger(L, arr[i]);
				lua_rawseti(L, -2, i + 1);
			}
			break;
		}
		case editScene::EventValue::FLOAT_ARRAY: {
			lua_newtable(L);
			const auto &arr = val.getFloatArray();
			for (size_t i = 0; i < arr.size(); i++) {
				lua_pushnumber(L, (lua_Number)arr[i]);
				lua_rawseti(L, -2, i + 1);
			}
			break;
		}
		case editScene::EventValue::DOUBLE_ARRAY: {
			lua_newtable(L);
			const auto &arr = val.getDoubleArray();
			for (size_t i = 0; i < arr.size(); i++) {
				lua_pushnumber(L, (lua_Number)arr[i]);
				lua_rawseti(L, -2, i + 1);
			}
			break;
		}
		case editScene::EventValue::STRING_ARRAY: {
			lua_newtable(L);
			const auto &arr = val.getStringArray();
			for (size_t i = 0; i < arr.size(); i++) {
				lua_pushstring(L, arr[i].c_str());
				lua_rawseti(L, -2, i + 1);
			}
			break;
		}
		case editScene::EventValue::ENTITY_ID_ARRAY: {
			lua_newtable(L);
			const auto &arr = val.getEntityIdArray();
			for (size_t i = 0; i < arr.size(); i++) {
				lua_pushinteger(L, (lua_Integer)arr[i]);
				lua_rawseti(L, -2, i + 1);
			}
			break;
		}
		default:
			lua_pushnil(L);
			break;
		}
		lua_setfield(L, -2, key.c_str());
	}
	// Add _types metadata table
	pushEventParamsTypes(L, params);
	lua_setfield(L, -2, "_types");
}

// Helper: read a Lua value and add it to EventParams
static void readLuaValueToEventParams(lua_State *L, int idx,
				      const std::string &key,
				      editScene::EventParams &params)
{
	int absIdx = lua_absindex(L, idx);
	int type = lua_type(L, absIdx);
	if (type == LUA_TNIL) {
		// Skip nil values
	} else if (type == LUA_TNUMBER) {
		lua_Number val = lua_tonumber(L, absIdx);
		// Check if it's an integer
		if (val == (lua_Integer)val) {
			params.setInt(key, (int)val);
		} else {
			// Store as double (Lua numbers are doubles)
			params.setDouble(key, (double)val);
		}
	} else if (type == LUA_TSTRING) {
		params.setString(key, lua_tostring(L, absIdx));
	} else if (type == LUA_TBOOLEAN) {
		params.setInt(key, lua_toboolean(L, absIdx) ? 1 : 0);
	} else if (type == LUA_TTABLE) {
		// Check if it's an array (all integer keys)
		bool isArray = true;
		bool isStringArray = false;
		bool isNumArray = false;
		int maxKey = 0;

		lua_pushnil(L);
		while (lua_next(L, absIdx) != 0) {
			if (lua_type(L, -2) == LUA_TNUMBER) {
				int k = (int)lua_tointeger(L, -2);
				if (k > maxKey)
					maxKey = k;
				if (lua_type(L, -1) == LUA_TSTRING)
					isStringArray = true;
				else if (lua_type(L, -1) == LUA_TNUMBER)
					isNumArray = true;
			} else {
				isArray = false;
			}
			lua_pop(L, 1);
		}

		if (isArray && maxKey > 0) {
			if (isStringArray) {
				std::vector<std::string> arr;
				for (int i = 1; i <= maxKey; i++) {
					lua_rawgeti(L, absIdx, i);
					if (lua_type(L, -1) == LUA_TSTRING)
						arr.push_back(
							lua_tostring(L, -1));
					lua_pop(L, 1);
				}
				params.setStringArray(key, arr);
			} else if (isNumArray) {
				std::vector<double> arr;
				for (int i = 1; i <= maxKey; i++) {
					lua_rawgeti(L, absIdx, i);
					if (lua_type(L, -1) == LUA_TNUMBER)
						arr.push_back(
							lua_tonumber(L, -1));
					lua_pop(L, 1);
				}
				params.setDoubleArray(key, arr);
			}
		}
	}
}

void registerLuaEventApi(lua_State *L)
{
	// Get or create the "ecs" global table
	lua_getglobal(L, "ecs");
	if (lua_isnil(L, -1)) {
		lua_pop(L, 1);
		lua_newtable(L);
	}

	// subscribe_event
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *eventName = lua_tostring(L, 1);
		if (!eventName || !lua_isfunction(L, 2)) {
			lua_pushnil(L);
			return 1;
		}
		// Store callback in Lua registry
		lua_pushvalue(L, 2);
		int ref = luaL_ref(L, LUA_REGISTRYINDEX);

		int subId = s_nextSubId++;
		s_subscriptions.push_back({ subId, eventName, ref });
		lua_pushinteger(L, subId);
		return 1;
	});
	lua_setfield(L, -2, "subscribe_event");

	// unsubscribe_event
	lua_pushcfunction(L, [](lua_State *L) -> int {
		int subId = lua_tointeger(L, 1);
		for (auto it = s_subscriptions.begin();
		     it != s_subscriptions.end(); ++it) {
			if (it->id == subId) {
				luaL_unref(L, LUA_REGISTRYINDEX,
					   it->callbackRef);
				s_subscriptions.erase(it);
				break;
			}
		}
		return 0;
	});
	lua_setfield(L, -2, "unsubscribe_event");

	// send_event
	lua_pushcfunction(L, [](lua_State *L) -> int {
		const char *eventName = lua_tostring(L, 1);
		if (!eventName)
			return 0;

		// Build EventParams from Lua table (arg 2)
		editScene::EventParams params;
		if (lua_istable(L, 2)) {
			lua_pushnil(L);
			while (lua_next(L, 2) != 0) {
				if (lua_type(L, -2) == LUA_TSTRING) {
					const char *key = lua_tostring(L, -2);
					if (key) {
						readLuaValueToEventParams(
							L, -1, key, params);
					}
				}
				lua_pop(L, 1);
			}
		}

		// Call all matching subscriptions
		for (auto &sub : s_subscriptions) {
			if (sub.eventName == eventName) {
				// Push callback
				lua_rawgeti(L, LUA_REGISTRYINDEX,
					    sub.callbackRef);
				// Push event name
				lua_pushstring(L, eventName);
				// Push params as Lua table
				pushEventParamsToLua(L, params);
				// Call callback(event, params)
				if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
					fprintf(stderr,
						"Event callback error: %s\n",
						lua_tostring(L, -1));
					lua_pop(L, 1);
				}
			}
		}
		return 0;
	});
	lua_setfield(L, -2, "send_event");

	lua_setglobal(L, "ecs");
}

} // namespace editScene
