#include "BehaviorTreeSystem.hpp"
#include "AnimationTreeSystem.hpp"
#include "CharacterSystem.hpp"
#include "SmartObjectSystem.hpp"
#include "ItemSystem.hpp"
#include "ItemRegistry.hpp"
#include "ItemStateRegistry.hpp"
#include "EventBus.hpp"
#include "../components/BehaviorTree.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/GoapBlackboard.hpp"
#include "../components/SmartObject.hpp"
#include "../components/Transform.hpp"
#include "../components/EntityName.hpp"
#include "../components/Character.hpp"
#include "../components/Item.hpp"
#include "../components/Inventory.hpp"
#include <OgreLogManager.h>
#include <iostream>
#include <cstdlib>
#include <cstring>

/* Forward declaration for Lua behavior tree node support.
 * The actual implementation is in lua/LuaBehaviorTreeApi.cpp.
 * Returns: 0 = success, 1 = failure, 2 = running, -1 = not found/error.
 * We use a function pointer approach to avoid a hard dependency on
 * the Lua headers in this translation unit.
 *
 * The lua_State pointer is passed as void* to avoid needing the Lua
 * headers in this translation unit. */
namespace editScene
{
int callLuaBehaviorTreeNode(void *L, const std::string &nodeName,
			    flecs::entity entity, const std::string &params);
}

static float g_epsilon = 0.0001f;

static bool parseValueString(const Ogre::String &str, int &outInt,
			     float &outFloat, Ogre::Vector3 &outVec3,
			     int &type);

/** Parse "key=val,key2=val2" params into an EventParams.
 *  Values are auto-detected: int, float, or quoted string. */
static void parseEventParams(const Ogre::String &str,
			     editScene::EventParams &out)
{
	if (str.empty())
		return;

	const char *s = str.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++;
		Ogre::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++;
		}
		Ogre::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);
			out.setString(key, val);
		} else {
			// Try int/float
			int iVal;
			float fVal;
			Ogre::Vector3 vVal;
			int vType;
			if (parseValueString(val, iVal, fVal, vVal, vType)) {
				if (vType == 0)
					out.setInt(key, iVal);
				else if (vType == 1)
					out.setFloat(key, fVal);
				else
					out.setFloat(key, vVal.x);
			} else {
				// Fallback to string
				out.setString(key, val);
			}
		}

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

static bool parseBitIndex(const Ogre::String &str, int &out)
{
	// Try numeric first
	char *end = nullptr;
	long idx = strtol(str.c_str(), &end, 10);
	if (end != str.c_str() && *end == '\0' && idx >= 0 && idx < 64) {
		out = static_cast<int>(idx);
		return true;
	}
	// Try named bit
	int found = GoapBlackboard::findBitByName(str);
	if (found >= 0) {
		out = found;
		return true;
	}
	return false;
}

static bool parseValueString(const Ogre::String &str, int &outInt,
			     float &outFloat, Ogre::Vector3 &outVec3, int &type)
{
	// type: 0=int, 1=float, 2=vec3
	if (str.find(',') != Ogre::String::npos) {
		// Try Vector3: "x,y,z"
		float x, y, z;
		if (sscanf(str.c_str(), "%f,%f,%f", &x, &y, &z) == 3) {
			outVec3 = Ogre::Vector3(x, y, z);
			type = 2;
			return true;
		}
	}
	if (str.find('.') != Ogre::String::npos) {
		char *end = nullptr;
		float f = strtof(str.c_str(), &end);
		if (end != str.c_str() && *end == '\0') {
			outFloat = f;
			type = 1;
			return true;
		}
	}
	char *end = nullptr;
	long i = strtol(str.c_str(), &end, 10);
	if (end != str.c_str() && *end == '\0') {
		outInt = static_cast<int>(i);
		type = 0;
		return true;
	}
	return false;
}

struct Comparison {
	enum Op { Eq, Ne, Lt, Gt, Le, Ge } op;
	int intVal;
	float floatVal;
	Ogre::Vector3 vec3Val;
	int valueType; // 0=int, 1=float, 2=vec3
};

static bool parseComparison(const Ogre::String &str, Comparison &out)
{
	const char *s = str.c_str();
	while (*s == ' ' || *s == '\t')
		s++;

	const char *ops[] = { "==", "!=", ">=", "<=", ">", "<" };
	Comparison::Op opTypes[] = { Comparison::Eq, Comparison::Ne,
				     Comparison::Ge, Comparison::Le,
				     Comparison::Gt, Comparison::Lt };

	for (int i = 0; i < 6; i++) {
		size_t len = strlen(ops[i]);
		if (strncmp(s, ops[i], len) == 0) {
			out.op = opTypes[i];
			Ogre::String valStr(s + len);
			return parseValueString(valStr, out.intVal,
						out.floatVal, out.vec3Val,
						out.valueType);
		}
	}
	return false;
}

static bool compareValues(const Comparison &cmp, int actualInt,
			  float actualFloat, const Ogre::Vector3 &actualVec3,
			  int actualType)
{
	// If types differ, promote int to float, or fail for vec3
	float a = 0.0f, b = 0.0f;
	int type = actualType;

	if (actualType == 0 && cmp.valueType == 1) {
		a = static_cast<float>(actualInt);
		type = 1;
	} else if (actualType == 1 && cmp.valueType == 0) {
		b = static_cast<float>(cmp.intVal);
	} else if (actualType != cmp.valueType) {
		// Type mismatch (vec3 vs scalar) — only == and != make sense
		if (cmp.op == Comparison::Eq)
			return false;
		if (cmp.op == Comparison::Ne)
			return true;
		return false;
	}

	if (type == 0) {
		a = static_cast<float>(actualInt);
		b = static_cast<float>(cmp.intVal);
	} else if (type == 1) {
		a = actualFloat;
		b = cmp.floatVal;
	} else if (type == 2) {
		bool eq =
			(std::abs(actualVec3.x - cmp.vec3Val.x) < g_epsilon) &&
			(std::abs(actualVec3.y - cmp.vec3Val.y) < g_epsilon) &&
			(std::abs(actualVec3.z - cmp.vec3Val.z) < g_epsilon);
		switch (cmp.op) {
		case Comparison::Eq:
			return eq;
		case Comparison::Ne:
			return !eq;
		default:
			return false;
		}
	}

	switch (cmp.op) {
	case Comparison::Eq:
		return std::abs(a - b) < g_epsilon;
	case Comparison::Ne:
		return std::abs(a - b) >= g_epsilon;
	case Comparison::Lt:
		return a < b;
	case Comparison::Gt:
		return a > b;
	case Comparison::Le:
		return a <= b;
	case Comparison::Ge:
		return a >= b;
	}
	return false;
}

BehaviorTreeSystem::BehaviorTreeSystem(flecs::world &world,
				       Ogre::SceneManager *sceneMgr,
				       AnimationTreeSystem *animSystem,
				       CharacterSystem *charSystem)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_animSystem(animSystem)
	, m_charSystem(charSystem)
{
}

BehaviorTreeSystem::~BehaviorTreeSystem() = default;

bool BehaviorTreeSystem::isNewlyActive(RunnerState &state,
				       const BehaviorTreeNode *node)
{
	char key[32];
	snprintf(key, sizeof(key), "%p", (void *)node);
	return state.firstRun ||
	       state.lastActiveLeaves.find(key) == state.lastActiveLeaves.end();
}

void BehaviorTreeSystem::markActive(RunnerState &state,
				    const BehaviorTreeNode *node)
{
	char key[32];
	snprintf(key, sizeof(key), "%p", (void *)node);
	state.currentActiveLeaves.insert(key);
}

BehaviorTreeSystem::Status
BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
				 RunnerState &state, float deltaTime)
{
	if (node.type == "sequence") {
		for (const auto &child : node.children) {
			Status s = evaluateNode(child, e, state, deltaTime);
			if (s == Status::failure)
				return Status::failure;
			if (s == Status::running)
				return Status::running;
		}
		return Status::success;
	}

	if (node.type == "selector") {
		for (const auto &child : node.children) {
			Status s = evaluateNode(child, e, state, deltaTime);
			if (s == Status::success)
				return Status::success;
			if (s == Status::running)
				return Status::running;
		}
		return Status::failure;
	}

	if (node.type == "invert") {
		if (node.children.empty())
			return Status::failure;
		Status s = evaluateNode(node.children[0], e, state, deltaTime);
		if (s == Status::success)
			return Status::failure;
		if (s == Status::failure)
			return Status::success;
		return Status::running;
	}

	/* Leaf nodes */
	markActive(state, &node);

	if (node.type == "task" || node.type == "check") {
		/* Generic task/check - for now always succeed */
		return Status::success;
	}

	if (node.type == "sendEvent") {
		if (isNewlyActive(state, &node)) {
			editScene::EventParams params;
			parseEventParams(node.params, params);
			EventBus::getInstance().send(node.name, params);
		}
		return Status::success;
	}

	if (node.type == "delay") {
		char key[32];
		snprintf(key, sizeof(key), "%p", (void *)&node);

		/* Parse delay duration from params (seconds as float) */
		float duration = 1.0f; /* default 1 second */
		if (!node.params.empty()) {
			char *end = nullptr;
			float val = strtof(node.params.c_str(), &end);
			if (end != node.params.c_str() && *end == '\0' &&
			    val > 0.0f)
				duration = val;
		}

		/* Initialize timer on first activation */
		if (isNewlyActive(state, &node))
			state.nodeTimers[key] = 0.0f;

		/* Accumulate time */
		state.nodeTimers[key] += deltaTime;

		if (state.nodeTimers[key] >= duration)
			return Status::success;

		return Status::running;
	}

	if (node.type == "debugPrint") {
		if (isNewlyActive(state, &node)) {
			std::cout << "[BT] " << node.name << std::endl;
			Ogre::LogManager::getSingleton().logMessage("[BT] " +
								    node.name);
		}
		return Status::success;
	}

	if (node.type == "setAnimationState") {
		if (isNewlyActive(state, &node) && m_animSystem) {
			/* Parse "stateMachineName/stateName" */
			Ogre::String name = node.name;
			size_t slash = name.find('/');
			if (slash != Ogre::String::npos) {
				Ogre::String smName = name.substr(0, slash);
				Ogre::String stName = name.substr(slash + 1);
				m_animSystem->setState(e, smName, stName, true);
			}
		}
		return Status::success;
	}

	if (node.type == "isAnimationEnded") {
		if (!m_animSystem)
			return Status::failure;
		/* node.name is the state machine name */
		Ogre::String current =
			m_animSystem->getCurrentState(e, node.name);
		if (current.empty())
			return Status::failure;

		/* Find the animation for this state and check time */
		Ogre::Entity *ent = AnimationTreeSystem::findAnimatedEntity(e);
		if (!ent || !ent->hasSkeleton())
			return Status::failure;

		/* Walk the animation tree to find current state's anim */
		if (e.has<BehaviorTreeComponent>()) {
			/* Actually we need AnimationTreeComponent, not
			 * BehaviorTreeComponent. Use flecs query */
		}

		/* Simpler approach: query AnimationTreeComponent */
		Ogre::AnimationState *as = nullptr;
		m_world.query<BehaviorTreeComponent>().each(
			[&](flecs::entity, BehaviorTreeComponent &) {});

		/* We need to find the animation state from the
		 * AnimationTreeComponent. Let's use a direct flecs
		 * lookup on the same entity. */
		if (e.has<AnimationTreeComponent>()) {
			auto &at = e.get<AnimationTreeComponent>();
			const AnimationTreeNode *smNode = nullptr;
			for (const auto &child : at.root.children) {
				if (child.type == "stateMachine" &&
				    child.name == node.name) {
					smNode = &child;
					break;
				}
				/* Recursively search */
				auto found = child.findStateMachine(node.name);
				if (found) {
					smNode = found;
					break;
				}
			}
			if (smNode) {
				for (const auto &st : smNode->children) {
					if (st.type == "state" &&
					    st.name == current) {
						auto leaf =
							st.findAnimationLeaf();
						if (leaf) {
							Ogre::AnimationStateSet *set =
								ent->getAllAnimationStates();
							if (set) {
								auto it =
									set->getAnimationStates()
										.find(leaf->animationName);
								if (it !=
								    set->getAnimationStates()
									    .end())
									as = it->second;
							}
						}
						break;
					}
				}
			}
		}

		if (as) {
			float t = as->getTimePosition();
			float len = as->getLength();
			if (len > 0.0f && t >= len * 0.95f)
				return Status::success;
		}
		return Status::failure;
	}

	/* --- Blackboard leaf nodes --- */
	GoapBlackboard *bb = nullptr;
	ActionDebug *actionDebug = nullptr;
	if (e.has<GoapBlackboard>()) {
		bb = const_cast<GoapBlackboard *>(&e.get<GoapBlackboard>());
	} else if (e.has<ActionDebug>()) {
		// Fallback: use ActionDebug's blackboard for test runs
		actionDebug = const_cast<ActionDebug *>(&e.get<ActionDebug>());
		bb = &actionDebug->blackboard;
	}

	if (node.type == "setBit") {
		if (bb) {
			int bitIdx = -1;
			if (parseBitIndex(node.name, bitIdx)) {
				bool val = true;
				if (!node.params.empty())
					val = (node.params != "0" &&
					       node.params != "false");
				bb->setBit(bitIdx, val);
			}
		}
		return Status::success;
	}

	if (node.type == "checkBit") {
		if (!bb)
			return Status::failure;
		int bitIdx = -1;
		if (!parseBitIndex(node.name, bitIdx))
			return Status::failure;
		return bb->getBit(bitIdx) ? Status::success : Status::failure;
	}

	if (node.type == "setValue") {
		if (bb && !node.params.empty()) {
			int iVal;
			float fVal;
			Ogre::Vector3 vVal;
			int vType;
			if (parseValueString(node.params, iVal, fVal, vVal,
					     vType)) {
				if (vType == 0)
					bb->setValue(node.name, iVal);
				else if (vType == 1)
					bb->setFloatValue(node.name, fVal);
				else
					bb->setVec3Value(node.name, vVal);
			}
		}
		return Status::success;
	}

	if (node.type == "checkValue") {
		if (!bb || node.params.empty())
			return Status::failure;
		Comparison cmp;
		if (!parseComparison(node.params, cmp))
			return Status::failure;

		// Look up actual value
		int actualType = -1;
		int actualInt = 0;
		float actualFloat = 0.0f;
		Ogre::Vector3 actualVec3 = Ogre::Vector3::ZERO;

		auto iti = bb->values.find(node.name);
		if (iti != bb->values.end()) {
			actualInt = iti->second;
			actualType = 0;
		}
		auto itf = bb->floatValues.find(node.name);
		if (itf != bb->floatValues.end()) {
			actualFloat = itf->second;
			actualType = 1;
		}
		auto itv = bb->vec3Values.find(node.name);
		if (itv != bb->vec3Values.end()) {
			actualVec3 = itv->second;
			actualType = 2;
		}
		if (actualType < 0)
			return Status::failure;

		bool ok = compareValues(cmp, actualInt, actualFloat, actualVec3,
					actualType);
		return ok ? Status::success : Status::failure;
	}

	if (node.type == "blackboardDump") {
		if (isNewlyActive(state, &node) && bb) {
			Ogre::String dump = bb->dump();
			std::cout << "[BT BlackboardDump]\n"
				  << dump << std::endl;
			Ogre::LogManager::getSingleton().logMessage(
				"[BT BlackboardDump]\n" + dump);
		}
		return Status::success;
	}

	/* --- Character physics disable/enable nodes --- */
	if (node.type == "disablePhysics") {
		if (isNewlyActive(state, &node) && m_charSystem) {
			m_charSystem->disablePhysics(e);
			std::cout << "[BT] disablePhysics: entity=" << e.id()
				  << std::endl;
		}
		return Status::success;
	}

	if (node.type == "enablePhysics") {
		if (isNewlyActive(state, &node) && m_charSystem) {
			m_charSystem->enablePhysics(e);
			std::cout << "[BT] enablePhysics: entity=" << e.id()
				  << std::endl;
		}
		return Status::success;
	}

	/* --- Item / Inventory nodes --- */
	/* These nodes need an ItemSystem instance. We look it up via
	 * the EditorApp singleton pattern (stored in SmartObjectSystem). */
	ItemSystem *itemSystem = nullptr;
	{
		SmartObjectSystem *soSystem = SmartObjectSystem::getInstance();
		if (soSystem)
			itemSystem = soSystem->getItemSystem();
	}

	if (node.type == "hasItem") {
		if (!itemSystem || !e.has<InventoryComponent>())
			return Status::failure;
		return itemSystem->hasItem(e, node.name) ? Status::success :
							   Status::failure;
	}

	if (node.type == "hasItemByName") {
		if (!itemSystem || !e.has<InventoryComponent>())
			return Status::failure;
		return itemSystem->hasItemByName(e, node.name) ?
			       Status::success :
			       Status::failure;
	}

	if (node.type == "countItem") {
		if (!itemSystem || !e.has<InventoryComponent>())
			return Status::failure;
		int required = 1;
		if (!node.params.empty()) {
			char *end = nullptr;
			long val = strtol(node.params.c_str(), &end, 10);
			if (end != node.params.c_str() && *end == '\0' &&
			    val > 0)
				required = static_cast<int>(val);
		}
		int count = itemSystem->countItem(e, node.name);
		return count >= required ? Status::success : Status::failure;
	}

	if (node.type == "pickupItem") {
		if (isNewlyActive(state, &node)) {
			if (!itemSystem || !e.has<InventoryComponent>())
				return Status::failure;

			// Find nearest ItemComponent entity matching the
			// optional itemId filter (node.name)
			flecs::entity nearestItem = flecs::entity::null();
			float nearestDist = std::numeric_limits<float>::max();

			Ogre::Vector3 charPos = Ogre::Vector3::ZERO;
			if (e.has<TransformComponent>()) {
				auto &trans = e.get<TransformComponent>();
				if (trans.node)
					charPos =
						trans.node
							->_getDerivedPosition();
				else
					charPos = trans.position;
			}

			m_world.query<ItemComponent, TransformComponent>().each(
				[&](flecs::entity itemEntity,
				    ItemComponent &itemComp,
				    TransformComponent &itemTrans) {
					// Skip items that are in containers
					// (they have no TransformComponent in world space)
					if (!itemTrans.node &&
					    itemTrans.position ==
						    Ogre::Vector3::ZERO)
						return;
					if (!node.name.empty() &&
					    itemComp.itemId != node.name)
						return;
					if (itemComp.disabled)
						return;

					Ogre::Vector3 itemPos =
						Ogre::Vector3::ZERO;
					if (itemTrans.node)
						itemPos =
							itemTrans.node
								->_getDerivedPosition();
					else
						itemPos = itemTrans.position;

					float dist = charPos.distance(itemPos);
					// Default pickup radius of 1.5 units
					if (dist < nearestDist && dist < 1.5f) {
						nearestDist = dist;
						nearestItem = itemEntity;
					}
				});

			if (nearestItem.is_alive()) {
				itemSystem->addItemEntityToInventory(
					e, nearestItem);
				std::cout << "[BT] pickupItem: picked up "
					  << nearestItem.id() << std::endl;
				return Status::success;
			}
			return Status::failure;
		}
		return Status::success;
	}

	if (node.type == "dropItem") {
		if (isNewlyActive(state, &node)) {
			if (!itemSystem || !e.has<InventoryComponent>())
				return Status::failure;

			int count = 1;
			if (!node.params.empty()) {
				char *end = nullptr;
				long val =
					strtol(node.params.c_str(), &end, 10);
				if (end != node.params.c_str() &&
				    *end == '\0' && val > 0)
					count = static_cast<int>(val);
			}

			// Find the item in inventory
			int slotIdx = -1;
			auto &inv = e.get<InventoryComponent>();
			for (int i = 0; i < (int)inv.slots.size(); i++) {
				if (!inv.slots[i].isEmpty() &&
				    inv.slots[i].itemId == node.name) {
					slotIdx = i;
					break;
				}
			}

			if (slotIdx < 0)
				return Status::failure;

			// Get drop position (in front of character)
			Ogre::Vector3 dropPos = Ogre::Vector3::ZERO;
			if (e.has<TransformComponent>()) {
				auto &trans = e.get<TransformComponent>();
				if (trans.node) {
					dropPos =
						trans.node
							->_getDerivedPosition();
					Ogre::Vector3 forward =
						trans.node
							->_getDerivedOrientation() *
						Ogre::Vector3(0, 0, -1);
					forward.y = 0;
					forward.normalise();
					dropPos += forward * 1.5f;
				} else {
					dropPos = trans.position;
				}
			}

			itemSystem->dropItem(e, slotIdx, dropPos, count);
			std::cout << "[BT] dropItem: dropped " << node.name
				  << " x" << count << std::endl;
			return Status::success;
		}
		return Status::success;
	}

	if (node.type == "useItem") {
		if (isNewlyActive(state, &node)) {
			if (!itemSystem || !e.has<InventoryComponent>())
				return Status::failure;

			// Find the item in inventory
			int slotIdx = -1;
			auto &inv = e.get<InventoryComponent>();
			for (int i = 0; i < (int)inv.slots.size(); i++) {
				if (!inv.slots[i].isEmpty() &&
				    inv.slots[i].itemId == node.name) {
					slotIdx = i;
					break;
				}
			}

			if (slotIdx < 0)
				return Status::failure;

			itemSystem->useItem(e, e, slotIdx);
			std::cout << "[BT] useItem: used " << node.name
				  << std::endl;
			return Status::success;
		}
		return Status::success;
	}

	if (node.type == "addItemToInventory") {
		if (isNewlyActive(state, &node)) {
			if (!itemSystem || !e.has<InventoryComponent>())
				return Status::failure;

			// Parse params: "itemId,count"
			std::string itemId = node.name.c_str();
			int count = 1;

			if (!node.params.empty()) {
				char *end = nullptr;
				long val = strtol(node.params.c_str(),
							  &end, 10);
				if (end != node.params.c_str() &&
				    *end == '\0' && val > 0)
					count = static_cast<int>(val);
			}

			bool added = itemSystem->addItemToInventory(e, itemId,
							    count);
			if (added) {
				std::cout << "[BT] addItemToInventory: added "
					  << itemId << " x" << count
					  << std::endl;
				return Status::success;
			}
			return Status::failure;
		}
		return Status::success;
	}

	/* --- Teleport to Smart Object child node --- */
	if (node.type == "teleportToChild") {
		if (isNewlyActive(state, &node)) {
			Ogre::String childName = node.name;
			if (childName.empty()) {
				std::cout << "[BT] teleportToChild: no child "
					     "name specified"
					  << std::endl;
				return Status::failure;
			}

			// Get the SmartObjectSystem to find the current
			// smart object target for this character
			SmartObjectSystem *soSystem =
				SmartObjectSystem::getInstance();
			if (!soSystem) {
				std::cout << "[BT] teleportToChild: "
					     "SmartObjectSystem not available"
					  << std::endl;
				return Status::failure;
			}

			// Find the smart object entity that this character
			// is currently interacting with. We query the
			// SmartObjectSystem's internal state by looking
			// for the nearest smart object with a matching
			// action in the character's blackboard.
			flecs::entity smartObject = flecs::entity::null();

			// Walk all SmartObjectComponent entities to find
			// the one this character is executing
			m_world.query<SmartObjectComponent, TransformComponent>()
				.each([&](flecs::entity so,
					  SmartObjectComponent &soComp,
					  TransformComponent &) {
					(void)soComp;
					// Check if this character has an
					// ActionDebug with a matching action
					// name from this smart object
					if (e.has<ActionDebug>()) {
						auto &debug =
							e.get<ActionDebug>();
						for (const auto &an :
						     soComp.actionNames) {
							if (an ==
							    debug.currentActionName) {
								smartObject =
									so;
								return;
							}
						}
					}
				});

			if (!smartObject.is_alive()) {
				std::cout << "[BT] teleportToChild: no smart "
					     "object found for character "
					  << e.id() << std::endl;
				return Status::failure;
			}

			// Find the child entity by name under the smart
			// object using Flecs' built-in child_of relationship
			flecs::entity childEntity = flecs::entity::null();
			smartObject.children([&](flecs::entity child) {
				if (childEntity.is_alive())
					return;
				if (child.has<EntityNameComponent>()) {
					auto &nameComp =
						child.get<EntityNameComponent>();
					if (nameComp.name == childName) {
						childEntity = child;
					}
				}
			});

			if (!childEntity.is_alive()) {
				std::cout << "[BT] teleportToChild: child '"
					  << childName
					  << "' not found under smart object "
					  << smartObject.id() << std::endl;
				return Status::failure;
			}

			// Get the child's absolute world transform
			Ogre::Vector3 targetPos = Ogre::Vector3::ZERO;
			Ogre::Quaternion targetRot = Ogre::Quaternion::IDENTITY;

			if (childEntity.has<TransformComponent>()) {
				auto &childTrans =
					childEntity.get<TransformComponent>();
				if (childTrans.node) {
					targetPos =
						childTrans.node
							->_getDerivedPosition();
					targetRot =
						childTrans.node
							->_getDerivedOrientation();
				} else {
					targetPos = childTrans.position;
					targetRot = childTrans.rotation;
				}
			}

			// Teleport the character to the target position
			if (e.has<TransformComponent>()) {
				auto &charTrans =
					e.get_mut<TransformComponent>();
				if (charTrans.node) {
					charTrans.node->setPosition(targetPos);
					charTrans.node->setOrientation(
						targetRot);
				}
				charTrans.position = targetPos;
				charTrans.rotation = targetRot;
				charTrans.markChanged();

				std::cout << "[BT] teleportToChild: teleported "
					  << "character " << e.id()
					  << " to child '" << childName
					  << "' at (" << targetPos.x << ", "
					  << targetPos.y << ", " << targetPos.z
					  << ")" << std::endl;
			} else {
				std::cout << "[BT] teleportToChild: character "
					  << e.id()
					  << " has no TransformComponent"
					  << std::endl;
				return Status::failure;
			}
		}
		return Status::success;
	}

	/* --- Disable item entity (for pickup / consumption) --- */
	if (node.type == "disableItem") {
		if (isNewlyActive(state, &node)) {
			// node.name can specify an instanceId override,
			// otherwise we look for an ItemComponent on this entity
			flecs::entity targetEntity = e;
			if (!node.name.empty()) {
				m_world.query<ItemComponent>().each(
					[&](flecs::entity itemEntity,
					    ItemComponent &itemComp) {
						if (itemComp.instanceId ==
							    node.name.c_str() &&
						    !targetEntity.is_alive()) {
							targetEntity = itemEntity;
						}
					});
			}

			if (!targetEntity.is_alive() ||
			    !targetEntity.has<ItemComponent>()) {
				std::cout << "[BT] disableItem: no item entity"
					  << std::endl;
				return Status::failure;
			}

			auto &item = targetEntity.get_mut<ItemComponent>();
			item.disabled = true;
			if (!item.instanceId.empty()) {
				ItemStateRegistry::getInstance().setDisabled(
					item.instanceId, true);
			}
			if (targetEntity.has<TransformComponent>()) {
				auto &trans = targetEntity.get_mut<
					TransformComponent>();
				if (trans.node)
					trans.node->setVisible(false);
			}

			std::cout << "[BT] disableItem: disabled item "
				  << targetEntity.id() << std::endl;
			return Status::success;
		}
		return Status::success;
	}

	/* --- Lua behavior tree node --- */
	if (node.type == "luaTask") {
		/* Call the Lua function via the forward-declared API.
		 * Returns: 0 = success, 1 = failure, 2 = running, -1 = error/not found */
		int result = editScene::callLuaBehaviorTreeNode(
			nullptr, node.name, e, node.params);
		if (result == 0)
			return Status::success;
		if (result == 1)
			return Status::failure;
		if (result == 2)
			return Status::running;
		/* -1 or other: log error and return failure */
		std::cout << "[BT] luaTask: node '" << node.name
			  << "' not registered or error" << std::endl;
		return Status::failure;
	}

	return Status::success;
}

void BehaviorTreeSystem::update(float deltaTime)
{
	/* --- BehaviorTreeComponent instances --- */
	m_world.query<BehaviorTreeComponent>().each(
		[this, deltaTime](flecs::entity e, BehaviorTreeComponent &bt) {
			if (!bt.enabled)
				return;

			auto &state = m_runnerStates[e.id()];
			state.currentActiveLeaves.clear();

			evaluateNode(bt.root, e, state, deltaTime);

			state.lastActiveLeaves = state.currentActiveLeaves;
			state.firstRun = false;
		});

	/* --- ActionDebug test runs --- */
	ActionDatabase *db = ActionDatabase::getSingletonPtr();
	if (!db)
		return;

	m_world.query<ActionDebug>().each(
		[this, db, deltaTime](flecs::entity e, ActionDebug &debug) {
			if (!debug.isRunning || debug.currentActionName.empty())
				return;

			const GoapAction *action =
				db->findAction(debug.currentActionName);
			if (!action)
				return;

			auto &state = m_actionDebugStates[e.id()];
			state.currentActiveLeaves.clear();

			/* Fresh run: reset leaf-tracking so
			 * isNewlyActive works again */
			if (debug.runTimer <= 0.0f) {
				state.lastActiveLeaves.clear();
				state.firstRun = true;
				state.nodeTimers.clear();
				state.treeResult = Status::running;
			}

			state.treeResult = evaluateNode(action->behaviorTree, e,
							state, deltaTime);

			state.lastActiveLeaves = state.currentActiveLeaves;
			state.firstRun = false;
			debug.runTimer += deltaTime;
		});
}

BehaviorTreeSystem::Status
BehaviorTreeSystem::evaluatePlayerAction(flecs::entity_t id,
					 const BehaviorTreeNode &root,
					 float deltaTime, bool reset)
{
	auto &state = m_playerActionStates[id];
	if (reset) {
		state.lastActiveLeaves.clear();
		state.firstRun = true;
		state.nodeTimers.clear();
		state.treeResult = Status::running;
	}
	state.currentActiveLeaves.clear();
	Status result =
		evaluateNode(root, m_world.entity(id), state, deltaTime);
	state.lastActiveLeaves = state.currentActiveLeaves;
	state.firstRun = false;
	state.treeResult = result;
	return result;
}
