#include "ItemSystem.hpp"
#include "ItemRegistry.hpp"
#include "ContainerStateRegistry.hpp"
#include "../EditorApp.hpp"
#include "BehaviorTreeSystem.hpp"
#include "../components/Item.hpp"
#include "../components/Inventory.hpp"
#include "../components/Transform.hpp"
#include "../components/EntityName.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/RuntimeMarker.hpp"
#include <OgreSceneNode.h>
#include <OgreLogManager.h>
#include <cmath>

ItemSystem::ItemSystem(flecs::world &world, Ogre::SceneManager *sceneMgr,
		       EditorApp *editorApp, BehaviorTreeSystem *btSystem)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_editorApp(editorApp)
	, m_btSystem(btSystem)
{
}

ItemSystem::~ItemSystem() = default;

// --- Inventory manipulation API ---

bool ItemSystem::addItemToInventory(flecs::entity inventoryEntity,
				    const std::string &itemId, int stackSize)
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;
	if (itemId.empty() || stackSize <= 0)
		return false;

	auto &inv = inventoryEntity.get_mut<InventoryComponent>();
	int maxStack = ItemRegistry::getSingleton().getMaxStackSize(itemId);

	// Try to stack with existing items of the same itemId
	for (int i = 0; i < (int)inv.slots.size(); i++) {
		auto &slot = inv.slots[i];
		if (!slot.isEmpty() && slot.itemId == itemId &&
		    slot.stackSize < maxStack) {
			int space = maxStack - slot.stackSize;
			int add = std::min(space, stackSize);
			slot.stackSize += add;
			stackSize -= add;
			if (stackSize <= 0) {
				inv.recalculateWeight();
				return true;
			}
		}
	}

	// Find empty slot for remaining items
	while (stackSize > 0) {
		int slotIdx = inv.findEmptySlot();
		if (slotIdx < 0)
			return false; // Inventory full

		// Ensure slots vector is large enough
		while ((int)inv.slots.size() <= slotIdx)
			inv.slots.emplace_back();

		auto &slot = inv.slots[slotIdx];
		slot.itemEntity = 0;
		slot.itemId = itemId;

		int add = std::min(stackSize, maxStack);
		slot.stackSize = add;
		stackSize -= add;
	}

	inv.recalculateWeight();
	return true;
}

bool ItemSystem::addItemEntityToInventory(flecs::entity inventoryEntity,
					  flecs::entity itemEntity)
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;
	if (!itemEntity.is_alive() || !itemEntity.has<ItemComponent>())
		return false;

	auto &item = itemEntity.get_mut<ItemComponent>();

	bool result = addItemToInventory(inventoryEntity, item.itemId,
					 item.stackSize);

	if (result) {
		// Store the world entity reference in the first matching slot
		auto &inv = inventoryEntity.get_mut<InventoryComponent>();
		for (auto &slot : inv.slots) {
			if (slot.itemId == item.itemId && slot.itemEntity == 0) {
				slot.itemEntity = itemEntity.id();
				break;
			}
		}

		// Hide the world entity
		if (itemEntity.has<TransformComponent>()) {
			auto &trans = itemEntity.get_mut<TransformComponent>();
			if (trans.node)
				trans.node->setVisible(false);
		}
	}

	return result;
}

int ItemSystem::removeItemFromInventory(flecs::entity inventoryEntity,
					const std::string &itemId, int count)
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return 0;

	auto &inv = inventoryEntity.get_mut<InventoryComponent>();
	int removed = 0;

	for (int i = (int)inv.slots.size() - 1; i >= 0 && count > 0; i--) {
		auto &slot = inv.slots[i];
		if (slot.isEmpty() || slot.itemId != itemId)
			continue;

		int remove = std::min(count, slot.stackSize);
		slot.stackSize -= remove;
		count -= remove;
		removed += remove;

		if (slot.stackSize <= 0)
			slot.clear();
	}

	if (removed > 0)
		inv.recalculateWeight();

	return removed;
}

bool ItemSystem::removeItemFromSlot(flecs::entity inventoryEntity,
				    int slotIndex, int count)
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;

	auto &inv = inventoryEntity.get_mut<InventoryComponent>();
	if (slotIndex < 0 || slotIndex >= (int)inv.slots.size())
		return false;

	auto &slot = inv.slots[slotIndex];
	if (slot.isEmpty())
		return false;

	int remove = std::min(count, slot.stackSize);
	slot.stackSize -= remove;

	if (slot.stackSize <= 0)
		slot.clear();

	inv.recalculateWeight();
	return true;
}

bool ItemSystem::transferItem(flecs::entity fromInventory, int slotIndex,
			      flecs::entity toInventory, int count)
{
	if (!fromInventory.is_alive() ||
	    !fromInventory.has<InventoryComponent>())
		return false;
	if (!toInventory.is_alive() || !toInventory.has<InventoryComponent>())
		return false;

	auto &fromInv = fromInventory.get_mut<InventoryComponent>();
	if (slotIndex < 0 || slotIndex >= (int)fromInv.slots.size())
		return false;

	auto &slot = fromInv.slots[slotIndex];
	if (slot.isEmpty())
		return false;

	int transferCount = std::min(count, slot.stackSize);

	// Add to target inventory
	bool added = addItemToInventory(toInventory, slot.itemId,
					transferCount);

	if (!added)
		return false;

	// Remove from source
	slot.stackSize -= transferCount;
	if (slot.stackSize <= 0)
		slot.clear();

	fromInv.recalculateWeight();
	return true;
}

bool ItemSystem::hasItem(flecs::entity inventoryEntity,
			 const std::string &itemId) const
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;

	return inventoryEntity.get<InventoryComponent>().hasItem(itemId);
}

bool ItemSystem::hasItemByName(flecs::entity inventoryEntity,
			       const std::string &itemName) const
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;

	// Look up itemId by display name in registry
	std::string foundId;
	for (const auto &pair : ItemRegistry::getSingleton().getDefinitions()) {
		if (pair.second.itemName == itemName) {
			foundId = pair.second.itemId;
			break;
		}
	}
	if (foundId.empty())
		return false;

	return inventoryEntity.get<InventoryComponent>().hasItem(foundId);
}

int ItemSystem::countItem(flecs::entity inventoryEntity,
			  const std::string &itemId) const
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return 0;

	return inventoryEntity.get<InventoryComponent>().countItem(itemId);
}

bool ItemSystem::dropItem(flecs::entity inventoryEntity, int slotIndex,
			  const Ogre::Vector3 &worldPosition, int count)
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;

	auto &inv = inventoryEntity.get_mut<InventoryComponent>();
	if (slotIndex < 0 || slotIndex >= (int)inv.slots.size())
		return false;

	auto &slot = inv.slots[slotIndex];
	if (slot.isEmpty())
		return false;

	int dropCount = std::min(count, slot.stackSize);
	const std::string itemName =
		ItemRegistry::getSingleton().getItemName(slot.itemId);

	// If the item has a world entity reference, show it
	if (slot.itemEntity != 0) {
		flecs::entity itemEntity = m_world.entity(slot.itemEntity);
		if (itemEntity.is_alive() && itemEntity.has<ItemComponent>()) {
			auto &item = itemEntity.get_mut<ItemComponent>();
			item.stackSize = dropCount;

			if (itemEntity.has<TransformComponent>()) {
				auto &trans =
					itemEntity.get_mut<TransformComponent>();
				trans.position = worldPosition;
				if (trans.node) {
					trans.node->setPosition(worldPosition);
					trans.node->setVisible(true);
				}
				trans.markChanged();
			}
		}
	} else {
		// Create a new world entity for the dropped item
		flecs::entity itemEntity = m_world.entity();
		itemEntity.add<RuntimeMarkerComponent>();
		itemEntity.set<ItemComponent>(ItemComponent(slot.itemId, dropCount));

		// Create a scene node for the dropped item
		Ogre::SceneNode *node =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode(
				worldPosition);
		TransformComponent trans;
		trans.node = node;
		trans.position = worldPosition;
		itemEntity.set<TransformComponent>(trans);

		EntityNameComponent nameComp;
		nameComp.name = itemName + "_dropped";
		itemEntity.set<EntityNameComponent>(nameComp);
	}

	// Remove from inventory
	slot.stackSize -= dropCount;
	if (slot.stackSize <= 0)
		slot.clear();

	inv.recalculateWeight();
	return true;
}

bool ItemSystem::useItem(flecs::entity characterEntity,
			 flecs::entity inventoryEntity, int slotIndex)
{
	if (!inventoryEntity.is_alive() ||
	    !inventoryEntity.has<InventoryComponent>())
		return false;

	auto &inv = inventoryEntity.get<InventoryComponent>();
	if (slotIndex < 0 || slotIndex >= (int)inv.slots.size())
		return false;

	const auto &slot = inv.slots[slotIndex];
	if (slot.isEmpty())
		return false;

	std::string useAction =
		ItemRegistry::getSingleton().getUseActionName(slot.itemId);
	if (useAction.empty())
		return false;

	// Execute the use action via behavior tree
	if (m_btSystem && characterEntity.is_alive()) {
		ActionDatabase *db = ActionDatabase::getSingletonPtr();
		if (db) {
			const GoapAction *action = db->findAction(useAction);
			if (action) {
				m_btSystem->evaluatePlayerAction(
					characterEntity.id(),
					action->behaviorTree, 0.016f, true);
				return true;
			}
		}
	}

	return false;
}

bool ItemSystem::pickupItem(flecs::entity characterEntity,
			    flecs::entity itemEntity)
{
	if (!characterEntity.is_alive() ||
	    !characterEntity.has<InventoryComponent>())
		return false;
	if (!itemEntity.is_alive() || !itemEntity.has<ItemComponent>())
		return false;

	bool result = addItemEntityToInventory(characterEntity, itemEntity);
	if (result) {
		Ogre::LogManager::getSingleton().logMessage(
			"[ItemSystem] Picked up: " +
			ItemRegistry::getSingleton().getItemName(
				itemEntity.get<ItemComponent>().itemId));
	}
	return result;
}
