#include "CharacterSpawnerSystem.hpp"
#include "CharacterRegistry.hpp"
#include "CameraSystem.hpp"
#include "EditorUISystem.hpp"
#include "../components/CharacterSpawner.hpp"
#include "../components/Transform.hpp"
#include "../components/EditorMarker.hpp"
#include "../components/Renderable.hpp"
#include "../components/CharacterIdentity.hpp"
#include "../GameMode.hpp"
#include <OgreCamera.h>
#include <OgreLogManager.h>

CharacterSpawnerSystem::CharacterSpawnerSystem(flecs::world &world,
					       Ogre::SceneManager *sceneMgr,
					       EditorCameraSystem *cameraSystem,
					       EditorUISystem *uiSystem)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_cameraSystem(cameraSystem)
	, m_uiSystem(uiSystem)
	, m_query(world.query<CharacterSpawnerComponent, TransformComponent>())
{
	m_world.observer<CharacterSpawnerComponent>("CharacterSpawnerCleanup")
		.event(flecs::OnRemove)
		.each([this](flecs::entity e, CharacterSpawnerComponent &) {
			despawn(e);
		});
}

CharacterSpawnerSystem::~CharacterSpawnerSystem()
{
	std::vector<flecs::entity_t> ids;
	ids.reserve(m_spawnedEntities.size());
	for (const auto &pair : m_spawnedEntities)
		ids.push_back(pair.first);
	for (flecs::entity_t id : ids)
		despawn(m_world.entity(id));
}

void CharacterSpawnerSystem::initialize()
{
	if (m_initialized)
		return;
	m_initialized = true;

	Ogre::LogManager::getSingleton().logMessage(
		"CharacterSpawnerSystem initialized");
}

Ogre::Vector3 CharacterSpawnerSystem::getCameraPosition() const
{
	if (m_cameraSystem) {
		Ogre::Camera *cam = m_cameraSystem->getActiveCamera();
		if (cam)
			return cam->getDerivedPosition();
	}
	return Ogre::Vector3::ZERO;
}

void CharacterSpawnerSystem::spawnCharacter(
	flecs::entity spawnerEntity, const CharacterSpawnerComponent &spawner)
{
	if (spawner.registryId == 0 || !spawnerEntity.is_alive() ||
	    !spawnerEntity.has<TransformComponent>())
		return;

	/* Make sure any previous instance is gone first. */
	despawn(spawnerEntity);

	flecs::entity character =
		CharacterRegistry::getSingleton().spawnCharacter(
			spawner.registryId);
	if (!character.is_alive()) {
		const CharacterRegistry::CharacterRecord *rec =
			CharacterRegistry::getSingleton().findCharacter(
				spawner.registryId);
		if (rec) {
			character = CharacterRegistry::getSingleton()
					    .spawnInlineCharacter(
						    *rec, Ogre::Vector3::ZERO);
		}
	}

	if (!character.is_alive()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CharacterSpawnerSystem: failed to spawn character " +
			std::to_string(spawner.registryId));
		return;
	}

	/* Apply spawner transform to the spawned character. */
	if (character.has<TransformComponent>()) {
		const auto &spawnerTransform =
			spawnerEntity.get<TransformComponent>();
		auto &charTransform = character.get_mut<TransformComponent>();
		charTransform.position = spawnerTransform.position;
		charTransform.rotation = spawnerTransform.rotation;
		charTransform.scale = spawnerTransform.scale;
		if (charTransform.node && spawnerTransform.node) {
			charTransform.node->setPosition(
				spawnerTransform.node->getPosition());
			charTransform.node->setOrientation(
				spawnerTransform.node->getOrientation());
			charTransform.node->setScale(
				spawnerTransform.node->getScale());
		}
		charTransform.applyToNode();
	}

	/* Hide from editor entity lists and property panels. */
	if (character.has<EditorMarkerComponent>())
		character.remove<EditorMarkerComponent>();

	/* Remove from editor UI cache so the spawned instance does not appear in
	 * entity lists or property panels. */
	if (m_uiSystem)
		m_uiSystem->removeEntity(character);

	m_spawnedEntities[spawnerEntity.id()] = character;
	m_spawnedVersions[spawnerEntity.id()] =
		CharacterRegistry::getSingleton().getCharacterVersion(
			spawner.registryId);

	/* Remember the spawner transform so we only re-apply it when it actually
	 * changes, leaving the spawned character free to move on its own. */
	const auto &spawnerTransform = spawnerEntity.get<TransformComponent>();
	m_lastSpawnerTransforms[spawnerEntity.id()] = {
		spawnerTransform.position,
		spawnerTransform.rotation,
		spawnerTransform.scale,
	};

	Ogre::LogManager::getSingleton().logMessage(
		"CharacterSpawnerSystem: spawned character " +
		std::to_string(spawner.registryId) + " for spawner " +
		std::to_string(spawnerEntity.id()));
}

void CharacterSpawnerSystem::destroySpawnedEntity(flecs::entity character)
{
	if (!character.is_alive())
		return;

	/* Explicitly clean up Ogre resources that flecs component destructors do
	 * not handle automatically. */
	if (character.has<TransformComponent>()) {
		auto &transform = character.get_mut<TransformComponent>();
		if (transform.node) {
			try {
				m_sceneMgr->destroySceneNode(transform.node);
			} catch (...) {
			}
			transform.node = nullptr;
		}
	}

	if (character.has<RenderableComponent>()) {
		auto &renderable = character.get_mut<RenderableComponent>();
		if (renderable.entity) {
			try {
				m_sceneMgr->destroyEntity(renderable.entity);
			} catch (...) {
			}
			renderable.entity = nullptr;
		}
	}

	/* Remove from editor UI cache before destruction. */
	if (m_uiSystem)
		m_uiSystem->removeEntity(character);

	/* CharacterSlotSystem and CharacterSystem clean up the rest via their
	 * OnRemove observers. */
	character.destruct();
}

void CharacterSpawnerSystem::applySpawnerTransform(
	flecs::entity spawnerEntity, flecs::entity characterEntity)
{
	if (!spawnerEntity.is_alive() || !characterEntity.is_alive())
		return;
	if (!spawnerEntity.has<TransformComponent>() ||
	    !characterEntity.has<TransformComponent>())
		return;

	const auto &spawnerTransform = spawnerEntity.get<TransformComponent>();
	auto &charTransform = characterEntity.get_mut<TransformComponent>();

	charTransform.position = spawnerTransform.position;
	charTransform.rotation = spawnerTransform.rotation;
	charTransform.scale = spawnerTransform.scale;
	if (charTransform.node && spawnerTransform.node) {
		charTransform.node->setPosition(
			spawnerTransform.node->getPosition());
		charTransform.node->setOrientation(
			spawnerTransform.node->getOrientation());
		charTransform.node->setScale(spawnerTransform.node->getScale());
	}
	charTransform.applyToNode();
}

void CharacterSpawnerSystem::despawn(flecs::entity spawnerEntity)
{
	if (!spawnerEntity.is_alive())
		return;

	auto it = m_spawnedEntities.find(spawnerEntity.id());
	if (it == m_spawnedEntities.end())
		return;

	flecs::entity character = it->second;
	m_spawnedEntities.erase(it);
	m_spawnedVersions.erase(spawnerEntity.id());
	m_lastSpawnerTransforms.erase(spawnerEntity.id());

	destroySpawnedEntity(character);

	Ogre::LogManager::getSingleton().logMessage(
		"CharacterSpawnerSystem: despawned character for spawner " +
		std::to_string(spawnerEntity.id()));
}

void CharacterSpawnerSystem::spawn(flecs::entity spawnerEntity)
{
	if (!spawnerEntity.is_alive() ||
	    !spawnerEntity.has<CharacterSpawnerComponent>())
		return;
	spawnCharacter(spawnerEntity,
		       spawnerEntity.get<CharacterSpawnerComponent>());
}

void CharacterSpawnerSystem::lockSpawner(flecs::entity spawnerEntity)
{
	if (!spawnerEntity.is_alive() ||
	    !spawnerEntity.has<CharacterSpawnerComponent>())
		return;
	m_lockedSpawners.insert(spawnerEntity.id());
}

void CharacterSpawnerSystem::unlockSpawner(flecs::entity spawnerEntity)
{
	m_lockedSpawners.erase(spawnerEntity.id());
}

flecs::entity
CharacterSpawnerSystem::getSpawnedCharacter(flecs::entity spawnerEntity) const
{
	auto it = m_spawnedEntities.find(spawnerEntity.id());
	if (it != m_spawnedEntities.end() && it->second.is_alive())
		return it->second;
	return flecs::entity::null();
}

flecs::entity
CharacterSpawnerSystem::getSpawnerForCharacter(flecs::entity characterEntity) const
{
	for (const auto &pair : m_spawnedEntities) {
		if (pair.second == characterEntity)
			return m_world.entity(pair.first);
	}
	return flecs::entity::null();
}

bool CharacterSpawnerSystem::isSpawnerLocked(flecs::entity spawnerEntity) const
{
	return m_lockedSpawners.find(spawnerEntity.id()) !=
	       m_lockedSpawners.end();
}

void CharacterSpawnerSystem::update()
{
	if (!m_initialized)
		return;

	const Ogre::Vector3 cameraPos = getCameraPosition();

	m_query.each([&](flecs::entity e, CharacterSpawnerComponent &spawner,
			 TransformComponent &transform) {
		if (!transform.node)
			return;

		const Ogre::Vector3 diff =
			transform.node->_getDerivedPosition() - cameraPos;
		const float distSq = diff.squaredLength();

		auto spawnedIt = m_spawnedEntities.find(e.id());
		const bool isSpawned = spawnedIt != m_spawnedEntities.end() &&
				       spawnedIt->second.is_alive();

		const bool locked = isSpawnerLocked(e);

		if (locked && !isSpawned) {
			spawnCharacter(e, spawner);
		} else if (!isSpawned && distSq <= spawner.spawnDistanceSq) {
			spawnCharacter(e, spawner);
		} else if (isSpawned && !locked &&
			   distSq > spawner.despawnDistanceSq) {
			despawn(e);
		} else if (isSpawned) {
			const uint64_t currentVersion =
				CharacterRegistry::getSingleton()
					.getCharacterVersion(
						spawner.registryId);
			auto verIt = m_spawnedVersions.find(e.id());
			if (verIt == m_spawnedVersions.end() ||
			    verIt->second != currentVersion) {
				despawn(e);
				spawnCharacter(e, spawner);
			} else {
				/* Editor placement: if the spawner itself moved/rotated/scaled,
				 * update the spawned character to match. Otherwise leave the
				 * character alone so it can move on its own. */
				auto lastIt =
					m_lastSpawnerTransforms.find(e.id());
				const bool hasSnapshot =
					lastIt != m_lastSpawnerTransforms.end();
				const bool moved =
					!hasSnapshot ||
					(transform.position -
					 lastIt->second.position)
							.squaredLength() >
						1e-6f ||
					(transform.scale - lastIt->second.scale)
							.squaredLength() >
						1e-6f ||
					1.0f - Ogre::Math::Abs(
						       transform.rotation.Dot(
							       lastIt->second
								       .rotation)) >
						1e-4f;
				if (moved) {
					applySpawnerTransform(
						e, spawnedIt->second);
					m_lastSpawnerTransforms[e.id()] = {
						transform.position,
						transform.rotation,
						transform.scale,
					};
				}
			}
		}
	});
}
