#ifndef EDITSCENE_TERRAINSYSTEM_HPP
#define EDITSCENE_TERRAINSYSTEM_HPP
#pragma once

#include <flecs.h>
#include <Ogre.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <OgreTerrainPaging.h>
#include <OgreTerrainPagedWorldSection.h>
#include <OgrePageManager.h>
#include <OgrePage.h>
#include <OgrePagedWorld.h>
#include <OgreManualObject.h>
#include <OgreMaterial.h>
#include <memory>
#include <unordered_map>
#include <set>
#include <deque>
#include <mutex>
#include <cstdint>

#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyFilter.h>
#include <Jolt/Physics/Body/Body.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>

#include "TerrainCommands.hpp"

class JoltPhysicsWrapper;

class TerrainSystem {
public:
	TerrainSystem(flecs::world &world, Ogre::SceneManager *sceneMgr,
		      Ogre::Camera *camera, JoltPhysicsWrapper *physics);
	~TerrainSystem();

	TerrainSystem(const TerrainSystem &) = delete;
	TerrainSystem &operator=(const TerrainSystem &) = delete;

	static TerrainSystem *getInstance() { return s_instance; }

	void update(float deltaTime);
	void deactivate();
	bool isActive() const { return m_active; }
	float getHeightAt(const Ogre::Vector3 &worldPos) const;

	bool raycastTerrain(const Ogre::Ray &ray, float &outT,
			    Ogre::Vector3 &outNormal) const;

	void setShowTerrainColliders(bool show);
	bool getShowTerrainColliders() const
	{
		return m_showTerrainColliders;
	}

	/* --- Heightmap data (M3) --- */
	float sampleHeightAt(long worldX, long worldZ) const;
	float sampleHeightAtLocked(long worldX, long worldZ) const;
	void setHeightAt(long worldX, long worldZ, float value);
	bool loadHeightmap(const std::string &path);
	bool saveHeightmap(const std::string &path);
	bool loadSceneHeightmap(const struct TerrainComponent &tc);
	bool saveSceneHeightmap(const struct TerrainComponent &tc);
	std::string getHeightmapPath(const struct TerrainComponent &tc) const;

	/* Blend map save/load (M3) */
	bool saveBlendMaps(const std::string &dirPath) const;
	bool loadBlendMaps(const std::string &dirPath);
	bool saveSceneBlendMaps(const struct TerrainComponent &tc) const;
	bool loadSceneBlendMaps(const struct TerrainComponent &tc);

	void markPageDirty(long pageX, long pageY);
	void rebuildDirtyPages();
	void processDeferredReloads();

	/* --- Sculpting (M3) --- */
	enum class SculptTool { Raise, Lower, Smooth, Flatten };
	bool getSculptMode() const { return m_sculptMode; }
	void setSculptMode(bool v);
	bool isSculpting() const { return m_sculptMode; }
	SculptTool getSculptTool() const { return m_sculptTool; }
	void setSculptTool(SculptTool t);
	float getSculptRadius() const { return m_sculptRadius; }
	void setSculptRadius(float r) { m_sculptRadius = r; }
	float getSculptStrength() const { return m_sculptStrength; }
	void setSculptStrength(float s) { m_sculptStrength = s; }
	void snapCameraAboveTerrain();
	void applySculptBrush(const Ogre::Vector3 &worldPos);

	/* --- Splat painting (M3) --- */
	bool getPaintMode() const { return m_paintMode; }
	void setPaintMode(bool v)
	{
		if (v == m_paintMode)
			return;
		m_paintMode = v;
		if (v) {
			/* Paint mode is mutually exclusive with sculpt. */
			if (m_sculptMode)
				setSculptMode(false);
		} else {
			hideBrushDecal();
		}
	}
	bool isPainting() const { return m_paintMode; }
	int getPaintLayerIndex() const { return m_paintLayerIndex; }
	void setPaintLayerIndex(int i) { m_paintLayerIndex = i; }
	float getPaintRadius() const { return m_paintRadius; }
	void setPaintRadius(float r) { m_paintRadius = r; }
	float getPaintStrength() const { return m_paintStrength; }
	void setPaintStrength(float s) { m_paintStrength = s; }
	void applySplatBrush(const Ogre::Vector3 &worldPos);

	/* --- Brush decal (M3) --- */
	void updateBrushDecal(const Ogre::Vector3 &worldPos,
			      const Ogre::Vector3 &normal, float radius);
	void hideBrushDecal();

	/* Convert physical heightmap X to visual display X.
	 * Terrain pages render column i at X = i*step - halfSize
	 * but the height data lives at X = pageMinX + i*step.
	 * Same for Z but Z-inverted (see physicalToVisualZ). */
	float physicalToVisualX(float physicalX) const;

	/* Inverse: physical X corresponding to a given visual world X. */
	float visualToPhysicalX(float visualX) const;

	/* Half the terrain world size (for coordinate math). */
	float getTerrainWorldHalfSize() const;

	Ogre::TerrainGroup *getTerrainGroup() const
	{
		return mTerrainGroup;
	}

	/* Convert physical heightmap Z to visual display Z.
	 * Terrain pages render row j at Z = pageMinZ + halfSize - j*step
	 * but the height data lives at Z = pageMinZ + j*step. */
	float physicalToVisualZ(float physicalZ) const;

	/* Inverse: physical Z that maps to the given visual world Z.
	 * Uses visual page indexing, unlike physicalToVisualZ. */
	float visualToPhysicalZ(float visualZ) const;

	TerrainCommandQueue &getCommandQueue()
	{
		return m_commandQueue;
	}

	friend class TerrainCommandQueue;

private:
	void activate(struct TerrainComponent &tc,
		      struct TransformComponent &xform);
	void ensureHeightmapLoaded(struct TerrainComponent &tc);
	void updatePageGeometry(long pageX, long pageY);
	void fillPageHeightData(Ogre::TerrainGroup *group, long x, long y,
				float *heightMap);
	long worldToPage(float worldCoord, float worldSize) const;
	void ensurePageColliders();

	struct TerrainCollider {
		long pageX, pageY;
		JPH::BodyID bodyId;
		bool pendingRemove = false;
	};

	class DummyPageProvider : public Ogre::PageProvider {
	public:
		bool prepareProceduralPage(Ogre::Page *,
					   Ogre::PagedWorldSection *) override
		{
			return true;
		}
		bool loadProceduralPage(Ogre::Page *,
					Ogre::PagedWorldSection *) override
		{
			return true;
		}
		bool unloadProceduralPage(Ogre::Page *page,
					  Ogre::PagedWorldSection *) override;
		bool unprepareProceduralPage(Ogre::Page *,
					     Ogre::PagedWorldSection *) override
		{
			return true;
		}
		TerrainSystem *owner = nullptr;
	};

	class CustomTerrainDefiner :
		public Ogre::TerrainPagedWorldSection::TerrainDefiner {
	public:
		CustomTerrainDefiner(TerrainSystem *sys)
			: Ogre::TerrainPagedWorldSection::TerrainDefiner()
			, m_sys(sys)
		{
		}
		void define(Ogre::TerrainGroup *terrainGroup, long x,
			    long y) override;
	private:
		TerrainSystem *m_sys;
	};

	class TerrainBodyDrawFilter : public JPH::BodyDrawFilter {
	public:
		bool ShouldDraw(const JPH::Body &inBody) const override;
		void addTerrainBody(const JPH::BodyID &id)
		{
			m_terrainIds.insert(id);
		}
		void removeTerrainBody(const JPH::BodyID &id)
		{
			m_terrainIds.erase(id);
		}
		void clear() { m_terrainIds.clear(); }
		bool showTerrain = false;
	private:
		std::set<JPH::BodyID> m_terrainIds;
	};

	JPH::ShapeRefC buildPageCollider(Ogre::Terrain *terrain);
	void queueColliderCreate(long x, long y);
	void queueColliderRemove(long x, long y);
	void processColliderCreates();
	void processColliderRemoves();
	void removeAllColliders();
	void destroyCollider(uint64_t key, TerrainCollider &collider);

	flecs::world &m_world;
	Ogre::SceneManager *m_sceneMgr;
	Ogre::Camera *m_camera;
	JoltPhysicsWrapper *m_physics = nullptr;
	bool m_active = false;
	bool m_showTerrainColliders = false;

	static TerrainSystem *s_instance;

	Ogre::TerrainGlobalOptions *mTerrainGlobals = nullptr;
	Ogre::TerrainGroup *mTerrainGroup = nullptr;
	Ogre::PageManager *mPageManager = nullptr;
	Ogre::TerrainPaging *mTerrainPaging = nullptr;
	Ogre::PagedWorld *mPagedWorld = nullptr;
	Ogre::TerrainPagedWorldSection *mTerrainPagedWorldSection = nullptr;
	std::unique_ptr<CustomTerrainDefiner> mTerrainDefiner;
	std::unique_ptr<DummyPageProvider> mDummyPageProvider;

	std::unordered_map<uint64_t, TerrainCollider> mColliders;
	std::deque<std::pair<long, long>> mColliderCreateQueue;
	std::set<uint64_t> mPendingColliderPages;
	std::mutex m_colliderQueueMutex;
	TerrainBodyDrawFilter mBodyDrawFilter;

	/* Paging range used for synchronous page load and collider polling. */
	long m_pageMinX = -1, m_pageMinY = -1;
	long m_pageMaxX = 1, m_pageMaxY = 1;

	/* Heightmap data (M3) — protected by m_heightmapMutex because
	 * collider building and page updates may happen concurrently. */
	mutable std::mutex m_heightmapMutex;
	std::vector<float> m_heightData;
	bool m_heightmapLoaded = false;
	int m_heightmapRes = 0;
	float m_heightmapWorldMinX = 0;
	float m_heightmapWorldMinZ = 0;
	float m_heightmapWorldSize = 2000.0f;

	std::set<uint64_t> m_dirtyPages;
	std::vector<uint64_t> m_reloadQueue;
	bool hasHeightmap() const { return m_heightmapLoaded; }

	/* Sculpting state */
	bool m_sculptMode = false;
	SculptTool m_sculptTool = SculptTool::Raise;
	float m_sculptRadius = 100.0f;
	float m_sculptStrength = 0.5f;

	/* Splat paint state */
	bool m_paintMode = false;
	int m_paintLayerIndex = 1;
	float m_paintRadius = 100.0f;
	float m_paintStrength = 0.5f;

	/* --- Sculpt preview (M3) --- */
	struct SculptPreview {
		Ogre::ManualObject *manualObj = nullptr;
		Ogre::SceneNode *sceneNode = nullptr;
		long pageX, pageY;
	};
	std::unordered_map<uint64_t, SculptPreview> m_sculptPreviews;
	Ogre::MaterialPtr m_sculptPreviewMaterial;
	bool m_sculptPreviewsActive = false;

	void beginSculptPreviews();
	void endSculptPreviews();
	void buildSculptPreview(long pageX, long pageY);
	void updateSculptDirtyPages();
	void ensureSculptPreviewMaterial();

	/* Brush decal (M3) */
	Ogre::ManualObject *m_brushDecal = nullptr;
	Ogre::SceneNode *m_brushDecalNode = nullptr;

	/* Entity tracking */
	flecs::entity_t m_terrainEntityId = 0;

	flecs::query<struct TerrainComponent, struct TransformComponent>
		m_terrainQuery;

	TerrainCommandQueue m_commandQueue;
};

#endif
