#include "LuaCharacterClassApi.hpp"
#include "../systems/CharacterClassSystem.hpp"
#include "../components/CharacterClassDatabase.hpp"
#include "../components/CharacterClassComponent.hpp"
#include "../components/GoapBlackboard.hpp"
#include <OgreLogManager.h>

namespace editScene
{

// ---------------------------------------------------------------------------
// Helper: get the Flecs world from the Lua registry
// ---------------------------------------------------------------------------

static flecs::world getWorld(lua_State *L)
{
	lua_getfield(L, LUA_REGISTRYINDEX, "EditSceneFlecsWorld");
	OgreAssert(lua_islightuserdata(L, -1), "Flecs world not registered");
	flecs::world *world =
		static_cast<flecs::world *>(lua_touserdata(L, -1));
	lua_pop(L, 1);
	return *world;
}

// ---------------------------------------------------------------------------
// Helper: push string-vector as Lua table
// ---------------------------------------------------------------------------

static void pushStringVector(lua_State *L,
			     const std::vector<Ogre::String> &vec)
{
	lua_newtable(L);
	for (size_t i = 0; i < vec.size(); i++) {
		lua_pushstring(L, vec[i].c_str());
		lua_rawseti(L, -2, static_cast<int>(i + 1));
	}
}

// ---------------------------------------------------------------------------
// Database queries
// ---------------------------------------------------------------------------

static int luaGetClassNames(lua_State *L)
{
	pushStringVector(L,
			 CharacterClassDatabase::getSingleton()
				 .getClassNames());
	return 1;
}

static int luaGetStatNames(lua_State *L)
{
	pushStringVector(L,
			 CharacterClassDatabase::getSingleton()
				 .getStatNames());
	return 1;
}

static int luaGetSkillNames(lua_State *L)
{
	pushStringVector(L,
			 CharacterClassDatabase::getSingleton()
				 .getSkillNames());
	return 1;
}

static int luaGetNeedNames(lua_State *L)
{
	pushStringVector(L,
			 CharacterClassDatabase::getSingleton()
				 .getNeedNames());
	return 1;
}

static int luaGetClass(lua_State *L)
{
	const char *name = lua_tostring(L, 1);
	if (!name)
		return 0;

	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		name);
	if (!cls)
		return 0;

	lua_newtable(L);
	lua_pushstring(L, cls->name.c_str());
	lua_setfield(L, -2, "name");
	lua_pushstring(L, cls->description.c_str());
	lua_setfield(L, -2, "description");
	pushStringVector(L, cls->primaryStats);
	lua_setfield(L, -2, "primary_stats");
	return 1;
}

static int luaGetStatKind(lua_State *L)
{
	const char *name = lua_tostring(L, 1);
	if (!name) {
		lua_pushstring(L, "unknown");
		return 1;
	}
	const auto *def = CharacterClassDatabase::getSingleton().findStat(name);
	if (!def) {
		lua_pushstring(L, "unknown");
		return 1;
	}
	lua_pushstring(L, def->kind == CharacterClassDatabase::StatKind::ResourcePool ?
				   "resource_pool" : "attribute");
	return 1;
}

// ---------------------------------------------------------------------------
// Per-entity runtime API
// ---------------------------------------------------------------------------

static int luaGetLevel(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>()) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L, e.get<CharacterClassComponent>().level);
	return 1;
}

static int luaGetXP(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>()) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L,
			(lua_Integer)e.get<CharacterClassComponent>().currentXP);
	return 1;
}

static int luaAddXP(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	int64_t amount = static_cast<int64_t>(lua_tointeger(L, 2));
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>()) {
		lua_pushboolean(L, 0);
		return 1;
	}
	// We can't easily access CharacterClassSystem singleton here,
	// so we do the XP add directly and let the system pick it up
	auto &cc = e.get_mut<CharacterClassComponent>();
	cc.currentXP += amount;
	lua_pushboolean(L, 1);
	return 1;
}

static int luaGetStat(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *statName = lua_tostring(L, 2);
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !statName) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L,
			(lua_Integer)e.get<CharacterClassComponent>().getStat(
				statName));
	return 1;
}

static int luaGetSkill(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *skillName = lua_tostring(L, 2);
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !skillName) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L,
			(lua_Integer)e.get<CharacterClassComponent>().getSkill(
				skillName));
	return 1;
}

static int luaGetNeed(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *needName = lua_tostring(L, 2);
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !needName) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L,
			(lua_Integer)e.get<CharacterClassComponent>().getNeed(
				needName));
	return 1;
}

static int luaGetAvailablePoints(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>()) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L,
			(lua_Integer)e.get<CharacterClassComponent>()
				.availablePoints);
	return 1;
}

static int luaSetNeed(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *needName = lua_tostring(L, 2);
	int value = static_cast<int>(lua_tointeger(L, 3));
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !needName) {
		return 0;
	}
	auto &cc = e.get_mut<CharacterClassComponent>();
	cc.needs[needName] = value;
	return 0;
}

static int luaGetPoolCurrent(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *poolName = lua_tostring(L, 2);
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !poolName) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L, (lua_Integer)e.get<CharacterClassComponent>().
				getPoolCurrent(poolName));
	return 1;
}

static int luaGetPoolMax(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *poolName = lua_tostring(L, 2);
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !poolName) {
		lua_pushinteger(L, 0);
		return 1;
	}
	lua_pushinteger(L, (lua_Integer)e.get<CharacterClassComponent>().
				getPoolMax(poolName));
	return 1;
}

static int luaSetPoolCurrent(lua_State *L)
{
	int entityId = static_cast<int>(lua_tointeger(L, 1));
	const char *poolName = lua_tostring(L, 2);
	int value = static_cast<int>(lua_tointeger(L, 3));
	flecs::entity e = getWorld(L).entity(entityId);
	if (!e.is_alive() || !e.has<CharacterClassComponent>() ||
	    !poolName) {
		lua_pushboolean(L, 0);
		return 1;
	}
	e.get_mut<CharacterClassComponent>().setPoolCurrent(poolName, value);
	lua_pushboolean(L, 1);
	return 1;
}

// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------

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

	lua_newtable(L);

	lua_pushcfunction(L, luaGetClassNames);
	lua_setfield(L, -2, "get_class_names");

	lua_pushcfunction(L, luaGetStatNames);
	lua_setfield(L, -2, "get_stat_names");

	lua_pushcfunction(L, luaGetSkillNames);
	lua_setfield(L, -2, "get_skill_names");

	lua_pushcfunction(L, luaGetNeedNames);
	lua_setfield(L, -2, "get_need_names");

	lua_pushcfunction(L, luaGetClass);
	lua_setfield(L, -2, "get_class");

	lua_pushcfunction(L, luaGetStatKind);
	lua_setfield(L, -2, "get_stat_kind");

	lua_pushcfunction(L, luaGetLevel);
	lua_setfield(L, -2, "get_level");

	lua_pushcfunction(L, luaGetXP);
	lua_setfield(L, -2, "get_xp");

	lua_pushcfunction(L, luaAddXP);
	lua_setfield(L, -2, "add_xp");

	lua_pushcfunction(L, luaGetStat);
	lua_setfield(L, -2, "get_stat");

	lua_pushcfunction(L, luaGetSkill);
	lua_setfield(L, -2, "get_skill");

	lua_pushcfunction(L, luaGetNeed);
	lua_setfield(L, -2, "get_need");

	lua_pushcfunction(L, luaGetAvailablePoints);
	lua_setfield(L, -2, "get_available_points");

	lua_pushcfunction(L, luaSetNeed);
	lua_setfield(L, -2, "set_need");

	lua_pushcfunction(L, luaGetPoolCurrent);
	lua_setfield(L, -2, "get_pool_current");

	lua_pushcfunction(L, luaGetPoolMax);
	lua_setfield(L, -2, "get_pool_max");

	lua_pushcfunction(L, luaSetPoolCurrent);
	lua_setfield(L, -2, "set_pool_current");

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

	lua_setglobal(L, "ecs");
}

} // namespace editScene
