#include "NavMeshSystem.hpp"
#include "../components/NavMesh.hpp"
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
#include "../components/RigidBody.hpp"
#include "../components/StaticGeometry.hpp"
#include "../components/StaticGeometryMember.hpp"
#include "../components/PhysicsCollider.hpp"
#include "../components/CellGrid.hpp"
#include "../recast/TileCacheNavMesh.hpp"
#include "CellGridSystem.hpp"
#include <OgreLogManager.h>
#include <algorithm>
#include <cctype>

static bool isNavMeshGeometry(const Ogre::String &meshName)
{
	Ogre::String lower = meshName;
	std::transform(lower.begin(), lower.end(), lower.begin(),
		       [](unsigned char c) { return std::tolower(c); });

	// Exclude geometry that doesn't affect floor-level navigation.
	// Ceilings are above walkable floors and don't block movement.
	// Furniture is handled separately (or excluded to keep navmesh
	// focused on architectural walkable surfaces).
	// Frames placed via StaticGeometry aren't traversed by the
	// SceneNode walker anyway.
	static const char *exclude[] = { "ceiling", "frame", "furniture" };
	for (const char *pat : exclude) {
		if (lower.find(pat) != Ogre::String::npos)
			return false;
	}
	return true;
}

// Helper to recursively collect Ogre::Entity objects from a scene node
static void collectEntitiesFromNode(Ogre::SceneNode *node,
				    std::vector<Ogre::Entity *> &out)
{
	if (!node)
		return;
	try {
		for (unsigned short i = 0; i < node->numAttachedObjects();
		     ++i) {
			Ogre::MovableObject *obj = node->getAttachedObject(i);
			if (!obj)
				continue;
			if (obj->getMovableType() == "Entity") {
				Ogre::Entity *ent =
					static_cast<Ogre::Entity *>(obj);
				if (!isNavMeshGeometry(
					    ent->getMesh()->getName()))
					continue;
				// Avoid duplicates
				if (std::find(out.begin(), out.end(), ent) ==
				    out.end())
					out.push_back(ent);
			}
		}
		for (unsigned short i = 0; i < node->numChildren(); ++i) {
			Ogre::Node *child = node->getChild(i);
			if (!child)
				continue;
			Ogre::SceneNode *childSN =
				dynamic_cast<Ogre::SceneNode *>(child);
			if (childSN)
				collectEntitiesFromNode(childSN, out);
		}
	} catch (...) {
		// Defensive: ignore corrupted nodes
	}
}

// Helper to recursively collect Ogre::Entity objects from a flecs entity
// and all its descendants.  This is needed because flecs parent/child
// hierarchy does NOT map to Ogre SceneNode hierarchy; each entity has
// its own independent scene node.
static void collectEntitiesFromFlecsEntity(flecs::entity e,
					   std::vector<Ogre::Entity *> &out)
{
	if (e.has<TransformComponent>()) {
		auto &trans = e.get<TransformComponent>();
		if (trans.node)
			collectEntitiesFromNode(trans.node, out);
	}
	e.children([&](flecs::entity child) {
		collectEntitiesFromFlecsEntity(child, out);
	});
}

NavMeshSystem *NavMeshSystem::s_instance = nullptr;

NavMeshSystem::NavMeshSystem(flecs::world &world, Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
{
	s_instance = this;
}

NavMeshSystem::~NavMeshSystem()
{
	if (s_instance == this)
		s_instance = nullptr;
}

void NavMeshSystem::setCellGridSystem(CellGridSystem *system)
{
	m_cellGridSystem = system;
}

void NavMeshSystem::update(float deltaTime)
{
	(void)deltaTime;

	m_world.query<NavMeshComponent>().each([&](flecs::entity e,
						   NavMeshComponent &comp) {
		if (!comp.enabled)
			return;

		auto &state = m_states[e.id()];

		// Handle full rebuild request
		if (comp.dirty) {
			buildOrUpdate(e.id(), comp, state);
			comp.dirty = false;
			comp.needsPartialRebuild = false;
			state.lastTransformVersions.clear();
			return;
		}

		// Check for geometry changes that need partial rebuild
		bool changed = false;
		Ogre::AxisAlignedBox dirtyArea;
		m_world.query<TransformComponent>().each([&](flecs::entity ge,
							     TransformComponent
								     &trans) {
			bool contributes = false;
			if (ge.has<RigidBodyComponent>()) {
				auto &rb = ge.get<RigidBodyComponent>();
				if (rb.bodyType ==
				    RigidBodyComponent::BodyType::Static)
					contributes = true;
			}
			if (ge.has<StaticGeometryMemberComponent>()) {
				auto &sgm =
					ge.get<StaticGeometryMemberComponent>();
				if (!sgm.meshName.empty())
					contributes = true;
			}
			if (ge.has<NavMeshGeometrySource>()) {
				auto &nmg = ge.get<NavMeshGeometrySource>();
				if (nmg.include)
					contributes = true;
			}
			if (ge.has<CellGridComponent>())
				contributes = true;
			if (ge.has<DistrictComponent>())
				contributes = true;
			if (ge.has<LotComponent>())
				contributes = true;
			if (!contributes)
				return;

			auto it = state.lastTransformVersions.find(ge.id());
			if (it == state.lastTransformVersions.end() ||
			    it->second != trans.version) {
				changed = true;
				Ogre::AxisAlignedBox bb = getEntityBounds(ge);
				if (!bb.isNull()) {
					if (dirtyArea.isNull())
						dirtyArea = bb;
					else
						dirtyArea.merge(bb);
				}
				state.lastTransformVersions[ge.id()] =
					trans.version;
			}
		});

		if (changed && state.navmesh) {
			if (!dirtyArea.isNull()) {
				Ogre::Vector3 padding(comp.agentRadius * 2,
						      comp.agentHeight * 2,
						      comp.agentRadius * 2);
				dirtyArea.setMinimum(dirtyArea.getMinimum() -
						     padding);
				dirtyArea.setMaximum(dirtyArea.getMaximum() +
						     padding);
				state.navmesh->rebuildTilesInArea(dirtyArea);
			}
			if (state.debugDraw)
				state.navmesh->drawNavMesh();
		}
	});
}

void NavMeshSystem::buildOrUpdate(flecs::entity_t e, NavMeshComponent &comp,
				  NavMeshState &state)
{
	state.navmesh.reset();
	state.lastTransformVersions.clear();

	TileCacheNavMesh::BuildParams params;
	params.cellSize = comp.cellSize;
	params.cellHeight = comp.cellHeight;
	params.agentHeight = comp.agentHeight;
	params.agentRadius = comp.agentRadius;
	params.agentMaxClimb = comp.agentMaxClimb;
	params.agentMaxSlope = comp.agentMaxSlope;
	params.edgeMaxLen = comp.edgeMaxLen;
	params.edgeMaxError = comp.edgeMaxError;
	params.regionMinSize = comp.regionMinSize;
	params.regionMergeSize = comp.regionMergeSize;
	params.tileSize = comp.tileSize;

	state.navmesh = std::make_unique<TileCacheNavMesh>(m_sceneMgr, params);

	std::vector<Ogre::Entity *> entities;
	std::vector<Ogre::Entity *> tempEntities;
	collectStaticEntities(entities, tempEntities);

	if (entities.empty()) {
		Ogre::LogManager::getSingleton().logMessage(
			"[NavMeshSystem] No static geometry found for navmesh");
		return;
	}

	bool ok = state.navmesh->build(entities);
	destroyTempEntities(tempEntities);

	if (!ok) {
		Ogre::LogManager::getSingleton().logMessage(
			"[NavMeshSystem] Navmesh build failed");
		return;
	}

	Ogre::LogManager::getSingleton().logMessage(
		"[NavMeshSystem] Navmesh built successfully");

	if (state.debugDraw)
		state.navmesh->drawNavMesh();

	state.firstBuild = false;

	m_world.query<TransformComponent>().each([&](flecs::entity ge,
						     TransformComponent &trans) {
		bool contributes = false;
		if (ge.has<RigidBodyComponent>()) {
			auto &rb = ge.get<RigidBodyComponent>();
			if (rb.bodyType == RigidBodyComponent::BodyType::Static)
				contributes = true;
		}
		if (ge.has<StaticGeometryMemberComponent>()) {
			auto &sgm = ge.get<StaticGeometryMemberComponent>();
			if (!sgm.meshName.empty())
				contributes = true;
		}
		if (ge.has<NavMeshGeometrySource>()) {
			auto &nmg = ge.get<NavMeshGeometrySource>();
			if (nmg.include)
				contributes = true;
		}
		if (ge.has<CellGridComponent>())
			contributes = true;
		if (ge.has<DistrictComponent>())
			contributes = true;
		if (ge.has<LotComponent>())
			contributes = true;
		if (contributes)
			state.lastTransformVersions[ge.id()] = trans.version;
	});
}

void NavMeshSystem::collectStaticEntities(std::vector<Ogre::Entity *> &out,
					  std::vector<Ogre::Entity *> &outTemp)
{
	// Helper: add existing scene entity (do NOT destroy later)
	auto addExisting = [&](Ogre::Entity *ent) {
		if (ent)
			out.push_back(ent);
	};
	// Helper: add temp entity (MUST be destroyed after build)
	auto addTemp = [&](Ogre::Entity *ent) {
		if (ent) {
			out.push_back(ent);
			outTemp.push_back(ent);
		}
	};

	Ogre::LogManager::getSingleton().logMessage(
		"[NavMeshSystem] collectStaticEntities started");

	// 1. Static rigid bodies with renderables
	m_world.query<TransformComponent, RigidBodyComponent>().each(
		[&](flecs::entity e, TransformComponent &trans,
		    RigidBodyComponent &rb) {
			if (rb.bodyType != RigidBodyComponent::BodyType::Static)
				return;
			if (!e.has<RenderableComponent>())
				return;
			auto &rend = e.get<RenderableComponent>();
			if (rend.entity) {
				addExisting(rend.entity);
			} else if (!rend.meshName.empty() &&
				   isNavMeshGeometry(rend.meshName)) {
				addTemp(createTempEntity(
					rend.meshName, trans.position,
					trans.rotation, trans.scale));
			}
		});

	// 2. StaticGeometry members
	m_world.query<TransformComponent, StaticGeometryMemberComponent>().each(
		[&](flecs::entity e, TransformComponent &trans,
		    StaticGeometryMemberComponent &sgm) {
			if (sgm.meshName.empty() ||
			    !isNavMeshGeometry(sgm.meshName))
				return;
			addTemp(createTempEntity(sgm.meshName, trans.position,
						 trans.rotation, trans.scale));
		});

	// 3. Forced geometry sources
	m_world.query<TransformComponent, NavMeshGeometrySource>().each(
		[&](flecs::entity e, TransformComponent &trans,
		    NavMeshGeometrySource &nmg) {
			if (!nmg.include)
				return;
			Ogre::String meshName;
			if (e.has<RenderableComponent>()) {
				auto &rend = e.get<RenderableComponent>();
				if (rend.entity) {
					addExisting(rend.entity);
					return;
				}
				meshName = rend.meshName;
			}
			if (e.has<StaticGeometryMemberComponent>()) {
				auto &sgm =
					e.get<StaticGeometryMemberComponent>();
				if (!sgm.meshName.empty())
					meshName = sgm.meshName;
			}
			if (!meshName.empty() && isNavMeshGeometry(meshName)) {
				addTemp(createTempEntity(
					meshName, trans.position,
					trans.rotation, trans.scale));
			}
		});

	// 4. Cell Grid, District, and Lot geometry (always included)
	// We recursively traverse the flecs hierarchy because flecs
	// parent/child relationships do NOT map to Ogre SceneNode
	// hierarchy; each entity has its own independent scene node.
	m_world.query<TransformComponent, CellGridComponent>().each(
		[&](flecs::entity e, TransformComponent &trans,
		    CellGridComponent &grid) {
			(void)trans;
			(void)grid;
			collectEntitiesFromFlecsEntity(e, out);
		});
	m_world.query<TransformComponent, DistrictComponent>().each(
		[&](flecs::entity e, TransformComponent &trans,
		    DistrictComponent &district) {
			(void)trans;
			(void)district;
			collectEntitiesFromFlecsEntity(e, out);
		});
	m_world.query<TransformComponent, LotComponent>().each(
		[&](flecs::entity e, TransformComponent &trans,
		    LotComponent &lot) {
			(void)trans;
			(void)lot;
			collectEntitiesFromFlecsEntity(e, out);
		});

	// 5. Frame and furniture placements from CellGridSystem
	if (m_cellGridSystem) {
		m_world.query<CellGridComponent>().each(
			[&](flecs::entity e, CellGridComponent &grid) {
				(void)grid;
				// Frames and furniture are excluded from navmesh
				// geometry because their vertical spans merge with
				// floor spans in Recast and break walkability.
				(void)m_cellGridSystem;
			});
	}

	// Detailed logging
	Ogre::LogManager::getSingleton().logMessage(
		"[NavMeshSystem] collectStaticEntities done: existing=" +
		Ogre::StringConverter::toString(
			static_cast<int>(out.size() - outTemp.size())) +
		" temp=" +
		Ogre::StringConverter::toString(
			static_cast<int>(outTemp.size())));
	for (size_t i = 0; i < out.size(); ++i) {
		Ogre::Entity *ent = out[i];
		if (!ent)
			continue;
		Ogre::SceneNode *node = ent->getParentSceneNode();
		Ogre::String nodeName = node ? node->getName() : "(none)";
		Ogre::String meshName = ent->getMesh()->getName();
		bool isTemp = std::find(outTemp.begin(), outTemp.end(), ent) !=
			      outTemp.end();
		Ogre::LogManager::getSingleton().logMessage(
			"[NavMeshSystem]   entity[" +
			Ogre::StringConverter::toString(static_cast<int>(i)) +
			"] mesh=" + meshName + " node=" + nodeName +
			" temp=" + (isTemp ? "yes" : "no"));
	}
}

Ogre::Entity *NavMeshSystem::createTempEntity(const Ogre::String &meshName,
					      const Ogre::Vector3 &pos,
					      const Ogre::Quaternion &rot,
					      const Ogre::Vector3 &scale)
{
	static int tempId = 0;
	Ogre::String uniqueName =
		"_NavTempEnt_" + Ogre::StringConverter::toString(tempId++);
	Ogre::String nodeName =
		"_NavTempNode_" + Ogre::StringConverter::toString(tempId++);

	try {
		Ogre::Entity *ent =
			m_sceneMgr->createEntity(uniqueName, meshName);
		Ogre::SceneNode *node =
			m_sceneMgr->getRootSceneNode()->createChildSceneNode(
				nodeName);
		node->setPosition(pos);
		node->setOrientation(rot);
		node->setScale(scale);
		node->attachObject(ent);
		return ent;
	} catch (...) {
		return nullptr;
	}
}

void NavMeshSystem::destroyTempEntities(std::vector<Ogre::Entity *> &entities)
{
	for (auto *ent : entities) {
		if (!ent)
			continue;
		Ogre::SceneNode *node = ent->getParentSceneNode();
		if (node) {
			node->detachObject(ent);
			m_sceneMgr->destroySceneNode(node);
		}
		m_sceneMgr->destroyEntity(ent);
	}
	entities.clear();
}

Ogre::AxisAlignedBox NavMeshSystem::getEntityBounds(flecs::entity e)
{
	if (!e.has<TransformComponent>())
		return Ogre::AxisAlignedBox::EXTENT_NULL;
	auto &trans = e.get<TransformComponent>();

	Ogre::AxisAlignedBox localBB;
	bool hasBounds = false;

	if (e.has<RenderableComponent>()) {
		auto &rend = e.get<RenderableComponent>();
		if (rend.entity && rend.entity->getMesh()) {
			localBB = rend.entity->getMesh()->getBounds();
			hasBounds = true;
		}
	}
	if (!hasBounds && e.has<StaticGeometryMemberComponent>()) {
		auto &sgm = e.get<StaticGeometryMemberComponent>();
		if (!sgm.meshName.empty()) {
			try {
				Ogre::MeshPtr mesh =
					Ogre::MeshManager::getSingleton()
						.getByName(sgm.meshName);
				if (mesh) {
					localBB = mesh->getBounds();
					hasBounds = true;
				}
			} catch (...) {
			}
		}
	}
	if (!hasBounds && e.has<PhysicsColliderComponent>()) {
		auto &col = e.get<PhysicsColliderComponent>();
		localBB = Ogre::AxisAlignedBox(-col.parameters, col.parameters);
		hasBounds = true;
	}

	if (!hasBounds)
		return Ogre::AxisAlignedBox::EXTENT_NULL;

	Ogre::Matrix4 mat;
	mat.makeTransform(trans.position, trans.scale, trans.rotation);
	Ogre::AxisAlignedBox worldBB;
	worldBB.merge(mat *
		      localBB.getCorner(Ogre::AxisAlignedBox::FAR_LEFT_BOTTOM));
	worldBB.merge(mat *
		      localBB.getCorner(Ogre::AxisAlignedBox::FAR_LEFT_TOP));
	worldBB.merge(mat * localBB.getCorner(
				    Ogre::AxisAlignedBox::FAR_RIGHT_BOTTOM));
	worldBB.merge(mat *
		      localBB.getCorner(Ogre::AxisAlignedBox::FAR_RIGHT_TOP));
	worldBB.merge(mat * localBB.getCorner(
				    Ogre::AxisAlignedBox::NEAR_LEFT_BOTTOM));
	worldBB.merge(mat *
		      localBB.getCorner(Ogre::AxisAlignedBox::NEAR_LEFT_TOP));
	worldBB.merge(mat * localBB.getCorner(
				    Ogre::AxisAlignedBox::NEAR_RIGHT_BOTTOM));
	worldBB.merge(mat *
		      localBB.getCorner(Ogre::AxisAlignedBox::NEAR_RIGHT_TOP));
	return worldBB;
}

bool NavMeshSystem::findPath(flecs::entity navmeshEntity, Ogre::Vector3 start,
			     Ogre::Vector3 end,
			     std::vector<Ogre::Vector3> &path)
{
	auto it = m_states.find(navmeshEntity.id());
	if (it == m_states.end() || !it->second.navmesh)
		return false;

	return it->second.navmesh->findPath(start, end, path);
}

bool NavMeshSystem::findNearestPoint(flecs::entity navmeshEntity,
				     Ogre::Vector3 pos, Ogre::Vector3 &out)
{
	auto it = m_states.find(navmeshEntity.id());
	if (it == m_states.end() || !it->second.navmesh)
		return false;

	return it->second.navmesh->findNearestPoint(pos, out);
}

Ogre::Vector3 NavMeshSystem::getRandomPoint(flecs::entity navmeshEntity)
{
	auto it = m_states.find(navmeshEntity.id());
	if (it == m_states.end() || !it->second.navmesh)
		return Ogre::Vector3::ZERO;

	return it->second.navmesh->getRandomPoint();
}

void NavMeshSystem::rebuild(flecs::entity navmeshEntity)
{
	if (!navmeshEntity.has<NavMeshComponent>())
		return;
	auto &comp = navmeshEntity.get_mut<NavMeshComponent>();
	auto &state = m_states[navmeshEntity.id()];
	buildOrUpdate(navmeshEntity.id(), comp, state);
	comp.dirty = false;
}

void NavMeshSystem::setDebugDraw(flecs::entity navmeshEntity, bool enable)
{
	auto it = m_states.find(navmeshEntity.id());
	if (it == m_states.end())
		return;

	it->second.debugDraw = enable;
	if (it->second.navmesh) {
		if (enable)
			it->second.navmesh->drawNavMesh();
		else
			it->second.navmesh->clearDebugDraw();
	}
}
