#include "LuaBehaviorTreeApi.hpp"
#include "LuaEntityApi.hpp"
#include "../components/GoapBlackboard.hpp"
#include <OgreLogManager.h>
#include <unordered_map>
#include <string>
#include <vector>

namespace editScene
{

// ---------------------------------------------------------------------------
// Global registry of Lua node handlers
// ---------------------------------------------------------------------------
// Maps node name -> Lua registry reference for the callback function.
// The callback is stored as a Lua reference in the registry so it persists
// across Lua state resets and can be called from C++.

static std::unordered_map<std::string, int> g_luaNodeHandlers;

// Global Lua state pointer, set during registerLuaBehaviorTreeApi().
// Used by callLuaBehaviorTreeNode() which is invoked from the C++
// behavior tree system without direct access to the Lua state.
static lua_State *g_luaState = nullptr;

// ---------------------------------------------------------------------------
// Helper: push a GoapBlackboard as a Lua table (reused from LuaActionApi)
// ---------------------------------------------------------------------------

static void pushBlackboard(lua_State *L, const GoapBlackboard &bb)
{
	lua_newtable(L); // blackboard table

	// Bits
	lua_newtable(L); // bits table
	for (int i = 0; i < 64; i++) {
		if (bb.hasBit(i)) {
			const char *name = GoapBlackboard::getBitName(i);
			const char *key = name ? name : "";
			lua_pushboolean(L, bb.getBit(i));
			lua_setfield(L, -2, key);
		}
	}
	lua_setfield(L, -2, "bits");

	// Integer values
	lua_newtable(L);
	for (const auto &kv : bb.values) {
		lua_pushinteger(L, kv.second);
		lua_setfield(L, -2, kv.first.c_str());
	}
	lua_setfield(L, -2, "values");

	// Float values
	lua_newtable(L);
	for (const auto &kv : bb.floatValues) {
		lua_pushnumber(L, kv.second);
		lua_setfield(L, -2, kv.first.c_str());
	}
	lua_setfield(L, -2, "floatValues");

	// String values
	lua_newtable(L);
	for (const auto &kv : bb.stringValues) {
		lua_pushstring(L, kv.second.c_str());
		lua_setfield(L, -2, kv.first.c_str());
	}
	lua_setfield(L, -2, "stringValues");
}

// ---------------------------------------------------------------------------
// Helper: parse "key=val,key2=val2" params into a Lua table
// ---------------------------------------------------------------------------

static void pushParamsTable(lua_State *L, const std::string &params)
{
	lua_newtable(L); // params table

	if (params.empty())
		return;

	const char *s = params.c_str();
	while (*s) {
		// Skip whitespace
		while (*s == ' ' || *s == '\t')
			s++;
		if (!*s)
			break;

		// Find key
		const char *keyStart = s;
		while (*s && *s != '=' && *s != ',')
			s++;
		std::string key(keyStart, static_cast<size_t>(s - keyStart));
		// Trim trailing spaces from key
		while (!key.empty() &&
		       (key.back() == ' ' || key.back() == '\t'))
			key.pop_back();

		if (*s != '=') {
			while (*s && *s != ',')
				s++;
			if (*s == ',')
				s++;
			continue;
		}
		s++; // skip '='

		// Skip whitespace before value
		while (*s == ' ' || *s == '\t')
			s++;

		// Find value end (next comma or end)
		const char *valStart = s;
		bool inQuotes = false;
		while (*s && (*s != ',' || inQuotes)) {
			if (*s == '"')
				inQuotes = !inQuotes;
			s++;
		}
		std::string val(valStart, static_cast<size_t>(s - valStart));
		// Trim trailing spaces from value
		while (!val.empty() &&
		       (val.back() == ' ' || val.back() == '\t'))
			val.pop_back();

		// Strip quotes if present
		if (val.size() >= 2 && val.front() == '"' &&
		    val.back() == '"') {
			val = val.substr(1, val.size() - 2);
			lua_pushstring(L, val.c_str());
			lua_setfield(L, -2, key.c_str());
		} else {
			// Try numeric
			char *end = nullptr;
			long iVal = strtol(val.c_str(), &end, 10);
			if (end != val.c_str() && *end == '\0') {
				lua_pushinteger(L, (int)iVal);
				lua_setfield(L, -2, key.c_str());
			} else {
				// Try float
				end = nullptr;
				float fVal = strtof(val.c_str(), &end);
				if (end != val.c_str() && *end == '\0') {
					lua_pushnumber(L, fVal);
					lua_setfield(L, -2, key.c_str());
				} else {
					// Fallback to string
					lua_pushstring(L, val.c_str());
					lua_setfield(L, -2, key.c_str());
				}
			}
		}

		if (*s == ',')
			s++;
	}
}

// ---------------------------------------------------------------------------
// Public API: Call a registered Lua node handler
// ---------------------------------------------------------------------------
// Returns: 0 = success, 1 = failure, 2 = running, -1 = not found/error

int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
			    flecs::entity entity, const std::string &params)
{
	// Use the global Lua state if none was passed
	lua_State *L = static_cast<lua_State *>(L_);
	if (!L)
		L = g_luaState;
	if (!L)
		return -1;

	auto it = g_luaNodeHandlers.find(nodeName);
	if (it == g_luaNodeHandlers.end())
		return -1;

	int ref = it->second;

	// Push the callback function
	lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

	// Push entity ID as first argument
	lua_pushinteger(L, luaEntityToId(entity));

	// Push params table as second argument
	pushParamsTable(L, params);

	// Call the function
	if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
		Ogre::LogManager::getSingleton().stream()
			<< "[Lua BT] Error calling node '" << nodeName
			<< "': " << lua_tostring(L, -1);
		lua_pop(L, 1);
		return -1;
	}

	// Read the result
	if (!lua_isstring(L, -1)) {
		lua_pop(L, 1);
		return -1;
	}

	const char *result = lua_tostring(L, -1);
	lua_pop(L, 1);

	if (strcmp(result, "success") == 0)
		return 0;
	if (strcmp(result, "failure") == 0)
		return 1;
	if (strcmp(result, "running") == 0)
		return 2;

	Ogre::LogManager::getSingleton().stream()
		<< "[Lua BT] Node '" << nodeName
		<< "' returned invalid result: " << result;
	return -1;
}

// ---------------------------------------------------------------------------
// Convenience overload: call with raw entity ID (for tests)
// ---------------------------------------------------------------------------

int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
			    uint64_t entityId, const std::string &params)
{
	// Use the global Lua state if none was passed
	lua_State *L = static_cast<lua_State *>(L_);
	if (!L)
		L = g_luaState;
	if (!L)
		return -1;

	auto it = g_luaNodeHandlers.find(nodeName);
	if (it == g_luaNodeHandlers.end())
		return -1;

	int ref = it->second;

	// Push the callback function
	lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

	// Push entity ID as first argument
	lua_pushinteger(L, (lua_Integer)entityId);

	// Push params table as second argument
	pushParamsTable(L, params);

	// Call the function
	if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
		Ogre::LogManager::getSingleton().stream()
			<< "[Lua BT] Error calling node '" << nodeName
			<< "': " << lua_tostring(L, -1);
		lua_pop(L, 1);
		return -1;
	}

	// Read the result
	if (!lua_isstring(L, -1)) {
		lua_pop(L, 1);
		return -1;
	}

	const char *result = lua_tostring(L, -1);
	lua_pop(L, 1);

	if (strcmp(result, "success") == 0)
		return 0;
	if (strcmp(result, "failure") == 0)
		return 1;
	if (strcmp(result, "running") == 0)
		return 2;

	Ogre::LogManager::getSingleton().stream()
		<< "[Lua BT] Node '" << nodeName
		<< "' returned invalid result: " << result;
	return -1;
}

// ---------------------------------------------------------------------------
// Lua: ecs.behavior_tree.register_node(name, function)
// ---------------------------------------------------------------------------

static int luaRegisterNode(lua_State *L)
{
	const char *name = luaL_checkstring(L, 1);

	if (!lua_isfunction(L, 2))
		luaL_error(L, "Expected function as second argument");

	// Remove any existing handler with the same name
	auto it = g_luaNodeHandlers.find(name);
	if (it != g_luaNodeHandlers.end()) {
		luaL_unref(L, LUA_REGISTRYINDEX, it->second);
	}

	// Store the function reference
	lua_pushvalue(L, 2);
	int ref = luaL_ref(L, LUA_REGISTRYINDEX);
	g_luaNodeHandlers[name] = ref;

	Ogre::LogManager::getSingleton().stream()
		<< "[Lua BT] Registered node handler: " << name;

	return 0;
}

// ---------------------------------------------------------------------------
// Lua: ecs.behavior_tree.unregister_node(name) -> bool
// ---------------------------------------------------------------------------

static int luaUnregisterNode(lua_State *L)
{
	const char *name = luaL_checkstring(L, 1);

	auto it = g_luaNodeHandlers.find(name);
	if (it == g_luaNodeHandlers.end()) {
		lua_pushboolean(L, false);
		return 1;
	}

	luaL_unref(L, LUA_REGISTRYINDEX, it->second);
	g_luaNodeHandlers.erase(it);

	lua_pushboolean(L, true);
	return 1;
}

// ---------------------------------------------------------------------------
// Lua: ecs.behavior_tree.list_nodes() -> table of names
// ---------------------------------------------------------------------------

static int luaListNodeHandlers(lua_State *L)
{
	lua_newtable(L);
	int idx = 1;
	for (const auto &kv : g_luaNodeHandlers) {
		lua_pushstring(L, kv.first.c_str());
		lua_rawseti(L, -2, idx++);
	}
	return 1;
}

// ---------------------------------------------------------------------------
// Lua: ecs.behavior_tree.create_node(type, name, params) -> table
//      ecs.behavior_tree.create_node(type, params) -> table
//      ecs.behavior_tree.create_node(luaHandlerName, params) -> table (backward compat)
// ---------------------------------------------------------------------------
// Creates a behavior tree node table suitable for use in action behavior trees.
//
// If the first argument matches a registered Lua node handler name, it creates
// a luaTask node (backward compatible behavior).
// Otherwise, the first argument is treated as the node type string.
//
// Examples:
//   ecs.behavior_tree.create_node("setAnimationState", "locomotion/walk")
//     -> { type = "setAnimationState", name = "locomotion/walk" }
//
//   ecs.behavior_tree.create_node("delay", "2.0")
//     -> { type = "delay", params = "2.0" }
//
//   ecs.behavior_tree.create_node("delay", "", "2.0")
//     -> { type = "delay", params = "2.0" }
//
//   ecs.behavior_tree.create_node("say_hello", "message=Hi")
//     -> { type = "luaTask", name = "say_hello", params = "message=Hi" }

static int luaCreateNode(lua_State *L)
{
	const char *arg1 = luaL_checkstring(L, 1);

	// Check if arg1 is a registered Lua node handler name (backward compat)
	bool isLuaHandler = g_luaNodeHandlers.find(arg1) !=
			    g_luaNodeHandlers.end();

	// Save arg2 and arg3 before pushing the result table (which shifts indices)
	const char *arg2 = NULL;
	const char *arg3 = NULL;
	if (lua_gettop(L) >= 2 && lua_isstring(L, 2))
		arg2 = lua_tostring(L, 2);
	if (lua_gettop(L) >= 3 && lua_isstring(L, 3))
		arg3 = lua_tostring(L, 3);

	lua_newtable(L);

	if (isLuaHandler) {
		// Backward compatible: create a luaTask node
		lua_pushstring(L, "luaTask");
		lua_setfield(L, -2, "type");

		lua_pushstring(L, arg1);
		lua_setfield(L, -2, "name");

		if (arg2 && arg2[0]) {
			lua_pushstring(L, arg2);
			lua_setfield(L, -2, "params");
		}
	} else {
		// arg1 is the node type
		lua_pushstring(L, arg1);
		lua_setfield(L, -2, "type");

		// arg2 is either name or params depending on the node type
		if (arg2 && arg2[0]) {
			lua_pushstring(L, arg2);
			lua_setfield(L, -2, "name");
		}

		// arg3 is params (optional)
		if (arg3 && arg3[0]) {
			lua_pushstring(L, arg3);
			lua_setfield(L, -2, "params");
		}
	}

	return 1;
}

// ---------------------------------------------------------------------------
// Public C++ API: get list of registered Lua node names
// ---------------------------------------------------------------------------

std::vector<std::string> getRegisteredLuaNodeNames()
{
	std::vector<std::string> names;
	names.reserve(g_luaNodeHandlers.size());
	for (const auto &kv : g_luaNodeHandlers)
		names.push_back(kv.first);
	return names;
}

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

void registerLuaBehaviorTreeApi(lua_State *L)
{
	// Store the Lua state globally so callLuaBehaviorTreeNode can use it
	g_luaState = L;

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

	// Create the behavior_tree sub-table
	lua_newtable(L);

	lua_pushcfunction(L, luaRegisterNode);
	lua_setfield(L, -2, "register_node");

	lua_pushcfunction(L, luaUnregisterNode);
	lua_setfield(L, -2, "unregister_node");

	lua_pushcfunction(L, luaListNodeHandlers);
	lua_setfield(L, -2, "list_nodes");

	lua_pushcfunction(L, luaCreateNode);
	lua_setfield(L, -2, "create_node");

	// Set behavior_tree as a field of ecs
	lua_setfield(L, -2, "behavior_tree");

	// Ensure ecs is global
	lua_setglobal(L, "ecs");
}

} // namespace editScene
