#include "NormalDebugSystem.hpp"
#include "CellGridSystem.hpp"
#include "../components/CellGrid.hpp"
#include "../components/Transform.hpp"
#include <OgreManualObject.h>
#include <OgreSceneNode.h>
#include <OgreEntity.h>
#include <OgreSubMesh.h>
#include <OgreMesh.h>
#include <OgreLogManager.h>
#include <OgreStringConverter.h>

// Normal line length in world units
static const float NORMAL_LINE_LENGTH = 0.5f;

NormalDebugSystem::NormalDebugSystem(flecs::world &world,
				     Ogre::SceneManager *sceneMgr,
				     CellGridSystem *cellGridSystem)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_cellGridSystem(cellGridSystem)
{
	// Create ManualObject for normal lines
	m_manualObj = m_sceneMgr->createManualObject("NormalDebugLines");
	m_manualObj->setDynamic(true);

	// Create scene node for the debug visualization
	m_node = m_sceneMgr->getRootSceneNode()->createChildSceneNode(
		"NormalDebugNode");
	m_node->attachObject(m_manualObj);
	m_node->setVisible(false); // Disabled by default
}

NormalDebugSystem::~NormalDebugSystem()
{
	if (m_node) {
		m_node->detachAllObjects();
		m_sceneMgr->destroySceneNode(m_node);
		m_node = nullptr;
	}
	if (m_manualObj) {
		m_sceneMgr->destroyManualObject(m_manualObj);
		m_manualObj = nullptr;
	}
}

void NormalDebugSystem::setEnabled(bool enabled)
{
	m_enabled = enabled;
	if (m_node) {
		m_node->setVisible(enabled);
	}
	if (enabled) {
		rebuildAll();
	} else {
		// Clear the manual object
		if (m_manualObj) {
			m_manualObj->clear();
		}
	}
}

void NormalDebugSystem::update()
{
	if (!m_enabled || !m_manualObj || !m_cellGridSystem) {
		return;
	}

	// Check if plaza/lot entities have changed by comparing cached IDs
	bool needsRebuild = false;

	// Get current plaza entities from CellGridSystem
	const auto &plazaMeshes = m_cellGridSystem->getPlazaMeshes();
	std::vector<uint64_t> currentPlazaEntities;
	for (const auto &pair : plazaMeshes) {
		currentPlazaEntities.push_back(pair.first);
	}
	if (currentPlazaEntities != m_cachedPlazaEntities) {
		needsRebuild = true;
		m_cachedPlazaEntities = currentPlazaEntities;
	}

	// Get current lot base entities from CellGridSystem
	const auto &lotBaseMeshes = m_cellGridSystem->getLotBaseMeshes();
	std::vector<uint64_t> currentLotEntities;
	for (const auto &pair : lotBaseMeshes) {
		currentLotEntities.push_back(pair.first);
	}
	if (currentLotEntities != m_cachedLotEntities) {
		needsRebuild = true;
		m_cachedLotEntities = currentLotEntities;
	}

	if (needsRebuild) {
		rebuildAll();
	}
}

void NormalDebugSystem::rebuildAll()
{
	if (!m_manualObj || !m_cellGridSystem) {
		return;
	}

	m_manualObj->clear();

	const auto &plazaMeshes = m_cellGridSystem->getPlazaMeshes();
	const auto &lotBaseMeshes = m_cellGridSystem->getLotBaseMeshes();

	// Count total normals to draw
	size_t totalNormals = 0;
	for (const auto &pair : plazaMeshes) {
		if (pair.second.entity) {
			Ogre::MeshPtr mesh = pair.second.entity->getMesh();
			if (mesh.isNull())
				continue;
			for (unsigned int i = 0; i < mesh->getNumSubMeshes();
			     ++i) {
				Ogre::SubMesh *sub = mesh->getSubMesh(i);
				if (sub && sub->vertexData) {
					totalNormals +=
						sub->vertexData->vertexCount;
				}
			}
		}
	}
	for (const auto &pair : lotBaseMeshes) {
		if (pair.second.entity) {
			Ogre::MeshPtr mesh = pair.second.entity->getMesh();
			if (mesh.isNull())
				continue;
			for (unsigned int i = 0; i < mesh->getNumSubMeshes();
			     ++i) {
				Ogre::SubMesh *sub = mesh->getSubMesh(i);
				if (sub && sub->vertexData) {
					totalNormals +=
						sub->vertexData->vertexCount;
				}
			}
		}
	}

	if (totalNormals == 0) {
		Ogre::LogManager::getSingleton().logMessage(
			"NormalDebug: No plaza or lot meshes found");
		return;
	}

	// Begin drawing lines (2 vertices per normal)
	m_manualObj->begin("Debug/NormalOverlay",
			   Ogre::RenderOperation::OT_LINE_LIST);

	// Draw plaza normals
	for (const auto &pair : plazaMeshes) {
		if (!pair.second.entity)
			continue;

		// Find the scene node for this plaza entity
		Ogre::SceneNode *parentNode = nullptr;
		flecs::entity districtEntity = m_world.entity(pair.first);
		if (districtEntity.is_alive() &&
		    districtEntity.has<TransformComponent>()) {
			const auto &transform =
				districtEntity.get<TransformComponent>();
			parentNode = transform.node;
		}

		drawMeshNormals(pair.second.entity, parentNode, m_manualObj);
	}

	// Draw lot base normals
	for (const auto &pair : lotBaseMeshes) {
		if (!pair.second.entity)
			continue;

		// Find the scene node for this lot entity
		Ogre::SceneNode *parentNode = nullptr;
		flecs::entity lotEntity = m_world.entity(pair.first);
		if (lotEntity.is_alive() &&
		    lotEntity.has<TransformComponent>()) {
			const auto &transform =
				lotEntity.get<TransformComponent>();
			parentNode = transform.node;
		}

		drawMeshNormals(pair.second.entity, parentNode, m_manualObj);
	}

	m_manualObj->end();

	Ogre::LogManager::getSingleton().logMessage(
		"NormalDebug: Drew " +
		Ogre::StringConverter::toString(totalNormals) +
		" normal lines");
}

void NormalDebugSystem::drawMeshNormals(Ogre::Entity *entity,
					Ogre::SceneNode *parentNode,
					Ogre::ManualObject *manualObj)
{
	if (!entity || !manualObj) {
		return;
	}

	Ogre::MeshPtr mesh = entity->getMesh();
	if (mesh.isNull()) {
		return;
	}

	// Get world transform from parent node
	Ogre::Vector3 worldPos(0, 0, 0);
	Ogre::Quaternion worldRot = Ogre::Quaternion::IDENTITY;
	Ogre::Vector3 worldScale(1, 1, 1);
	if (parentNode) {
		worldPos = parentNode->_getDerivedPosition();
		worldRot = parentNode->_getDerivedOrientation();
		worldScale = parentNode->_getDerivedScale();
	}

	for (unsigned int i = 0; i < mesh->getNumSubMeshes(); ++i) {
		Ogre::SubMesh *subMesh = mesh->getSubMesh(i);
		if (!subMesh || !subMesh->vertexData) {
			continue;
		}

		Ogre::VertexData *vertexData = subMesh->vertexData;
		const Ogre::VertexElement *posElem =
			vertexData->vertexDeclaration->findElementBySemantic(
				Ogre::VES_POSITION);
		const Ogre::VertexElement *normElem =
			vertexData->vertexDeclaration->findElementBySemantic(
				Ogre::VES_NORMAL);

		if (!posElem || !normElem) {
			continue;
		}

		// Get vertex buffer
		Ogre::HardwareVertexBufferSharedPtr vbuf =
			vertexData->vertexBufferBinding->getBuffer(0);
		unsigned char *vertexDataPtr = static_cast<unsigned char *>(
			vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));

		float *pFloat;
		Ogre::Vector3 pos;
		Ogre::Vector3 normal;

		for (size_t v = 0; v < vertexData->vertexCount; ++v) {
			// Read position
			posElem->baseVertexPointerToElement(vertexDataPtr,
							    &pFloat);
			pos.x = *pFloat++;
			pos.y = *pFloat++;
			pos.z = *pFloat;

			// Read normal
			normElem->baseVertexPointerToElement(vertexDataPtr,
							     &pFloat);
			normal.x = *pFloat++;
			normal.y = *pFloat++;
			normal.z = *pFloat;

			// Transform position and normal to world space
			Ogre::Vector3 worldVertex =
				worldPos + (worldRot * (pos * worldScale));
			Ogre::Vector3 worldNormal = worldRot * normal;
			worldNormal.normalise();

			// Calculate end point of normal line
			Ogre::Vector3 endPoint =
				worldVertex + worldNormal * NORMAL_LINE_LENGTH;

			// Colour based on normal direction for visual clarity
			// Red = X, Green = Y, Blue = Z
			Ogre::ColourValue colour(std::abs(worldNormal.x),
						 std::abs(worldNormal.y),
						 std::abs(worldNormal.z), 1.0f);

			// Draw line from vertex along normal
			manualObj->colour(colour);
			manualObj->position(worldVertex);
			manualObj->colour(colour);
			manualObj->position(endPoint);

			vertexDataPtr += vbuf->getVertexSize();
		}

		vbuf->unlock();
	}
}
