#pragma once

#include <flecs.h>
#include <memory>
#include <Ogre.h>
#include "../components/RigidBody.hpp"
#include "../components/PhysicsCollider.hpp"

namespace Procedural
{
class TriangleBuffer;
}

namespace Ogre
{
class StaticGeometry;
}

class CellGridSystem {
public:
	CellGridSystem(flecs::world &world, Ogre::SceneManager *sceneMgr);
	~CellGridSystem();

	void initialize();
	void update();

	// Force rebuild of a specific cell grid
	void rebuildCellGrid(flecs::entity entity);

	// Create/Update town material
	void updateTownMaterial(flecs::entity townEntity);

	// Expose frame/furniture placements for navmesh generation
	struct PlacementInfo {
		std::string meshName;
		Ogre::Vector3 position;
		Ogre::Quaternion rotation;
	};
	std::vector<PlacementInfo>
	getFramePlacements(flecs::entity entity) const;
	std::vector<PlacementInfo>
	getFurniturePlacements(flecs::entity entity) const;

	// Plaza mesh storage per district entity
	struct PlazaData {
		std::string meshName;
		Ogre::Entity *entity = nullptr;
		// Track texture dependency for automatic rebuild when texture changes
		flecs::entity textureEntity = flecs::entity::null();
		unsigned int textureVersion = 0;
		flecs::entity physicsParentEntity = flecs::entity::null();
	};

	// Lot base mesh storage per lot entity
	struct LotBaseData {
		std::string meshName;
		Ogre::Entity *entity = nullptr;
		// Track texture dependency for automatic rebuild when texture changes
		flecs::entity textureEntity = flecs::entity::null();
		unsigned int textureVersion = 0;
		flecs::entity physicsParentEntity = flecs::entity::null();
	};

	// Expose plaza and lot base mesh data for debug visualization
	const std::unordered_map<uint64_t, PlazaData> &getPlazaMeshes() const
	{
		return m_plazaMeshes;
	}
	const std::unordered_map<uint64_t, LotBaseData> &
	getLotBaseMeshes() const
	{
		return m_lotBaseMeshes;
	}

private:
	flecs::world &m_world;
	Ogre::SceneManager *m_sceneMgr;

	flecs::query<struct CellGridComponent> m_cellGridQuery;
	flecs::query<struct TownComponent> m_townQuery;
	flecs::query<struct DistrictComponent> m_districtQuery;
	flecs::query<struct LotComponent> m_lotQuery;

	bool m_initialized = false;
	int m_meshCount = 0;

	std::vector<flecs::entity> m_pendingPhysicsBuilds;

	// Build geometry from cell grid
	void buildCellGrid(flecs::entity entity,
			   struct CellGridComponent &grid);

	// Create triangle buffers for different parts
	void buildFloorsAndCeilings(const struct CellGridComponent &grid,
				    Procedural::TriangleBuffer &floorTb,
				    Procedural::TriangleBuffer &ceilingTb);
	void buildWalls(const struct CellGridComponent &grid,
			Procedural::TriangleBuffer &extWallTb,
			Procedural::TriangleBuffer &intWallTb,
			Procedural::TriangleBuffer &intWindowsTb);
	void buildDoors(const struct CellGridComponent &grid,
			Procedural::TriangleBuffer &extDoorsTb,
			Procedural::TriangleBuffer &intDoorsTb);
	void buildWindows(const struct CellGridComponent &grid,
			  Procedural::TriangleBuffer &extWindowsTb,
			  Procedural::TriangleBuffer &intWindowsTb);
	void buildCorners(const struct CellGridComponent &grid,
			  Procedural::TriangleBuffer &extWallTb);
	void buildInternalCorners(const struct CellGridComponent &grid,
				  Procedural::TriangleBuffer &intWallTb);

	// Build roofs
	void buildRoofs(flecs::entity lotEntity,
			const struct CellGridComponent &grid,
			Procedural::TriangleBuffer &roofTopTb,
			Procedural::TriangleBuffer &roofSideTb);

	// Build plaza for district
	void buildDistrictPlaza(flecs::entity entity,
				struct DistrictComponent &district);

	// Build lot base geometry
	void buildLotBase(flecs::entity entity, struct LotComponent &lot);

	// Build furniture meshes
	void buildFurniture(flecs::entity entity,
			    const struct CellGridComponent &grid);
	void destroyFurniture(flecs::entity entity);

	// Build window and door frames (3D frame meshes)
	void buildFrames(flecs::entity entity,
			 const struct CellGridComponent &grid,
			 const std::string &materialName);
	void createWindowFrameMeshes(const struct CellGridComponent &grid,
				     const std::string &materialName,
				     const std::string &meshPrefix,
				     flecs::entity materialEntity);
	void createDoorFrameMeshes(const struct CellGridComponent &grid,
				   const std::string &materialName,
				   const std::string &meshPrefix,
				   flecs::entity materialEntity);
	void placeWindowFrames(flecs::entity entity,
			       const struct CellGridComponent &grid,
			       Ogre::SceneNode *parentNode,
			       std::vector<Ogre::Entity *> &frameEntities);
	void placeDoorFrames(flecs::entity entity,
			     const struct CellGridComponent &grid,
			     Ogre::SceneNode *parentNode,
			     std::vector<Ogre::Entity *> &frameEntities);
	void destroyFrames(flecs::entity entity);

	// StaticGeometry versions (batched for better performance)
	void placeWindowFramesInStaticGeometry(
		flecs::entity entity, const struct CellGridComponent &grid,
		const Ogre::Vector3 &parentPos,
		const Ogre::Quaternion &parentRot,
		const Ogre::Vector3 &parentScale,
		Ogre::StaticGeometry *staticGeom, int &frameCount);
	void placeDoorFramesInStaticGeometry(
		flecs::entity entity, const struct CellGridComponent &grid,
		const Ogre::Vector3 &parentPos,
		const Ogre::Quaternion &parentRot,
		const Ogre::Vector3 &parentScale,
		Ogre::StaticGeometry *staticGeom, int &frameCount);

	// Convert triangle buffer to mesh
	Ogre::MeshPtr convertToMesh(const std::string &name,
				    Procedural::TriangleBuffer &tb,
				    const std::string &materialName);

	// Generate LOD levels for a mesh (shared settings for all procedural editor meshes)
	void generateLodForMesh(Ogre::MeshPtr mesh);

	// Apply UV mapping from color rects
	void applyUVMapping(Procedural::TriangleBuffer &tb,
			    flecs::entity entity, const std::string &rectName);

	// Apply UV mapping using texture coordinates directly
	void applyUVMappingToBuffer(Procedural::TriangleBuffer &tb,
				    const std::string &rectName,
				    flecs::entity materialEntity);

	// Destroy existing mesh
	void destroyCellGridMeshes(struct CellGridComponent &grid);
	void destroyCellGridMeshes(flecs::entity entity);

	// Physics helpers
	void addPhysicsCollider(flecs::entity physicsParent,
				const std::string &meshName,
				const Ogre::Vector3 &position,
				const Ogre::Quaternion &rotation);
	void buildPhysicsColliders(flecs::entity entity);
	void destroyPhysicsColliders(flecs::entity entity);

	// Mesh storage per entity
	struct MeshData {
		struct ColliderPlacement {
			std::string meshName;
			Ogre::Vector3 position;
			Ogre::Quaternion rotation;
		};

		std::string floorMesh;
		std::string ceilingMesh;
		std::string extWallMesh;
		std::string intWallMesh;
		std::string intWindowsMesh;
		std::vector<std::string> doorMeshes;
		std::vector<std::string> windowMeshes;
		std::string roofMesh;
		std::string roofSideMesh;
		std::vector<Ogre::Entity *> entities;

		// Track texture dependency for automatic rebuild when texture changes
		flecs::entity textureEntity = flecs::entity::null();
		unsigned int textureVersion = 0;

		// Frame meshes (unique per CellGrid for cellSize/cellHeight adaptation)
		std::string externalWindowFrameMesh;
		std::string internalWindowFrameMesh;
		std::string externalDoorFrameMesh;
		std::string internalDoorFrameMesh;

		// Frame entities attached to the SceneNode (so they move with transform)
		std::vector<Ogre::Entity *> frameEntities;

		// StaticGeometry for frames (batches all frames into single draw call)
		Ogre::StaticGeometry *frameStaticGeometry = nullptr;

		// Furniture entities
		std::vector<Ogre::Entity *> furnitureEntities;

		// Physics placement data for compound shape
		std::vector<ColliderPlacement> framePlacements;
		std::vector<ColliderPlacement> furniturePlacements;
		std::vector<flecs::entity> isolatedFurnitureEntities;
		flecs::entity physicsParentEntity = flecs::entity::null();
	};
	std::unordered_map<uint64_t, MeshData> m_entityMeshes;

	// Plaza mesh storage per district entity
	std::unordered_map<uint64_t, PlazaData> m_plazaMeshes;

	// Lot base mesh storage per lot entity
	std::unordered_map<uint64_t, LotBaseData> m_lotBaseMeshes;
};
