#include "ActuatorSystem.hpp"
#include "../EditorApp.hpp"
#include "BehaviorTreeSystem.hpp"
#include "ItemSystem.hpp"
#include "ItemRegistry.hpp"
#include "ItemStateRegistry.hpp"
#include "../components/Actuator.hpp"
#include "../components/Item.hpp"
#include "../components/Inventory.hpp"
#include "../components/PlayerController.hpp"
#include "../components/Transform.hpp"
#include "../components/Character.hpp"
#include "../components/ActionDatabase.hpp"
#include "../components/EntityName.hpp"
#include "../camera/EditorCamera.hpp"
#include <OgreCamera.h>
#include <OgreSceneNode.h>
#include <OgreLogManager.h>
#include <imgui.h>
#include <cmath>

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

ActuatorSystem::~ActuatorSystem() = default;

Ogre::Vector2 ActuatorSystem::projectToScreen(const Ogre::Vector3 &worldPoint)
{
	if (!m_editorApp)
		return Ogre::Vector2(-1, -1);

	EditorCamera *editorCam = m_editorApp->getEditorCamera();
	if (!editorCam)
		return Ogre::Vector2(-1, -1);
	Ogre::Camera *camera = editorCam->getCamera();
	if (!camera)
		return Ogre::Vector2(-1, -1);

	ImVec2 vpSize = ImGui::GetMainViewport()->Size;
	float width = vpSize.x;
	float height = vpSize.y;

	// 1. Convert to camera space (OGRE camera looks down -Z)
	Ogre::Vector3 eyeSpacePoint = camera->getViewMatrix() * worldPoint;
	if (eyeSpacePoint.z >= 0.0f)
		return Ogre::Vector2(-1, -1);

	// 2. Project to homogeneous clip space using Vector4 to preserve W
	Ogre::Vector4 clipSpacePoint = camera->getProjectionMatrix() *
		Ogre::Vector4(eyeSpacePoint.x, eyeSpacePoint.y,
			      eyeSpacePoint.z, 1.0f);
	if (clipSpacePoint.w <= 0.0f)
		return Ogre::Vector2(-1, -1);

	// 3. Perspective divide to get NDC [-1, 1]
	float ndcX = clipSpacePoint.x / clipSpacePoint.w;
	float ndcY = clipSpacePoint.y / clipSpacePoint.w;

	// 4. Convert NDC to screen space [0, 1], flipping Y for ImGui
	float screenX = (ndcX * 0.5f) + 0.5f;
	float screenY = 1.0f - ((ndcY * 0.5f) + 0.5f);

	// 5. Reject if outside viewport bounds
	if (screenX < 0.0f || screenX > 1.0f ||
	    screenY < 0.0f || screenY > 1.0f)
		return Ogre::Vector2(-1, -1);

	// 6. Map to actual pixel dimensions
	return Ogre::Vector2(screenX * width, screenY * height);
}

bool ActuatorSystem::isInRange(const Ogre::Vector3 &charPos,
			       const Ogre::Vector3 &objPos, float radius,
			       float height)
{
	Ogre::Vector3 diff = charPos - objPos;
	float xzDist = std::sqrt(diff.x * diff.x + diff.z * diff.z);
	if (xzDist > radius)
		return false;
	float yDiff = std::abs(diff.y);
	if (yDiff > height)
		return false;
	return true;
}

void ActuatorSystem::setPlayerInputLocked(bool locked)
{
	m_world.query<PlayerControllerComponent>().each(
		[&](flecs::entity, PlayerControllerComponent &pc) {
			pc.inputLocked = locked;
		});
}

void ActuatorSystem::executeAction(flecs::entity character,
				   flecs::entity actuatorEntity,
				   const Ogre::String &actionName)
{
	if (!character.is_alive() || !actuatorEntity.is_alive())
		return;

	if (!actuatorEntity.has<ActuatorComponent>())
		return;

	auto &actuator = actuatorEntity.get_mut<ActuatorComponent>();
	actuator.isExecuting = true;

	m_executingActuatorId = actuatorEntity.id();
	m_executingCharacterId = character.id();
	m_executingActionName = actionName;
	m_actionFirstFrame = true;

	// Lock player input while action executes
	setPlayerInputLocked(true);

	Ogre::LogManager::getSingleton().logMessage(
		"[ActuatorSystem] Executing action: " + actionName);
}

void ActuatorSystem::executeItemAction(flecs::entity character,
					       flecs::entity itemEntity,
					       const Ogre::String &actionName)
{
	if (!character.is_alive() || !itemEntity.is_alive())
		return;

	if (!itemEntity.has<ItemComponent>())
		return;

	m_executingActuatorId = itemEntity.id();
	m_executingCharacterId = character.id();
	m_executingActionName = actionName;
	m_actionFirstFrame = true;

	// Lock player input while action executes
	setPlayerInputLocked(true);

	Ogre::LogManager::getSingleton().logMessage(
		"[ActuatorSystem] Executing item action: " + actionName);
}

bool ActuatorSystem::isActionComplete(flecs::entity character, float deltaTime)
{
	if (!m_btSystem || !character.is_alive())
		return true;

	if (m_executingActionName.empty())
		return true;

	// Look up the action in the singleton database to get its behavior tree
	ActionDatabase *db = ActionDatabase::getSingletonPtr();
	if (!db)
		return true;

	const GoapAction *action = db->findAction(m_executingActionName);
	if (!action)
		return true;

	// Evaluate the behavior tree directly (no ActionDebug)
	auto status = m_btSystem->evaluatePlayerAction(character.id(),
						       action->behaviorTree,
						       deltaTime,
						       m_actionFirstFrame);
	m_actionFirstFrame = false;

	return status != BehaviorTreeSystem::Status::running;
}

void ActuatorSystem::drawActionMenu(flecs::entity actuatorEntity)
{
	if (!actuatorEntity.is_alive() ||
	    !actuatorEntity.has<ActuatorComponent>())
		return;

	auto &actuator = actuatorEntity.get<ActuatorComponent>();

	ImVec2 center = ImGui::GetMainViewport()->GetCenter();
	ImGui::SetNextWindowPos(center, ImGuiCond_Appearing,
				ImVec2(0.5f, 0.5f));
	ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Appearing);

	ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize |
				 ImGuiWindowFlags_NoCollapse |
				 ImGuiWindowFlags_AlwaysAutoResize;

	if (ImGui::Begin("Select Action", nullptr, flags)) {
		ImGui::Text("Select an action:");
		ImGui::Separator();

		for (const auto &name : actuator.actionNames) {
			if (name.empty())
				continue;
			if (ImGui::Button(
				    name.c_str(),
				    ImVec2(ImGui::GetContentRegionAvail().x,
					   0))) {
				m_pendingActionName = name;
			}
		}

		ImGui::Separator();
		if (ImGui::Button("Cancel", ImVec2(120, 0))) {
			m_menuOpen = false;
			m_menuActuatorId = 0;
			m_eHoldTime = 0.0f;
			m_eWasHeld = false;
			if (m_editorApp)
				m_editorApp->setWindowGrab(true);
		}
	}
	ImGui::End();
}

void ActuatorSystem::update(float deltaTime)
{
	// Update cooldown timers for all actuators
	m_world.query<ActuatorComponent>().each(
		[&](flecs::entity, ActuatorComponent &actuator) {
			if (actuator.cooldownTimer > 0.0f) {
				actuator.cooldownTimer -= deltaTime;
				if (actuator.cooldownTimer < 0.0f)
					actuator.cooldownTimer = 0.0f;
			}
		});

	// Only run in game mode while playing
	if (!m_editorApp ||
	    m_editorApp->getGameMode() != EditorApp::GameMode::Game ||
	    m_editorApp->getGamePlayState() !=
		    EditorApp::GamePlayState::Playing)
		return;

	// Check if an action is currently executing
	if (m_executingActuatorId != 0) {
		flecs::entity character =
			m_world.entity(m_executingCharacterId);
		if (isActionComplete(character, deltaTime)) {
			// Action finished - start cooldown
			flecs::entity actuator =
				m_world.entity(m_executingActuatorId);
			if (actuator.is_alive() &&
			    actuator.has<ActuatorComponent>()) {
				auto &ac =
					actuator.get_mut<ActuatorComponent>();
				ac.isExecuting = false;
				float cooldown = 1.5f;
				m_world.query<PlayerControllerComponent>().each(
					[&](flecs::entity,
					    PlayerControllerComponent &pc) {
						cooldown = pc.actuatorCooldown;
					});
				ac.cooldownTimer = cooldown;
			}
			m_executingActuatorId = 0;
			m_executingCharacterId = 0;
			m_executingActionName.clear();

			// Unlock player input
			setPlayerInputLocked(false);
		}
		// Don't collect or draw anything while executing
		m_visibleActuators.clear();
		m_targetIndex = -1;
		m_labelText.clear();
		return;
	}

	// Find player character (resolve spawned instance if controller
	// targets a spawner).
	flecs::entity playerCharacter = flecs::entity::null();
	if (m_editorApp)
		playerCharacter = m_editorApp->getPlayerCharacterEntity();

	float actuatorDistance = 25.0f;
	float actuatorCooldown = 1.5f;
	m_circleColor = ImVec4(0.0f, 0.4f, 1.0f, 1.0f);
	m_distantRadius = 8.0f;
	m_nearRadius = 14.0f;
	m_labelFontSize = 14.0f;

	m_world.query<PlayerControllerComponent>().each([&](flecs::entity,
								    PlayerControllerComponent
									    &pc) {
		actuatorDistance = pc.actuatorDistance;
		actuatorCooldown = pc.actuatorCooldown;
		m_circleColor = ImVec4(pc.actuatorColor.x,
				       pc.actuatorColor.y,
				       pc.actuatorColor.z, 1.0f);
		m_distantRadius = pc.distantCircleRadius;
		m_nearRadius = pc.nearCircleRadius;
		m_labelFontSize = pc.actuatorLabelFontSize;
	});

	if (!playerCharacter.is_alive() ||
	    !playerCharacter.has<TransformComponent>())
		return;

	Ogre::Vector3 charPos = playerCharacter.get<TransformComponent>()
					.node->_getDerivedPosition();

	// Collect actuators within distance
	m_visibleActuators.clear();
	std::vector<ScreenActuator> inRangeActuators;

	// --- Collect ActuatorComponent entities ---
	m_world.query<ActuatorComponent, TransformComponent>().each(
		[&](flecs::entity e, ActuatorComponent &actuator,
		    TransformComponent &trans) {
			// Skip if on cooldown or executing
			if (actuator.cooldownTimer > 0.0f ||
			    actuator.isExecuting)
				return;

			if (!trans.node)
				return;

			Ogre::Vector3 objPos =
				trans.node->_getDerivedPosition();
			float dist = charPos.distance(objPos);
			if (dist > actuatorDistance)
				return;

			Ogre::Vector2 screenPos = projectToScreen(objPos);
			if (screenPos.x < 0)
				return;

			ScreenActuator sa;
			sa.entity = e;
			sa.screenPos = screenPos;
			sa.distance = dist;
			ImVec2 vpSize = ImGui::GetMainViewport()->Size;
			sa.distToScreenCenter =
				std::abs(screenPos.x - vpSize.x * 0.5f);

			m_visibleActuators.push_back(sa);

			if (isInRange(charPos, objPos, actuator.radius,
				      actuator.height)) {
				inRangeActuators.push_back(sa);
			}
		});

	// --- Collect ItemComponent entities (no ActuatorComponent) ---
	m_world.query<ItemComponent, TransformComponent>().each(
		[&](flecs::entity e, ItemComponent &item,
		    TransformComponent &trans) {
			// Skip items that also have an ActuatorComponent
			// (those are handled above as actuators)
			if (e.has<ActuatorComponent>())
				return;
			if (item.disabled)
				return;

			if (!trans.node)
				return;

			Ogre::Vector3 objPos =
				trans.node->_getDerivedPosition();
			float dist = charPos.distance(objPos);
			if (dist > actuatorDistance)
				return;

			Ogre::Vector2 screenPos = projectToScreen(objPos);
			if (screenPos.x < 0)
				return;

			ScreenActuator sa;
			sa.entity = e;
			sa.screenPos = screenPos;
			sa.distance = dist;
			ImVec2 vpSize = ImGui::GetMainViewport()->Size;
			sa.distToScreenCenter =
				std::abs(screenPos.x - vpSize.x * 0.5f);

			m_visibleActuators.push_back(sa);

			// Items use a default pickup range (same as actuator defaults)
			if (isInRange(charPos, objPos, 1.5f, 1.8f)) {
				inRangeActuators.push_back(sa);
			}
		});

	// Determine target actuator for interaction
	m_targetIndex = -1;
	if (!inRangeActuators.empty()) {
		size_t bestIdx = 0;
		for (size_t i = 1; i < inRangeActuators.size(); i++) {
			if (inRangeActuators[i].distToScreenCenter <
			    inRangeActuators[bestIdx].distToScreenCenter) {
				bestIdx = i;
			} else if (inRangeActuators[i].distToScreenCenter ==
				   inRangeActuators[bestIdx].distToScreenCenter) {
				if (inRangeActuators[i].screenPos.y >
				    inRangeActuators[bestIdx].screenPos.y)
					bestIdx = i;
			}
		}
		// Find the index in m_visibleActuators
		for (size_t i = 0; i < m_visibleActuators.size(); i++) {
			if (m_visibleActuators[i].entity.id() ==
			    inRangeActuators[bestIdx].entity.id()) {
				m_targetIndex = static_cast<int>(i);
				break;
			}
		}
	}

	// Build label text
	m_labelText.clear();
	if (m_targetIndex >= 0) {
		flecs::entity targetEntity =
			m_visibleActuators[m_targetIndex].entity;
		if (targetEntity.is_alive()) {
			if (targetEntity.has<ActuatorComponent>()) {
				auto &actuator =
					targetEntity.get<ActuatorComponent>();
				if (actuator.actionNames.size() == 1 &&
				    !actuator.actionNames[0].empty()) {
					m_labelText =
						"E " + actuator.actionNames[0];
				} else if (actuator.actionNames.size() > 1) {
					m_labelText = "E";
				}
			} else if (targetEntity.has<ItemComponent>()) {
				// Show "E - Pick up [ItemName]" for items
				auto &item = targetEntity.get<ItemComponent>();
				std::string displayName = ItemRegistry::getSingleton().getItemName(
					item.itemId);
				if (displayName.empty())
					displayName = item.itemId;
				m_labelText = "E Pick up " + displayName;
			}
		}
	}

	// Handle input
	GameInputState &input = m_editorApp->getGameInputState();

	// Handle menu state first
	if (m_menuOpen) {
		flecs::entity menuActuator = m_world.entity(m_menuActuatorId);
		// Menu rendering happens in render()

		if (!m_pendingActionName.empty()) {
			executeAction(playerCharacter, menuActuator,
				      m_pendingActionName);
			m_pendingActionName.clear();
			m_menuOpen = false;
			m_menuActuatorId = 0;
			m_eHoldTime = 0.0f;
			m_eWasHeld = false;
			if (m_editorApp)
				m_editorApp->setWindowGrab(true);
		}

		if (!input.e && m_eWasHeld) {
			m_menuOpen = false;
			m_menuActuatorId = 0;
			m_eHoldTime = 0.0f;
			m_eWasHeld = false;
			if (m_editorApp)
				m_editorApp->setWindowGrab(true);
		}
		return;
	}

	// Handle E key
	if (m_targetIndex >= 0 && input.e) {
		flecs::entity targetEntity =
			m_visibleActuators[m_targetIndex].entity;

		if (targetEntity.has<ActuatorComponent>()) {
			auto &actuator = targetEntity.get<ActuatorComponent>();
			m_eHoldTime += deltaTime;

			if (actuator.actionNames.size() == 1 &&
			    !actuator.actionNames[0].empty()) {
				if (input.ePressed) {
					executeAction(playerCharacter,
						      targetEntity,
						      actuator.actionNames[0]);
					m_eHoldTime = 0.0f;
				}
			} else if (actuator.actionNames.size() > 1) {
				if (m_eHoldTime > 0.3f && !m_menuOpen) {
					m_menuOpen = true;
					m_menuActuatorId = targetEntity.id();
					m_eWasHeld = true;
					if (m_editorApp)
						m_editorApp->setWindowGrab(
							false);
				}
			}
		} else if (targetEntity.has<ItemComponent>() &&
			   input.ePressed) {
			auto &item = targetEntity.get_mut<ItemComponent>();

			if (!item.action.empty()) {
				// Execute the item's action via behavior tree
				executeItemAction(playerCharacter,
						  targetEntity,
						  item.action);
			} else {
				// Pick up item
				if (m_itemSystem) {
					m_itemSystem->pickupItem(
						playerCharacter,
						targetEntity);
				}
				// Disable the item and persist state
				item.disabled = true;
				if (!item.instanceId.empty()) {
					ItemStateRegistry::getInstance()
						.setDisabled(item.instanceId,
							     true);
				}
				// Hide the entity in the scene
				if (targetEntity.has<
					    TransformComponent>()) {
					auto &trans = targetEntity
							      .get_mut<
							      TransformComponent>();
					if (trans.node) {
						trans.node->setVisible(
							false);
					}
				}
			}
			m_eHoldTime = 0.0f;
		}
	} else {
		m_eHoldTime = 0.0f;
		m_eWasHeld = false;
	}
}

void ActuatorSystem::render()
{
	// Only run in game mode while playing
	if (!m_editorApp ||
	    m_editorApp->getGameMode() != EditorApp::GameMode::Game ||
	    m_editorApp->getGamePlayState() !=
		    EditorApp::GamePlayState::Playing)
		return;

	ImDrawList *drawList = ImGui::GetBackgroundDrawList();
	if (!drawList)
		return;

	ImColor defaultColor(m_circleColor);
	ImColor targetColor(ImVec4(std::min(m_circleColor.x + 0.2f, 1.0f),
				   std::min(m_circleColor.y + 0.3f, 1.0f),
				   std::min(m_circleColor.z + 0.0f, 1.0f),
				   1.0f));

	for (size_t i = 0; i < m_visibleActuators.size(); i++) {
		bool isTarget = (static_cast<int>(i) == m_targetIndex);
		float circleRadius = isTarget ? m_nearRadius : m_distantRadius;
		ImColor circleCol = isTarget ? targetColor : defaultColor;

		ImVec2 center(m_visibleActuators[i].screenPos.x,
			      m_visibleActuators[i].screenPos.y);
		drawList->AddCircleFilled(center, circleRadius, circleCol);
		drawList->AddCircle(center, circleRadius,
				    IM_COL32(255, 255, 255, 180), 0, 2.0f);
	}

	// Draw label for target
	if (m_targetIndex >= 0 && !m_labelText.empty()) {
		float circleRadius = m_nearRadius;
		ImVec2 center(m_visibleActuators[m_targetIndex].screenPos.x,
			      m_visibleActuators[m_targetIndex].screenPos.y);

		ImFont *font = ImGui::GetFont();
		ImVec2 textSize = font->CalcTextSizeA(
			m_labelFontSize, FLT_MAX, -1.0f, m_labelText.c_str());

		ImVec2 textPos(center.x - (textSize.x * 0.5f),
			       center.y + circleRadius + 6.0f);

		// Shadow
		drawList->AddText(font, m_labelFontSize,
				  ImVec2(textPos.x + 1, textPos.y + 1),
				  IM_COL32(0, 0, 0, 200), m_labelText.c_str());
		// Text
		drawList->AddText(font, m_labelFontSize, textPos,
				  IM_COL32(255, 255, 255, 255),
				  m_labelText.c_str());
	}

	// Draw action menu if open
	if (m_menuOpen) {
		flecs::entity menuActuator = m_world.entity(m_menuActuatorId);
		drawActionMenu(menuActuator);
	}
}
