/**
 * @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>

// ---------------------------------------------------------------------------
// 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");
}

} // 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
