#include "PlayerControllerSystem.hpp"
#include "../EditorApp.hpp"
#include "../components/PlayerController.hpp"
#include "../components/InWater.hpp"
#include "../components/Character.hpp"
#include "../components/Transform.hpp"
#include "../components/EntityName.hpp"
#include "../components/CharacterSpawner.hpp"
#include "../components/PathFollowing.hpp"
#include "../components/BehaviorTree.hpp"
#include "AnimationTreeSystem.hpp"
#include "CharacterSlotSystem.hpp"
#include "CharacterSpawnerSystem.hpp"
#include "../camera/EditorCamera.hpp"
#include <OgreEntity.h>
#include <OgreCamera.h>
#include <OgreLogManager.h>
#include <OgreSceneNode.h>
#include <OgreSkeletonInstance.h>
#include <cmath>

#ifdef EDITSCENE_VERBOSE_DEBUG
#define EDITSCENE_LOG_PC(msg) \
	Ogre::LogManager::getSingleton().logMessage(msg)
#else
#define EDITSCENE_LOG_PC(msg)
#endif

PlayerControllerSystem::PlayerControllerSystem(flecs::world &world,
					       Ogre::SceneManager *sceneMgr,
					       EditorApp *editorApp)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_editorApp(editorApp)
	, m_spawnerSystem(editorApp ? editorApp->getCharacterSpawnerSystem() :
				      nullptr)
{
}

PlayerControllerSystem::~PlayerControllerSystem()
{
	for (auto &pair : m_states) {
		shutdownController(pair.second);
	}
	m_states.clear();
}

void PlayerControllerSystem::shutdownController(ControllerState &state)
{
	if (state.targetEntity.is_alive())
		state.targetEntity.remove<PlayerControlledComponent>();
	state.targetEntity = flecs::entity::null();

	if (state.pivotNode) {
		m_sceneMgr->destroySceneNode(state.pivotNode);
		state.pivotNode = nullptr;
	}
	if (state.goalNode) {
		m_sceneMgr->destroySceneNode(state.goalNode);
		state.goalNode = nullptr;
	}
	if (state.spawnerEntity.is_alive() && m_spawnerSystem) {
		m_spawnerSystem->unlockSpawner(state.spawnerEntity);
		state.spawnerEntity = flecs::entity::null();
	}
	state.initialized = false;
}

flecs::entity PlayerControllerSystem::findTargetEntity(const Ogre::String &name)
{
	if (name.empty())
		return flecs::entity::null();

	flecs::entity result = flecs::entity::null();
	m_world.query<EntityNameComponent>().each(
		[&](flecs::entity e, EntityNameComponent &en) {
			if (result.is_alive())
				return;
			if (en.name == name)
				result = e;
		});
	return result;
}

void PlayerControllerSystem::initController(flecs::entity controllerEntity,
					    PlayerControllerComponent &pc,
					    ControllerState &state)
{
	(void)controllerEntity;

	/* Release any previous spawner lock before re-resolving the target. */
	if (state.spawnerEntity.is_alive() && m_spawnerSystem) {
		m_spawnerSystem->unlockSpawner(state.spawnerEntity);
	}
	state.spawnerEntity = flecs::entity::null();

	flecs::entity namedEntity = findTargetEntity(pc.targetCharacterName);
	flecs::entity target = namedEntity;

	if (namedEntity.is_alive() &&
	    namedEntity.has<CharacterSpawnerComponent>()) {
		state.spawnerEntity = namedEntity;
		if (m_spawnerSystem) {
			m_spawnerSystem->lockSpawner(namedEntity);
			target = m_spawnerSystem->getSpawnedCharacter(
				namedEntity);
			if (!target.is_alive()) {
				m_spawnerSystem->spawn(namedEntity);
				target = m_spawnerSystem->getSpawnedCharacter(
					namedEntity);
			}
		} else {
			target = flecs::entity::null();
		}
	}

	if (state.targetEntity.is_alive() && state.targetEntity != target)
		state.targetEntity.remove<PlayerControlledComponent>();

	state.targetEntity = target;
	if (!state.targetEntity.is_alive())
		return;

	state.targetEntity.add<PlayerControlledComponent>();

	if (!state.pivotNode) {
		state.pivotNode =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode();
	}
	if (!state.goalNode) {
		state.goalNode =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode();
	}

	// Initialize camera to start behind character (180 degrees yaw)
	state.yaw = 180.0f;
	state.pitch = 0.0f;

	state.initialized = true;
	state.faceHidden = false;
}

void PlayerControllerSystem::update(float deltaTime)
{
	if (!m_editorApp ||
	    m_editorApp->getGameMode() != EditorApp::GameMode::Game ||
	    m_editorApp->getGamePlayState() !=
		    EditorApp::GamePlayState::Playing) {
		/* Clean up any active controller state (and the PlayerControlled
		 * tag) when leaving game mode / play state so editor-mode
		 * ActionDebug tests can run on the same character. */
		for (auto &pair : m_states)
			shutdownController(pair.second);
		m_states.clear();
		return;
	}

	m_world.query<PlayerControllerComponent>().each([&](flecs::entity e,
							    PlayerControllerComponent
								    &pc) {
		auto it = m_states.find(e.id());
		if (it == m_states.end()) {
			ControllerState newState;
			initController(e, pc, newState);
			it = m_states.insert({ e.id(), newState }).first;
		}

		ControllerState &state = it->second;

		// Re-resolve target if name changed or entity invalid
		if (!state.targetEntity.is_alive()) {
			initController(e, pc, state);
		}
		if (!state.targetEntity.is_alive())
			return;

		if (!state.targetEntity.has<TransformComponent>())
			return;

		auto &transform = state.targetEntity.get<TransformComponent>();
		if (!transform.node)
			return;

		Ogre::Vector3 charPos = transform.node->_getDerivedPosition();

		// Update camera
		if (pc.cameraMode == PlayerControllerComponent::TPS) {
			updateTPSCamera(pc, state, charPos, deltaTime);
		} else {
			updateFPSCamera(pc, state, charPos);
		}

		// Update locomotion
		updateLocomotion(pc, state, deltaTime);
	});
}

void PlayerControllerSystem::updateTPSCamera(PlayerControllerComponent &pc,
					     ControllerState &state,
					     const Ogre::Vector3 &charPos,
					     float deltaTime)
{
	if (!state.pivotNode || !state.goalNode)
		return;

	EditorCamera *editorCam = m_editorApp->getEditorCamera();
	if (!editorCam)
		return;
	Ogre::Camera *cam = editorCam->getCamera();
	if (!cam)
		return;
	Ogre::SceneNode *camNode = cam->getParentSceneNode();
	if (!camNode)
		return;

	// Restore face visibility when switching from FPS
	if (state.faceHidden) {
		CharacterSlotSystem *css =
			m_editorApp->getCharacterSlotSystem();
		if (css) {
			css->setSlotVisible(state.targetEntity, "face", true);
		}
		state.faceHidden = false;
	}

	// Read mouse input (skip if input locked by action)
	GameInputState &input = m_editorApp->getGameInputState();
	if (!pc.inputLocked && input.mouseMoved) {
		state.yaw -= input.mouseDeltaX * pc.mouseSensitivity;
		state.pitch -= input.mouseDeltaY * pc.mouseSensitivity;
		// Clamp pitch
		if (state.pitch > 25.0f)
			state.pitch = 25.0f;
		if (state.pitch < -60.0f)
			state.pitch = -60.0f;
	}

	// Position pivot at character shoulder height
	Ogre::Vector3 pivotPos = charPos + Ogre::Vector3(0, pc.tpsHeight, 0);
	state.pivotNode->setPosition(pivotPos);

	// Compute goal position based on yaw/pitch/distance
	Ogre::Quaternion yawRot(Ogre::Degree(state.yaw), Ogre::Vector3::UNIT_Y);
	Ogre::Quaternion pitchRot(Ogre::Degree(state.pitch),
				  Ogre::Vector3::UNIT_X);
	Ogre::Vector3 offset =
		yawRot * pitchRot * Ogre::Vector3(0, 0, pc.tpsDistance);
	Ogre::Vector3 goalPos = pivotPos + offset;

	state.goalNode->setPosition(goalPos);

	// Snap camera directly to goal to avoid jitter
	// caused by lerp smoothing with varying dt.
	camNode->setPosition(goalPos);
	camNode->lookAt(pivotPos, Ogre::Node::TS_WORLD);
}

void PlayerControllerSystem::updateFPSCamera(PlayerControllerComponent &pc,
					     ControllerState &state,
					     const Ogre::Vector3 &charPos)
{
	(void)charPos;

	EditorCamera *editorCam = m_editorApp->getEditorCamera();
	if (!editorCam)
		return;
	Ogre::Camera *cam = editorCam->getCamera();
	if (!cam)
		return;
	Ogre::SceneNode *camNode = cam->getParentSceneNode();
	if (!camNode)
		return;

	// Find animated entity for bone access
	AnimationTreeSystem *ats = m_editorApp->getAnimationTreeSystem();
	if (!ats)
		return;

	Ogre::Entity *ent = ats->findAnimatedEntity(state.targetEntity);
	if (!ent || !ent->hasSkeleton()) {
		// Fallback to TPS if no skeleton
		pc.cameraMode = PlayerControllerComponent::TPS;
		return;
	}

	Ogre::SkeletonInstance *skel = ent->getSkeleton();
	Ogre::Bone *bone = nullptr;
	try {
		bone = skel->getBone(pc.fpsBoneName);
	} catch (...) {
		// Try common alternatives
		const char *alternatives[] = { "Head", "head",	 "Neck",
					       "neck", "Camera", "camera" };
		for (const char *name : alternatives) {
			try {
				bone = skel->getBone(name);
				break;
			} catch (...) {
			}
		}
	}

	if (!bone) {
		Ogre::LogManager::getSingleton().logMessage(
			"PlayerControllerSystem: Bone " + pc.fpsBoneName +
			" not found, falling back to TPS");
		pc.cameraMode = PlayerControllerComponent::TPS;
		return;
	}

	// Hide face slot
	if (!state.faceHidden) {
		CharacterSlotSystem *css =
			m_editorApp->getCharacterSlotSystem();
		if (css) {
			css->setSlotVisible(state.targetEntity, "face", false);
		}
		state.faceHidden = true;
	}

	// Get character scene node
	auto &transform = state.targetEntity.get<TransformComponent>();
	Ogre::SceneNode *charNode = transform.node;

	// Compute bone world transform
	Ogre::Vector3 boneWorldPos = charNode->_getDerivedOrientation() *
					     bone->_getDerivedPosition() +
				     charNode->_getDerivedPosition();
	Ogre::Quaternion boneWorldRot = charNode->_getDerivedOrientation() *
					bone->_getDerivedOrientation();

	// Offset slightly forward
	Ogre::Vector3 offset = boneWorldRot * Ogre::Vector3(0, 0, 0.15f);

	camNode->setPosition(boneWorldPos + offset);

	// Apply mouse look (skip if input locked by action)
	GameInputState &input = m_editorApp->getGameInputState();
	if (!pc.inputLocked && input.mouseMoved) {
		state.yaw -= input.mouseDeltaX * pc.mouseSensitivity;
		state.pitch -= input.mouseDeltaY * pc.mouseSensitivity;
		if (state.pitch > 89.0f)
			state.pitch = 89.0f;
		if (state.pitch < -89.0f)
			state.pitch = -89.0f;
	}

	Ogre::Quaternion yawRot(Ogre::Degree(state.yaw), Ogre::Vector3::UNIT_Y);
	Ogre::Quaternion pitchRot(Ogre::Degree(state.pitch),
				  Ogre::Vector3::UNIT_X);
	camNode->setOrientation(yawRot * pitchRot);
}

void PlayerControllerSystem::updateLocomotion(PlayerControllerComponent &pc,
					      ControllerState &state,
					      float deltaTime)
{
	(void)deltaTime;
	if (!state.targetEntity.has<CharacterComponent>())
		return;

	// Skip locomotion if input is locked by an executing action
	if (pc.inputLocked)
		return;

	GameInputState &input = m_editorApp->getGameInputState();

	// Get camera yaw for relative movement
	Ogre::Quaternion yawRot(Ogre::Degree(state.yaw), Ogre::Vector3::UNIT_Y);
	Ogre::Vector3 forward = yawRot * Ogre::Vector3::UNIT_Z;
	Ogre::Vector3 right = yawRot * -Ogre::Vector3::UNIT_X;

	// Flatten to horizontal plane
	forward.y = 0;
	right.y = 0;
	if (forward.squaredLength() > 0.0001f)
		forward.normalise();
	if (right.squaredLength() > 0.0001f)
		right.normalise();

	Ogre::Vector3 desiredDir = Ogre::Vector3::ZERO;
	if (input.w)
		desiredDir += forward;
	if (input.s)
		desiredDir -= forward;
	if (input.a)
		desiredDir -= right;
	if (input.d)
		desiredDir += right;

	bool isMoving = desiredDir.squaredLength() > 0.0001f;

	if (isMoving) {
		desiredDir.normalise();

		// Rotate character to face movement direction
		auto &transform =
			state.targetEntity.get_mut<TransformComponent>();
		if (transform.node) {
			Ogre::Vector3 flatForward = desiredDir;
			flatForward.y = 0;
			if (flatForward.squaredLength() > 0.0001f) {
				flatForward.normalise();
				Ogre::Quaternion targetRot =
					Ogre::Vector3::NEGATIVE_UNIT_Z
						.getRotationTo(flatForward);
				Ogre::Quaternion currentRot =
					transform.node->getOrientation();
				transform.node->setOrientation(
					Ogre::Quaternion::Slerp(
						deltaTime * 10.0f, currentRot,
						targetRot, true));
			}
		}
	}

	// Update animation state
	AnimationTreeSystem *ats = m_editorApp->getAnimationTreeSystem();
	if (!ats)
		return;

	bool inWater = state.targetEntity.has<InWater>();
	Ogre::String animState;
	if (inWater) {
		if (!isMoving) {
			animState = pc.swimIdleState;
		} else if (input.shift) {
			animState = pc.swimFastState;
		} else {
			animState = pc.swimState;
		}
	} else {
		if (!isMoving) {
			animState = pc.idleState;
		} else if (input.shift) {
			animState = pc.runState;
		} else {
			animState = pc.walkState;
		}
	}

	Ogre::String prevState = ats->getCurrentState(
		state.targetEntity, pc.locomotionStateMachine);

	if (!animState.empty()) {
		ats->setState(state.targetEntity, pc.locomotionStateMachine,
			      animState);
	}

	/* Log every locomotion transition so we can verify state names and
	 * see whether another system is overriding the state. */
	if (prevState != animState) {
		EDITSCENE_LOG_PC("[PC_DBG] locomotion transition: requested=" +
				 animState + " prev=" + prevState + " input w=" +
				 Ogre::StringConverter::toString(input.w) +
				 " shift=" +
				 Ogre::StringConverter::toString(input.shift));
	}

	/* Detect the stuck slow-walk condition: we are setting the walk
	 * animation but no movement keys are pressed, or another system
	 * has already forced the walk state before we could set idle. */
	bool anyMovement = input.w || input.a || input.s || input.d;
	bool hasPathFollowing =
		state.targetEntity.has<PathFollowingComponent>();
	bool hasBehaviorTree = state.targetEntity.has<BehaviorTreeComponent>();

	if ((animState == pc.walkState && !anyMovement) ||
	    (animState == pc.idleState && prevState == pc.walkState &&
	     !anyMovement)) {
		m_stuckWalkFrames++;
		if (m_stuckWalkFrames >= 60 && !m_stuckWalkLogged) {
			EDITSCENE_LOG_PC("[PC_DBG] STUCK WALK DETECTED: frames=" +
				Ogre::StringConverter::toString(
					m_stuckWalkFrames) +
				" w=" +
				Ogre::StringConverter::toString(input.w) +
				" a=" +
				Ogre::StringConverter::toString(input.a) +
				" s=" +
				Ogre::StringConverter::toString(input.s) +
				" d=" +
				Ogre::StringConverter::toString(input.d) +
				" shift=" +
				Ogre::StringConverter::toString(input.shift) +
				" requested=" + animState +
				" prev=" + prevState + " isMoving=" +
				Ogre::StringConverter::toString(isMoving) +
				" inputLocked=" +
				Ogre::StringConverter::toString(
					pc.inputLocked) +
				" targetAlive=" +
				Ogre::StringConverter::toString(
					state.targetEntity.is_alive()) +
				" pathFollowing=" +
				Ogre::StringConverter::toString(
					hasPathFollowing) +
				" behaviorTree=" +
				Ogre::StringConverter::toString(
					hasBehaviorTree));
			m_stuckWalkLogged = true;
		}
	} else {
		m_stuckWalkFrames = 0;
		m_stuckWalkLogged = false;
	}
}

flecs::entity
PlayerControllerSystem::getCurrentTarget(flecs::entity controllerEntity)
{
	auto it = m_states.find(controllerEntity.id());
	if (it != m_states.end() && it->second.targetEntity.is_alive())
		return it->second.targetEntity;

	if (!controllerEntity.is_alive() ||
	    !controllerEntity.has<PlayerControllerComponent>())
		return flecs::entity::null();

	auto &pc = controllerEntity.get<PlayerControllerComponent>();
	return resolveTargetEntity(pc.targetCharacterName, true);
}

flecs::entity
PlayerControllerSystem::resolveTargetEntity(const Ogre::String &targetName,
					    bool forceSpawn)
{
	flecs::entity namedEntity = findTargetEntity(targetName);
	if (!namedEntity.is_alive())
		return flecs::entity::null();

	if (!namedEntity.has<CharacterSpawnerComponent>())
		return namedEntity;

	if (!m_spawnerSystem)
		return flecs::entity::null();

	if (forceSpawn) {
		m_spawnerSystem->lockSpawner(namedEntity);
		flecs::entity spawned =
			m_spawnerSystem->getSpawnedCharacter(namedEntity);
		if (!spawned.is_alive()) {
			m_spawnerSystem->spawn(namedEntity);
			spawned = m_spawnerSystem->getSpawnedCharacter(
				namedEntity);
		}
		return spawned;
	}

	return m_spawnerSystem->getSpawnedCharacter(namedEntity);
}
