#include "EditorSunSystem.hpp"
#include "../components/Sun.hpp"
#include "../components/Transform.hpp"
#include <OgreLogManager.h>
#include <OgreManualObject.h>
#include <cmath>

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

EditorSunSystem::~EditorSunSystem() = default;

void EditorSunSystem::update(float deltaTime)
{
	m_query.each([&](flecs::entity entity, SunComponent &sun,
			 TransformComponent &transform) {
		if (!sun.enabled) {
			if (sun.light) {
				cleanupSun(sun);
			}
			return;
		}

		// Rebuild if dirty or first time
		if (sun.dirty || !sun.light) {
			rebuildSun(entity, sun, transform);
			sun.dirty = false;
		}

		// Advance time
		sun.timeOfDay += deltaTime * sun.timeSpeed;
		if (sun.timeOfDay >= 24.0f)
			sun.timeOfDay -= 24.0f;
		if (sun.timeOfDay < 0.0f)
			sun.timeOfDay += 24.0f;

		// Compute sun angle:
		// 6:00  = 0 rad      (horizon, east)
		// 12:00 = PI/2 rad   (zenith)
		// 18:00 = PI rad     (horizon, west)
		// 24:00 = 3*PI/2 rad (nadir)
		float sunAngle = (sun.timeOfDay - 6.0f) / 24.0f * 2.0f * M_PI;
		float sunElev = std::sin(sunAngle);

		// Light direction (points TOWARD scene from sun)
		float tiltRad = sun.orbitTilt * M_PI / 180.0f;
		Ogre::Vector3 lightDir;
		lightDir.x = -std::cos(sunAngle);
		lightDir.y = -sunElev;
		lightDir.z = -std::sin(sunAngle) * std::sin(tiltRad);
		lightDir.normalise();

		// Update light direction by orienting the light node
		if (sun.lightNode) {
			Ogre::Vector3 defaultDir(0, -1, 0);
			Ogre::Quaternion rot = defaultDir.getRotationTo(lightDir);
			sun.lightNode->setOrientation(rot);
		}

		// Determine if sun is above horizon
		bool isDay = sunElev > -0.1f;

		// Interpolate light color
		Ogre::ColourValue lightColor;
		float intensity = sun.intensity;
		if (isDay) {
			float t = std::clamp((sunElev + 0.1f) / 0.3f, 0.0f, 1.0f);
			lightColor = Ogre::ColourValue::White * (1.0f - t) +
				     sun.sunColor * t;
			// Dim at horizon
			intensity *= std::clamp((sunElev + 0.1f) / 0.2f, 0.0f, 1.0f);
		} else {
			lightColor = sun.moonColor;
			intensity *= 0.3f;
		}

		if (sun.light) {
			sun.light->setDiffuseColour(lightColor * intensity);
			sun.light->setSpecularColour(lightColor * intensity * 0.5f);
		}

		// Update ambient light
		Ogre::ColourValue ambient;
		if (sunElev > 0.2f) {
			// Day
			ambient = sun.ambientDay;
		} else if (sunElev < -0.2f) {
			// Night
			ambient = sun.ambientNight;
		} else if (sun.timeOfDay < 12.0f) {
			// Sunrise transition
			float t = std::clamp((sunElev + 0.2f) / 0.4f, 0.0f, 1.0f);
			ambient = sun.ambientSunrise * (1.0f - t) +
				  sun.ambientDay * t;
		} else {
			// Sunset transition
			float t = std::clamp((sunElev + 0.2f) / 0.4f, 0.0f, 1.0f);
			ambient = sun.ambientSunset * (1.0f - t) +
				  sun.ambientDay * t;
		}
		m_sceneMgr->setAmbientLight(ambient);

		// Update sphere positions
		float orbitRadius = 200.0f;
		Ogre::Vector3 sunPos = -lightDir * orbitRadius;
		Ogre::Vector3 moonPos = lightDir * orbitRadius;

		if (sun.sunSphereNode) {
			sun.sunSphereNode->setPosition(sunPos);
			sun.sunSphereNode->setVisible(
				sun.showSunSphere && sunElev > -0.2f);
		}
		if (sun.moonSphereNode) {
			sun.moonSphereNode->setPosition(moonPos);
			sun.moonSphereNode->setVisible(
				sun.showMoonSphere && sunElev < 0.2f);
		}
	});
}

void EditorSunSystem::rebuildSun(flecs::entity entity, SunComponent &sun,
				 TransformComponent &transform)
{
	cleanupSun(sun);

	// Create or get scene node from transform
	if (!transform.node) {
		Ogre::LogManager::getSingleton().logMessage(
			"SunSystem: Entity has no transform node, skipping");
		return;
	}

	// Create directional light
	Ogre::String lightName = "SunLight_" +
				 Ogre::StringConverter::toString(entity.id());
	sun.light = m_sceneMgr->createLight(lightName);
	sun.light->setType(Ogre::Light::LT_DIRECTIONAL);
	sun.light->setDiffuseColour(sun.sunColor * sun.intensity);
	sun.light->setSpecularColour(
		Ogre::ColourValue(0.4f, 0.4f, 0.4f) * sun.intensity);
	sun.light->setCastShadows(sun.castShadows);

	// Create a child node for the light (separate from entity node
	// so we can orbit it independently)
	sun.lightNode = transform.node->createChildSceneNode(
		lightName + "Node");
	sun.lightNode->attachObject(sun.light);

	// Create sun sphere
	if (sun.showSunSphere) {
		Ogre::String sunName = "SunSphere_" +
				       Ogre::StringConverter::toString(
					       entity.id());
		sun.sunSphere = m_sceneMgr->createManualObject(sunName);
		createSphere(sun.sunSphere, sun.sunSphereSize,
			     Ogre::ColourValue(1.0f, 0.95f, 0.8f));
		sun.sunSphereNode =
			transform.node->createChildSceneNode(sunName + "Node");
		sun.sunSphereNode->attachObject(sun.sunSphere);
	}

	// Create moon sphere
	if (sun.showMoonSphere) {
		Ogre::String moonName = "MoonSphere_" +
					Ogre::StringConverter::toString(
						entity.id());
		sun.moonSphere = m_sceneMgr->createManualObject(moonName);
		createSphere(sun.moonSphere, sun.moonSphereSize,
			     Ogre::ColourValue(0.8f, 0.8f, 0.9f));
		sun.moonSphereNode =
			transform.node->createChildSceneNode(moonName + "Node");
		sun.moonSphereNode->attachObject(sun.moonSphere);
	}

	Ogre::LogManager::getSingleton().logMessage(
		"SunSystem: Created sun for entity " +
		Ogre::StringConverter::toString(entity.id()));
}

void EditorSunSystem::cleanupSun(SunComponent &sun)
{
	if (sun.sunSphere) {
		if (sun.sunSphereNode) {
			sun.sunSphereNode->detachObject(sun.sunSphere);
		}
		m_sceneMgr->destroyManualObject(sun.sunSphere);
		sun.sunSphere = nullptr;
	}
	if (sun.sunSphereNode) {
		if (sun.sunSphereNode->getParentSceneNode()) {
			sun.sunSphereNode->getParentSceneNode()->removeChild(
				sun.sunSphereNode);
		}
		m_sceneMgr->destroySceneNode(sun.sunSphereNode);
		sun.sunSphereNode = nullptr;
	}

	if (sun.moonSphere) {
		if (sun.moonSphereNode) {
			sun.moonSphereNode->detachObject(sun.moonSphere);
		}
		m_sceneMgr->destroyManualObject(sun.moonSphere);
		sun.moonSphere = nullptr;
	}
	if (sun.moonSphereNode) {
		if (sun.moonSphereNode->getParentSceneNode()) {
			sun.moonSphereNode->getParentSceneNode()->removeChild(
				sun.moonSphereNode);
		}
		m_sceneMgr->destroySceneNode(sun.moonSphereNode);
		sun.moonSphereNode = nullptr;
	}

	if (sun.light) {
		if (sun.lightNode) {
			sun.lightNode->detachObject(sun.light);
		}
		m_sceneMgr->destroyLight(sun.light);
		sun.light = nullptr;
	}
	if (sun.lightNode) {
		if (sun.lightNode->getParentSceneNode()) {
			sun.lightNode->getParentSceneNode()->removeChild(
				sun.lightNode);
		}
		m_sceneMgr->destroySceneNode(sun.lightNode);
		sun.lightNode = nullptr;
	}
}

void EditorSunSystem::createSphere(Ogre::ManualObject *obj, float radius,
				   const Ogre::ColourValue &color)
{
	// Simple icosphere approximation (octahedron + one subdivision)
	// 8 faces, subdivided once = 32 faces
	obj->begin("BaseWhiteNoLighting",
		   Ogre::RenderOperation::OT_TRIANGLE_LIST);

	struct Vertex {
		Ogre::Vector3 p;
	};
	std::vector<Vertex> verts;
	std::vector<int> indices;

	// Octahedron vertices
	verts.push_back({ Ogre::Vector3(0, 1, 0) * radius });
	verts.push_back({ Ogre::Vector3(0, -1, 0) * radius });
	verts.push_back({ Ogre::Vector3(-1, 0, 0) * radius });
	verts.push_back({ Ogre::Vector3(1, 0, 0) * radius });
	verts.push_back({ Ogre::Vector3(0, 0, 1) * radius });
	verts.push_back({ Ogre::Vector3(0, 0, -1) * radius });

	// Octahedron faces
	int faces[] = { 0, 4, 3, 0, 3, 5, 0, 5, 2, 0, 2, 4,
			1, 3, 4, 1, 5, 3, 1, 2, 5, 1, 4, 2 };

	for (int i = 0; i < 24; i += 3) {
		indices.push_back(faces[i]);
		indices.push_back(faces[i + 1]);
		indices.push_back(faces[i + 2]);
	}

	// Subdivide once
	std::vector<int> newIndices;
	for (size_t i = 0; i < indices.size(); i += 3) {
		Ogre::Vector3 a = verts[indices[i]].p;
		Ogre::Vector3 b = verts[indices[i + 1]].p;
		Ogre::Vector3 c = verts[indices[i + 2]].p;

		Ogre::Vector3 ab = (a + b).normalisedCopy() * radius;
		Ogre::Vector3 bc = (b + c).normalisedCopy() * radius;
		Ogre::Vector3 ca = (c + a).normalisedCopy() * radius;

		int ia = indices[i];
		int ib = indices[i + 1];
		int ic = indices[i + 2];
		int iab = (int)verts.size();
		verts.push_back({ ab });
		int ibc = (int)verts.size();
		verts.push_back({ bc });
		int ica = (int)verts.size();
		verts.push_back({ ca });

		newIndices.push_back(ia);
		newIndices.push_back(iab);
		newIndices.push_back(ica);
		newIndices.push_back(iab);
		newIndices.push_back(ib);
		newIndices.push_back(ibc);
		newIndices.push_back(ica);
		newIndices.push_back(ibc);
		newIndices.push_back(ic);
		newIndices.push_back(iab);
		newIndices.push_back(ibc);
		newIndices.push_back(ica);
	}
	indices = newIndices;

	// Write vertices
	for (const auto &v : verts) {
		obj->position(v.p);
		obj->colour(color);
	}
	for (int idx : indices) {
		obj->index(idx);
	}

	obj->end();
}
