#include "Gizmo.hpp"
#include "../components/Transform.hpp"
#include "../components/StaticGeometryMember.hpp"
#include <cmath>

// Simple colors for axes - not using vertex colors since RTSS has issues
static const float COLOR_RED[3] = {1.0f, 0.0f, 0.0f};
static const float COLOR_GREEN[3] = {0.0f, 1.0f, 0.0f};
static const float COLOR_BLUE[3] = {0.0f, 0.0f, 1.0f};
static const float COLOR_YELLOW[3] = {1.0f, 1.0f, 0.0f};

Gizmo::Gizmo(Ogre::SceneManager *sceneMgr)
	: m_sceneMgr(sceneMgr)
	, m_gizmoNode(nullptr)
	, m_axisX(nullptr)
	, m_axisY(nullptr)
	, m_axisZ(nullptr)
	, m_mode(Mode::Translate)
	, m_selectedAxis(Axis::None)
	, m_hoveredAxis(Axis::None)
	, m_size(1.0f)
	, m_axisLength(2.0f)
	, m_isDragging(false)
	, m_dragStartT(0.0f)
{
	m_gizmoNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode("GizmoNode");
	
	m_axisX = m_sceneMgr->createManualObject("GizmoAxisX");
	m_axisY = m_sceneMgr->createManualObject("GizmoAxisY");
	m_axisZ = m_sceneMgr->createManualObject("GizmoAxisZ");
	
	m_gizmoNode->attachObject(m_axisX);
	m_gizmoNode->attachObject(m_axisY);
	m_gizmoNode->attachObject(m_axisZ);
	
	// Set render queue to overlay so gizmo draws on top of everything
	m_axisX->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
	m_axisY->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
	m_axisZ->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
	
	m_gizmoNode->setVisible(false);
}

void Gizmo::shutdown()
{
	if (!m_sceneMgr) {
		return;  // Already shutdown
	}
	
	// Detach objects from node first to avoid double-delete
	// Use try-catch since Ogre objects may be invalid during app shutdown
	if (m_gizmoNode && m_axisX) {
		try { m_gizmoNode->detachObject(m_axisX); } catch (...) {}
	}
	if (m_gizmoNode && m_axisY) {
		try { m_gizmoNode->detachObject(m_axisY); } catch (...) {}
	}
	if (m_gizmoNode && m_axisZ) {
		try { m_gizmoNode->detachObject(m_axisZ); } catch (...) {}
	}
	
	// Now destroy the manual objects
	if (m_axisX) {
		try { m_sceneMgr->destroyManualObject(m_axisX); } catch (...) {}
		m_axisX = nullptr;
	}
	if (m_axisY) {
		try { m_sceneMgr->destroyManualObject(m_axisY); } catch (...) {}
		m_axisY = nullptr;
	}
	if (m_axisZ) {
		try { m_sceneMgr->destroyManualObject(m_axisZ); } catch (...) {}
		m_axisZ = nullptr;
	}
	
	// Finally destroy the node
	if (m_gizmoNode) {
		try { m_sceneMgr->destroySceneNode(m_gizmoNode); } catch (...) {}
		m_gizmoNode = nullptr;
	}
	
	// Mark scene manager as invalid
	m_sceneMgr = nullptr;
}

Gizmo::~Gizmo()
{
	// If shutdown wasn't called, do cleanup now
	if (m_sceneMgr) {
		shutdown();
	}
}

void Gizmo::attachTo(flecs::entity entity)
{
	m_attachedEntity = entity;
	
	if (entity.is_alive() && entity.has<TransformComponent>()) {
		createGizmoGeometry();
		update();
		m_gizmoNode->setVisible(true);
	} else {
		m_gizmoNode->setVisible(false);
	}
}

void Gizmo::detach()
{
	m_attachedEntity = flecs::entity::null();
	m_gizmoNode->setVisible(false);
	m_selectedAxis = Axis::None;
	m_hoveredAxis = Axis::None;
	m_isDragging = false;
}

void Gizmo::update()
{
	if (!m_attachedEntity.is_alive() || !m_attachedEntity.has<TransformComponent>()) {
		m_gizmoNode->setVisible(false);
		return;
	}

	auto &transform = m_attachedEntity.get<TransformComponent>();
	if (transform.node) {
		// Use derived (world) position and orientation
		m_gizmoNode->setPosition(transform.node->_getDerivedPosition());
		m_gizmoNode->setOrientation(transform.node->_getDerivedOrientation());
		m_gizmoNode->setVisible(true);
	}
}

void Gizmo::setMode(Mode mode)
{
	if (m_mode != mode) {
		m_mode = mode;
		createGizmoGeometry();
	}
}

void Gizmo::createGizmoGeometry()
{
	float len = m_axisLength * m_size;
	float arrowSize = 0.3f * m_size;

	// X Axis - Red (or Yellow if selected/hovered)
	bool xSelected = (m_selectedAxis == Axis::X || m_hoveredAxis == Axis::X);
	m_axisX->clear();
	m_axisX->begin("Ogre/AxisGizmo", Ogre::RenderOperation::OT_LINE_LIST);
	if (xSelected) {
		m_axisX->colour(1.0f, 1.0f, 0.0f); // Yellow
	} else {
		m_axisX->colour(1.0f, 0.0f, 0.0f); // Red
	}
	// Main line
	m_axisX->position(0, 0, 0);
	m_axisX->position(len, 0, 0);
	// Arrow head lines
	m_axisX->position(len, 0, 0);
	m_axisX->position(len - arrowSize, arrowSize, 0);
	m_axisX->position(len, 0, 0);
	m_axisX->position(len - arrowSize, -arrowSize, 0);
	m_axisX->end();

	// Y Axis - Green
	bool ySelected = (m_selectedAxis == Axis::Y || m_hoveredAxis == Axis::Y);
	m_axisY->clear();
	m_axisY->begin("Ogre/AxisGizmo", Ogre::RenderOperation::OT_LINE_LIST);
	if (ySelected) {
		m_axisY->colour(1.0f, 1.0f, 0.0f); // Yellow
	} else {
		m_axisY->colour(0.0f, 1.0f, 0.0f); // Green
	}
	// Main line
	m_axisY->position(0, 0, 0);
	m_axisY->position(0, len, 0);
	// Arrow head lines
	m_axisY->position(0, len, 0);
	m_axisY->position(arrowSize, len - arrowSize, 0);
	m_axisY->position(0, len, 0);
	m_axisY->position(-arrowSize, len - arrowSize, 0);
	m_axisY->end();

	// Z Axis - Blue
	bool zSelected = (m_selectedAxis == Axis::Z || m_hoveredAxis == Axis::Z);
	m_axisZ->clear();
	m_axisZ->begin("Ogre/AxisGizmo", Ogre::RenderOperation::OT_LINE_LIST);
	if (zSelected) {
		m_axisZ->colour(1.0f, 1.0f, 0.0f); // Yellow
	} else {
		m_axisZ->colour(0.0f, 0.0f, 1.0f); // Blue
	}
	// Main line
	m_axisZ->position(0, 0, 0);
	m_axisZ->position(0, 0, len);
	// Arrow head lines
	m_axisZ->position(0, 0, len);
	m_axisZ->position(arrowSize, 0, len - arrowSize);
	m_axisZ->position(0, 0, len);
	m_axisZ->position(-arrowSize, 0, len - arrowSize);
	m_axisZ->end();
}

// Project ray onto axis line and return the parameter t along the axis
static bool projectRayOntoAxis(const Ogre::Ray& ray, const Ogre::Vector3& axisOrigin, 
    const Ogre::Vector3& axisDir, float& outT)
{
    Ogre::Vector3 rayOrigin = ray.getOrigin();
    Ogre::Vector3 rayDir = ray.getDirection();
    
    Ogre::Vector3 w0 = rayOrigin - axisOrigin;
    
    float a = rayDir.dotProduct(rayDir);
    float b = rayDir.dotProduct(axisDir);
    float c = axisDir.dotProduct(axisDir);
    float d = rayDir.dotProduct(w0);
    float e = axisDir.dotProduct(w0);
    
    float denom = a * c - b * b;
    
    if (std::abs(denom) < 0.0001f) {
        return false;
    }
    
    outT = (a * e - b * d) / denom;
    return true;
}

bool Gizmo::onMousePressed(const Ogre::Ray &mouseRay)
{
	if (!m_axisX->isVisible()) return false;
	
	m_selectedAxis = hitTest(mouseRay);
	
	if (m_selectedAxis != Axis::None && m_attachedEntity.is_alive() 
	    && m_attachedEntity.has<TransformComponent>()) {
		
		m_isDragging = true;
		auto &transform = m_attachedEntity.get_mut<TransformComponent>();
		
		// Use derived (world) position for dragging
		m_dragStartPosition = transform.node->_getDerivedPosition();
		
		// Get axis direction in world space from gizmo orientation
		switch (m_selectedAxis) {
			case Axis::X: m_dragAxisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_X; break;
			case Axis::Y: m_dragAxisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Y; break;
			case Axis::Z: m_dragAxisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Z; break;
			default: m_dragAxisDir = Ogre::Vector3::UNIT_X; break;
		}
		
		projectRayOntoAxis(mouseRay, m_dragStartPosition, m_dragAxisDir, m_dragStartT);
		
		return true;
	}
	
	return false;
}

bool Gizmo::onMouseMoved(const Ogre::Ray &mouseRay, const Ogre::Vector2 &mouseDelta)
{
	if (m_isDragging && m_selectedAxis != Axis::None 
	    && m_attachedEntity.is_alive() 
	    && m_attachedEntity.has<TransformComponent>()) {
		
		auto &transform = m_attachedEntity.get_mut<TransformComponent>();
		
		float currentT;
		if (!projectRayOntoAxis(mouseRay, m_dragStartPosition, m_dragAxisDir, currentT)) {
			return true;
		}
		
		float deltaT = currentT - m_dragStartT;
		
		// Calculate new world position
		Ogre::Vector3 newWorldPos = m_dragStartPosition + m_dragAxisDir * deltaT;
		
		// Convert to local position if node has a parent
		if (transform.node->getParent()) {
			transform.position = transform.node->getParent()->convertWorldToLocalPosition(newWorldPos);
		} else {
			transform.position = newWorldPos;
		}
		
		transform.applyToNode();
		transform.markChanged();
		
		// Mark StaticGeometryMember dirty if present
		if (m_attachedEntity.has<StaticGeometryMemberComponent>()) {
			m_attachedEntity.get_mut<StaticGeometryMemberComponent>().markDirty();
		}
		
		// Update gizmo to follow (use derived position)
		m_gizmoNode->setPosition(transform.node->_getDerivedPosition());
		
		return true;
	} else if (m_axisX->isVisible()) {
		Axis prevHover = m_hoveredAxis;
		m_hoveredAxis = hitTest(mouseRay);
		if (prevHover != m_hoveredAxis) {
			// Could update visualization here
		}
	}
	
	return false;
}

bool Gizmo::onMouseReleased()
{
	if (m_isDragging) {
		m_isDragging = false;
		m_selectedAxis = Axis::None;
		return true;
	}
	return false;
}

Gizmo::Axis Gizmo::hitTest(const Ogre::Ray &mouseRay)
{
	if (!m_gizmoNode || !m_axisX->isVisible())
		return Axis::None;

	Ogre::Vector3 gizmoPos = m_gizmoNode->getPosition();
	float len = m_axisLength * m_size;
	float threshold = 0.5f * m_size;

	float bestDist = 1000000.0f;
	Axis bestAxis = Axis::None;

	for (int i = 0; i < 3; ++i) {
		Ogre::Vector3 axisDir;
		if (i == 0) axisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_X;
		else if (i == 1) axisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Y;
		else axisDir = m_gizmoNode->getOrientation() * Ogre::Vector3::UNIT_Z;

		for (int j = 0; j <= 8; ++j) {
			float t = len * j / 8.0f;
			Ogre::Vector3 pointOnAxis = gizmoPos + axisDir * t;
			
			Ogre::Vector3 L = pointOnAxis - mouseRay.getOrigin();
			float tca = L.dotProduct(mouseRay.getDirection());
			
			if (tca < 0.01f) continue;
			
			float d2 = L.dotProduct(L) - tca * tca;
			if (d2 <= threshold * threshold) {
				if (tca < bestDist) {
					bestDist = tca;
					bestAxis = static_cast<Axis>(i + 1);
				}
			}
		}
	}
	
	return bestAxis;
}

void Gizmo::setVisible(bool visible)
{
	if (m_gizmoNode) {
		m_gizmoNode->setVisible(visible);
	}
}

bool Gizmo::isVisible() const
{
	return m_gizmoNode && m_axisX && m_axisX->isVisible();
}
