#include "EditorWaterPlaneSystem.hpp"
#include "../components/WaterPlane.hpp"
#include "../components/Transform.hpp"
#include "../components/WaterPhysics.hpp"
#include <OgreLogManager.h>
#include <OgreManualObject.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreGpuProgramParams.h>
#include <OgreCompositorManager.h>

static const uint32_t WATER_MASK = 0xF00;

void EditorWaterPlaneSystem::WaterRenderTargetListener::preRenderTargetUpdate(
	const Ogre::RenderTargetEvent &evt)
{
	if (waterMesh)
		waterMesh->setVisible(false);
}

void EditorWaterPlaneSystem::WaterRenderTargetListener::postRenderTargetUpdate(
	const Ogre::RenderTargetEvent &evt)
{
	if (waterMesh)
		waterMesh->setVisible(true);
}

EditorWaterPlaneSystem::EditorWaterPlaneSystem(flecs::world &world,
				       Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_query(world.query<WaterPlane, TransformComponent>())
{
}

EditorWaterPlaneSystem::~EditorWaterPlaneSystem() = default;

void EditorWaterPlaneSystem::update(float deltaTime, Ogre::Camera *camera)
{
	m_query.each([&](flecs::entity entity, WaterPlane &wp,
			 TransformComponent &transform) {
		if (!wp.enabled) {
			if (wp.sceneNode)
				wp.sceneNode->setVisible(false);
			return;
		}

		if (wp.dirty || !wp.manualObject) {
			rebuildWaterPlane(entity, wp, transform);
			wp.dirty = false;
		}

		if (!wp.sceneNode || !wp.manualObject)
			return;

		// Auto-sync Y from WaterPhysics
		if (wp.autoUpdateFromWaterPhysics) {
			m_world.query<WaterPhysics>().each(
				[&](flecs::entity, WaterPhysics &physics) {
					if (physics.waterSurfaceY != wp.waterSurfaceY) {
						wp.waterSurfaceY = physics.waterSurfaceY;
						wp.sceneNode->setPosition(
							wp.sceneNode->getPosition().x,
							wp.waterSurfaceY,
							wp.sceneNode->getPosition().z);
					}
				});
		}

		// Update shader time and params
		updateShaderParams(wp, deltaTime);

		// Update cameras
		if (camera)
			updateCameras(wp, camera);

		// Follow camera on XZ plane
		if (camera)
			updatePosition(wp, deltaTime, camera);

		// Alternate viewport updates
		if (wp.renderTarget) {
			if (wp.updateViewportIndex == 0 && wp.reflectionViewport)
				wp.reflectionViewport->update();
			else if (wp.updateViewportIndex == 1 && wp.refractionViewport)
				wp.refractionViewport->update();
			wp.updateViewportIndex = 1 - wp.updateViewportIndex;
		}

		wp.sceneNode->setVisible(true);
	});

	updateUnderwaterEffect(camera, deltaTime);
}

void EditorWaterPlaneSystem::rebuildWaterPlane(
	flecs::entity entity, WaterPlane &wp,
	TransformComponent &transform)
{
	cleanupWaterPlane(wp);

	Ogre::String baseName = "WaterPlane_" +
				Ogre::StringConverter::toString(entity.id());

	// Create scene node
	if (transform.node) {
		wp.sceneNode =
			transform.node->createChildSceneNode(baseName + "Node");
	} else {
		wp.sceneNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode(
			baseName + "Node");
	}
	wp.sceneNode->setPosition(0.0f, wp.waterSurfaceY, 0.0f);

	// Create plane mesh
	createPlaneMesh(wp);

	// Create render texture
	Ogre::String texName = baseName + "_RTT";
	wp.renderTexture = Ogre::TextureManager::getSingleton().createManual(
		texName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
		Ogre::TEX_TYPE_2D, wp.renderTextureSize, wp.renderTextureSize, 0,
		Ogre::PF_R8G8B8A8, Ogre::TU_RENDERTARGET);

	wp.renderTarget = wp.renderTexture->getBuffer()->getRenderTarget();
	wp.renderTarget->setAutoUpdated(false);

	// Set up clip planes
	wp.reflectionPlane =
		Ogre::Plane(Ogre::Vector3(0.0f, 1.0f, 0.0f), wp.waterSurfaceY);
	wp.reflectionClipPlane = Ogre::Plane(Ogre::Vector3(0.0f, 1.0f, 0.0f),
					     wp.waterSurfaceY - 0.1f);
	wp.refractionClipPlane = Ogre::Plane(
		Ogre::Vector3(0.0f, -1.0f, 0.0f), -(wp.waterSurfaceY + 0.1f));

	// Set listener to hide water during RTT
	m_listener.waterMesh = wp.manualObject;
	wp.renderTarget->addListener(&m_listener);

	// Create cameras with their own nodes
	wp.reflectionCamera = m_sceneMgr->createCamera(baseName + "ReflectionCam");
	wp.refractionCamera = m_sceneMgr->createCamera(baseName + "RefractionCam");

	Ogre::SceneNode *reflNode =
		m_sceneMgr->getRootSceneNode()->createChildSceneNode(
			baseName + "ReflectionNode");
	reflNode->attachObject(wp.reflectionCamera);

	Ogre::SceneNode *refrNode =
		m_sceneMgr->getRootSceneNode()->createChildSceneNode(
			baseName + "RefractionNode");
	refrNode->attachObject(wp.refractionCamera);

	// Reflection viewport (left half)
	wp.reflectionViewport = wp.renderTarget->addViewport(
		wp.reflectionCamera, 0, 0.0f, 0.0f, 0.5f, 1.0f);
	wp.reflectionViewport->setClearEveryFrame(true);
	wp.reflectionViewport->setBackgroundColour(
		Ogre::ColourValue(0.0f, 0.0f, 0.1f, 1.0f));
	wp.reflectionViewport->setOverlaysEnabled(false);
	wp.reflectionViewport->setSkiesEnabled(true);
	wp.reflectionViewport->setAutoUpdated(false);
	wp.reflectionViewport->setVisibilityMask(~WATER_MASK);

	// Refraction viewport (right half)
	wp.refractionViewport = wp.renderTarget->addViewport(
		wp.refractionCamera, 1, 0.5f, 0.0f, 0.5f, 1.0f);
	wp.refractionViewport->setClearEveryFrame(true);
	wp.refractionViewport->setBackgroundColour(
		Ogre::ColourValue(0.0f, 0.1f, 0.2f, 1.0f));
	wp.refractionViewport->setOverlaysEnabled(false);
	wp.refractionViewport->setSkiesEnabled(false);
	wp.refractionViewport->setAutoUpdated(false);
	wp.refractionViewport->setVisibilityMask(~WATER_MASK);

	// Apply material
	Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(
		"WaterPlane/Dynamic",
		Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	if (!mat) {
		Ogre::LogManager::getSingleton().logMessage(
			"WaterPlaneSystem: Warning - WaterPlane/Dynamic material not found, using placeholder");
	}
	wp.manualObject->setMaterialName(0, "WaterPlane/Dynamic");

	// Set visibility mask so cameras can hide water
	wp.manualObject->setVisibilityFlags(WATER_MASK);

	Ogre::LogManager::getSingleton().logMessage(
		"WaterPlaneSystem: Created water plane for entity " +
		Ogre::StringConverter::toString(entity.id()) +
		", size=" + Ogre::StringConverter::toString(wp.planeSize) +
		", Y=" + Ogre::StringConverter::toString(wp.waterSurfaceY));
}

void EditorWaterPlaneSystem::cleanupWaterPlane(WaterPlane &wp)
{
	if (wp.renderTarget) {
		wp.renderTarget->removeListener(&m_listener);
		wp.renderTarget->removeAllViewports();
		wp.renderTarget = nullptr;
	}
	wp.reflectionViewport = nullptr;
	wp.refractionViewport = nullptr;

	if (wp.renderTexture) {
		Ogre::TextureManager::getSingleton().remove(wp.renderTexture);
		wp.renderTexture.reset();
	}

	if (wp.reflectionCamera) {
		Ogre::SceneNode *node = wp.reflectionCamera->getParentSceneNode();
		if (node) {
			node->detachObject(wp.reflectionCamera);
			m_sceneMgr->destroySceneNode(node);
		}
		m_sceneMgr->destroyCamera(wp.reflectionCamera);
		wp.reflectionCamera = nullptr;
	}
	if (wp.refractionCamera) {
		Ogre::SceneNode *node = wp.refractionCamera->getParentSceneNode();
		if (node) {
			node->detachObject(wp.refractionCamera);
			m_sceneMgr->destroySceneNode(node);
		}
		m_sceneMgr->destroyCamera(wp.refractionCamera);
		wp.refractionCamera = nullptr;
	}

	if (wp.manualObject) {
		if (wp.sceneNode)
			wp.sceneNode->detachObject(wp.manualObject);
		m_sceneMgr->destroyManualObject(wp.manualObject);
		wp.manualObject = nullptr;
	}
	if (wp.sceneNode) {
		if (wp.sceneNode->getParentSceneNode()) {
			wp.sceneNode->getParentSceneNode()->removeChild(
				wp.sceneNode);
		}
		m_sceneMgr->destroySceneNode(wp.sceneNode);
		wp.sceneNode = nullptr;
	}
}

void EditorWaterPlaneSystem::createPlaneMesh(WaterPlane &wp)
{
	Ogre::String objName = "WaterPlaneMesh_" +
			       Ogre::StringConverter::toString(
					(size_t)wp.sceneNode);
	wp.manualObject = m_sceneMgr->createManualObject(objName);

	int segments = 20;
	float halfSize = wp.planeSize * 0.5f;
	float step = wp.planeSize / (float)segments;

	wp.manualObject->begin("WaterPlane/Dynamic",
			       Ogre::RenderOperation::OT_TRIANGLE_LIST);

	for (int z = 0; z <= segments; z++) {
		for (int x = 0; x <= segments; x++) {
			float px = -halfSize + x * step;
			float pz = -halfSize + z * step;
			float u = (float)x / (float)segments;
			float v = (float)z / (float)segments;
			wp.manualObject->position(px, 0.0f, pz);
			wp.manualObject->normal(0.0f, 1.0f, 0.0f);
			wp.manualObject->textureCoord(u, v);
		}
	}

	for (int z = 0; z < segments; z++) {
		for (int x = 0; x < segments; x++) {
			int i0 = z * (segments + 1) + x;
			int i1 = i0 + 1;
			int i2 = i0 + (segments + 1);
			int i3 = i2 + 1;
			wp.manualObject->triangle(i0, i2, i1);
			wp.manualObject->triangle(i1, i2, i3);
		}
	}

	wp.manualObject->end();
	wp.sceneNode->attachObject(wp.manualObject);
}

Ogre::SceneNode *EditorWaterPlaneSystem::getReflectionNode(WaterPlane &wp)
{
	if (!wp.reflectionCamera)
		return nullptr;
	Ogre::SceneNode *node = wp.reflectionCamera->getParentSceneNode();
	return node;
}

Ogre::SceneNode *EditorWaterPlaneSystem::getRefractionNode(WaterPlane &wp)
{
	if (!wp.refractionCamera)
		return nullptr;
	Ogre::SceneNode *node = wp.refractionCamera->getParentSceneNode();
	return node;
}

void EditorWaterPlaneSystem::updateCameras(WaterPlane &wp,
					   Ogre::Camera *camera)
{
	if (!camera)
		return;

	Ogre::SceneNode *camNode = camera->getParentSceneNode();
	Ogre::Vector3 camPos = camNode ? camNode->_getDerivedPosition()
				       : Ogre::Vector3::ZERO;
	Ogre::Quaternion camOrient = camNode
					     ? camNode->_getDerivedOrientation()
					     : Ogre::Quaternion::IDENTITY;

	// Reflection camera: mirrored below water plane
	if (wp.reflectionCamera) {
		wp.reflectionCamera->setAspectRatio(
			camera->getAspectRatio());
		wp.reflectionCamera->setNearClipDistance(
			camera->getNearClipDistance());
		wp.reflectionCamera->setFarClipDistance(
			camera->getFarClipDistance());
		Ogre::SceneNode *node = getReflectionNode(wp);
		if (node) {
			node->setPosition(camPos);
			node->setOrientation(camOrient);
		}
		wp.reflectionCamera->enableReflection(wp.reflectionPlane);
		wp.reflectionCamera->enableCustomNearClipPlane(
			wp.reflectionClipPlane);
	}

	// Refraction camera: same position/orientation, clipped above water
	if (wp.refractionCamera) {
		wp.refractionCamera->setAspectRatio(
			camera->getAspectRatio());
		wp.refractionCamera->setNearClipDistance(
			camera->getNearClipDistance());
		wp.refractionCamera->setFarClipDistance(
			camera->getFarClipDistance());
		Ogre::SceneNode *node = getRefractionNode(wp);
		if (node) {
			node->setPosition(camPos);
			node->setOrientation(camOrient);
		}
		wp.refractionCamera->enableCustomNearClipPlane(
			wp.refractionClipPlane);
	}
}

void EditorWaterPlaneSystem::updatePosition(WaterPlane &wp, float deltaTime,
					    Ogre::Camera *camera)
{
	if (!camera)
		return;

	wp.positionUpdateTimer += deltaTime;
	if (wp.positionUpdateTimer < WaterPlane::POSITION_UPDATE_INTERVAL)
		return;
	wp.positionUpdateTimer = 0.0f;

	Ogre::SceneNode *camNode = camera->getParentSceneNode();
	Ogre::Vector3 camPos = camNode ? camNode->_getDerivedPosition()
				       : Ogre::Vector3::ZERO;

	Ogre::Vector3 waterPos = wp.sceneNode->getPosition();
	Ogre::Vector3 targetPos = camPos;
	targetPos.y = wp.waterSurfaceY;

	Ogre::Vector3 d = targetPos - waterPos;
	d.y = 0.0f; // Only move on XZ

	if (d.squaredLength() < 100.0f * 100.0f)
		wp.sceneNode->translate(d * 3.0f * deltaTime);
	else
		wp.sceneNode->translate(d);
}

void EditorWaterPlaneSystem::updateShaderParams(WaterPlane &wp,
						float deltaTime)
{
	wp.shaderTime += deltaTime * wp.waveSpeed;

	Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(
		"WaterPlane/Dynamic",
		Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	if (!mat)
		return;

	Ogre::Pass *pass = mat->getTechnique(0)->getPass(0);
	Ogre::GpuProgramParametersSharedPtr fpParams =
		pass->getFragmentProgramParameters();
	if (!fpParams)
		return;

	fpParams->setNamedConstant("time", wp.shaderTime);
	fpParams->setNamedConstant("waveScale", wp.waveScale);
	fpParams->setNamedConstant(
		"waterColor",
		Ogre::Vector4(wp.waterColor.r, wp.waterColor.g,
			      wp.waterColor.b, wp.waterColor.a));
	fpParams->setNamedConstant("reflectivity", wp.reflectivity);

	Ogre::GpuProgramParametersSharedPtr vpParams =
		pass->getVertexProgramParameters();
	if (vpParams)
		vpParams->setNamedConstant("tiling", wp.tiling);
}

void EditorWaterPlaneSystem::updateUnderwaterEffect(Ogre::Camera *camera,
					    float deltaTime)
{
	if (!camera)
		return;

	Ogre::Viewport *viewport = camera->getViewport();
	if (!viewport)
		return;

	Ogre::SceneNode *camNode = camera->getParentSceneNode();
	float camY = camNode ? camNode->_getDerivedPosition().y : 0.0f;

	// Check if camera is below any enabled water plane
	bool underwater = false;
	m_query.each([&](flecs::entity, WaterPlane &wp,
			 TransformComponent &) {
		if (wp.enabled && camY < wp.waterSurfaceY)
			underwater = true;
	});

	// Add compositor to viewport once (or re-add if viewport changed)
	if (!m_compositorAdded || m_compositorViewport != viewport) {
		if (m_compositorAdded && m_compositorViewport)
			Ogre::CompositorManager::getSingleton().removeCompositor(
				m_compositorViewport, "Underwater");
		Ogre::CompositorInstance *instance =
			Ogre::CompositorManager::getSingleton().addCompositor(
				viewport, "Underwater");
		if (instance) {
			m_compositorAdded = true;
			m_compositorViewport = viewport;
		} else {
			Ogre::LogManager::getSingleton().logMessage(
				"WaterPlaneSystem: Warning - failed to add Underwater compositor");
		}
	}

	// Enable/disable compositor based on underwater state
	if (m_compositorAdded) {
		try {
			Ogre::CompositorManager::getSingleton().setCompositorEnabled(
				viewport, "Underwater", underwater);
		} catch (const Ogre::Exception &e) {
			Ogre::LogManager::getSingleton().logMessage(
				"WaterPlaneSystem: Warning - setCompositorEnabled failed: " +
				Ogre::String(e.what()));
		}
	}

	// Update shader time when underwater
	if (underwater && m_compositorAdded) {
		m_underwaterTime += deltaTime;
		Ogre::MaterialPtr mat =
			Ogre::MaterialManager::getSingleton().getByName(
				"Underwater/Post",
				Ogre::ResourceGroupManager::
					DEFAULT_RESOURCE_GROUP_NAME);
		if (mat && mat->getNumTechniques() > 0 &&
		    mat->getTechnique(0)->getNumPasses() > 0) {
			Ogre::Pass *pass = mat->getTechnique(0)->getPass(0);
			Ogre::GpuProgramParametersSharedPtr fpParams =
				pass->getFragmentProgramParameters();
			if (fpParams)
				fpParams->setNamedConstant(
					"time", m_underwaterTime);
		}
	}
}
