#include "Cursor3D.hpp"
#include "../components/Transform.hpp"
#include <cmath>

static const float COLOR_RED[3] = { 1.0f, 0.2f, 0.2f };
static const float COLOR_GREEN[3] = { 0.2f, 1.0f, 0.2f };
static const float COLOR_BLUE[3] = { 0.2f, 0.4f, 1.0f };
static const float COLOR_CYAN[3] = { 0.0f, 1.0f, 1.0f };
static const float COLOR_YELLOW[3] = { 1.0f, 1.0f, 0.0f };

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;
}

Cursor3D::Cursor3D(Ogre::SceneManager *sceneMgr)
	: m_sceneMgr(sceneMgr)
	, m_cursorNode(nullptr)
	, m_axisX(nullptr)
	, m_axisY(nullptr)
	, m_axisZ(nullptr)
	, m_centerMarker(nullptr)
	, m_position(Ogre::Vector3::ZERO)
	, m_orientation(Ogre::Quaternion::IDENTITY)
	, m_size(1.0f)
	, m_visible(false)
	, m_mode(Mode::Translate)
	, m_selectedAxis(Axis::None)
	, m_hoveredAxis(Axis::None)
	, m_isDragging(false)
	, m_dragStartT(0.0f)
	, m_dragStartAngle(0.0f)
{
	m_cursorNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode(
		"Cursor3DNode");

	m_axisX = m_sceneMgr->createManualObject("CursorAxisX");
	m_axisY = m_sceneMgr->createManualObject("CursorAxisY");
	m_axisZ = m_sceneMgr->createManualObject("CursorAxisZ");
	m_centerMarker = m_sceneMgr->createManualObject("CursorCenter");

	m_cursorNode->attachObject(m_axisX);
	m_cursorNode->attachObject(m_axisY);
	m_cursorNode->attachObject(m_axisZ);
	m_cursorNode->attachObject(m_centerMarker);

	m_axisX->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
	m_axisY->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
	m_axisZ->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
	m_centerMarker->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);

	m_cursorNode->setVisible(false);
	createGeometry();
}

void Cursor3D::shutdown()
{
	if (!m_sceneMgr)
		return;
	auto detach = [&](Ogre::ManualObject *obj) {
		if (m_cursorNode && obj) {
			try {
				m_cursorNode->detachObject(obj);
			} catch (...) {
			}
		}
	};
	auto destroy = [&](Ogre::ManualObject *obj) {
		if (obj) {
			try {
				m_sceneMgr->destroyManualObject(obj);
			} catch (...) {
			}
		}
	};
	detach(m_axisX);
	detach(m_axisY);
	detach(m_axisZ);
	detach(m_centerMarker);
	destroy(m_axisX);
	m_axisX = nullptr;
	destroy(m_axisY);
	m_axisY = nullptr;
	destroy(m_axisZ);
	m_axisZ = nullptr;
	destroy(m_centerMarker);
	m_centerMarker = nullptr;
	if (m_cursorNode) {
		try {
			m_sceneMgr->destroySceneNode(m_cursorNode);
		} catch (...) {
		}
		m_cursorNode = nullptr;
	}
	m_sceneMgr = nullptr;
}

Cursor3D::~Cursor3D()
{
	if (m_sceneMgr)
		shutdown();
}

void Cursor3D::setPosition(const Ogre::Vector3 &pos)
{
	m_position = pos;
	updateNodeTransform();
}

void Cursor3D::setOrientation(const Ogre::Quaternion &rot)
{
	m_orientation = rot;
	updateNodeTransform();
}

void Cursor3D::setVisible(bool visible)
{
	m_visible = visible;
	if (m_cursorNode)
		m_cursorNode->setVisible(visible);
}

bool Cursor3D::isVisible() const
{
	return m_visible;
}

void Cursor3D::setSize(float size)
{
	m_size = size;
	createGeometry();
}

void Cursor3D::snapToTransform(const TransformComponent &transform)
{
	if (transform.node) {
		m_position = transform.node->_getDerivedPosition();
		m_orientation = transform.node->_getDerivedOrientation();
	} else {
		m_position = transform.position;
		m_orientation = transform.rotation;
	}
	updateNodeTransform();
}

void Cursor3D::applyToTransform(TransformComponent &transform) const
{
	if (!transform.node)
		return;
	Ogre::SceneNode *parent = static_cast<Ogre::SceneNode *>(
		transform.node->getParent());
	if (parent) {
		transform.position = parent->convertWorldToLocalPosition(
			m_position);
		transform.rotation = parent->convertWorldToLocalOrientation(
			m_orientation);
	} else {
		transform.position = m_position;
		transform.rotation = m_orientation;
	}
	transform.applyToNode();
	transform.markChanged();
}

void Cursor3D::updateNodeTransform()
{
	if (m_cursorNode) {
		m_cursorNode->setPosition(m_position);
		m_cursorNode->setOrientation(m_orientation);
	}
}

void Cursor3D::createGeometry()
{
	float len = 0.5f * m_size;
	float half = 0.05f * m_size;

	// X Axis
	bool xSel = (m_selectedAxis == Axis::X || m_hoveredAxis == Axis::X);
	m_axisX->clear();
	m_axisX->begin("Ogre/AxisGizmo",
			Ogre::RenderOperation::OT_LINE_LIST);
	m_axisX->colour(xSel ? COLOR_YELLOW[0] : COLOR_RED[0],
			xSel ? COLOR_YELLOW[1] : COLOR_RED[1],
			xSel ? COLOR_YELLOW[2] : COLOR_RED[2]);
	m_axisX->position(0, 0, 0);
	m_axisX->position(len, 0, 0);
	m_axisX->end();

	// Y Axis
	bool ySel = (m_selectedAxis == Axis::Y || m_hoveredAxis == Axis::Y);
	m_axisY->clear();
	m_axisY->begin("Ogre/AxisGizmo",
			Ogre::RenderOperation::OT_LINE_LIST);
	m_axisY->colour(ySel ? COLOR_YELLOW[0] : COLOR_GREEN[0],
			ySel ? COLOR_YELLOW[1] : COLOR_GREEN[1],
			ySel ? COLOR_YELLOW[2] : COLOR_GREEN[2]);
	m_axisY->position(0, 0, 0);
	m_axisY->position(0, len, 0);
	m_axisY->end();

	// Z Axis
	bool zSel = (m_selectedAxis == Axis::Z || m_hoveredAxis == Axis::Z);
	m_axisZ->clear();
	m_axisZ->begin("Ogre/AxisGizmo",
			Ogre::RenderOperation::OT_LINE_LIST);
	m_axisZ->colour(zSel ? COLOR_YELLOW[0] : COLOR_BLUE[0],
			zSel ? COLOR_YELLOW[1] : COLOR_BLUE[1],
			zSel ? COLOR_YELLOW[2] : COLOR_BLUE[2]);
	m_axisZ->position(0, 0, 0);
	m_axisZ->position(0, 0, len);
	m_axisZ->end();

	// Center marker
	m_centerMarker->clear();
	m_centerMarker->begin("Ogre/AxisGizmo",
			      Ogre::RenderOperation::OT_LINE_LIST);
	m_centerMarker->colour(COLOR_CYAN[0], COLOR_CYAN[1],
			       COLOR_CYAN[2]);
	Ogre::Vector3 c[8] = {
		Ogre::Vector3(-half, -half, -half),
		Ogre::Vector3(half, -half, -half),
		Ogre::Vector3(half, half, -half),
		Ogre::Vector3(-half, half, -half),
		Ogre::Vector3(-half, -half, half),
		Ogre::Vector3(half, -half, half),
		Ogre::Vector3(half, half, half),
		Ogre::Vector3(-half, half, half),
	};
	auto line = [&](int a, int b) {
		m_centerMarker->position(c[a]);
		m_centerMarker->position(c[b]);
	};
	line(0, 1);
	line(1, 2);
	line(2, 3);
	line(3, 0);
	line(4, 5);
	line(5, 6);
	line(6, 7);
	line(7, 4);
	line(0, 4);
	line(1, 5);
	line(2, 6);
	line(3, 7);
	m_centerMarker->end();
}

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

	Ogre::Vector3 cursorPos = m_cursorNode->getPosition();
	float len = 0.5f * m_size;
	float threshold = 0.15f * 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_cursorNode->getOrientation() *
				  Ogre::Vector3::UNIT_X;
		else if (i == 1)
			axisDir = m_cursorNode->getOrientation() *
				  Ogre::Vector3::UNIT_Y;
		else
			axisDir = m_cursorNode->getOrientation() *
				  Ogre::Vector3::UNIT_Z;

		for (int j = 0; j <= 8; ++j) {
			float t = len * j / 8.0f;
			Ogre::Vector3 pointOnAxis = cursorPos + 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;
}

bool Cursor3D::onMousePressed(const Ogre::Ray &mouseRay,
			      const Ogre::Camera *camera)
{
	(void)camera;
	if (!m_axisX->isVisible())
		return false;

	m_selectedAxis = hitTest(mouseRay);

	if (m_selectedAxis != Axis::None) {
		m_isDragging = true;
		m_dragStartPosition = m_position;
		m_dragStartRotation = m_orientation;

		Ogre::Vector3 axisDir;
		switch (m_selectedAxis) {
		case Axis::X:
			axisDir = m_orientation * Ogre::Vector3::UNIT_X;
			break;
		case Axis::Y:
			axisDir = m_orientation * Ogre::Vector3::UNIT_Y;
			break;
		case Axis::Z:
			axisDir = m_orientation * Ogre::Vector3::UNIT_Z;
			break;
		default:
			axisDir = Ogre::Vector3::UNIT_X;
			break;
		}
		m_dragAxisDir = axisDir;

		if (m_mode == Mode::Translate) {
			projectRayOntoAxis(mouseRay, m_dragStartPosition,
					   m_dragAxisDir, m_dragStartT);
		} else if (m_mode == Mode::Rotate) {
			(void)camera;
			m_dragStartAngle = 0.0f;
		}

		createGeometry();
		return true;
	}

	return false;
}

bool Cursor3D::onMouseMoved(const Ogre::Ray &mouseRay,
			    const Ogre::Vector2 &mouseDelta)
{
	if (m_isDragging && m_selectedAxis != Axis::None) {
		if (m_mode == Mode::Translate) {
			float currentT;
			if (!projectRayOntoAxis(mouseRay, m_dragStartPosition,
						m_dragAxisDir, currentT)) {
				return true;
			}
			float deltaT = currentT - m_dragStartT;
			Ogre::Vector3 newPos =
				m_dragStartPosition + m_dragAxisDir * deltaT;
			setPosition(newPos);
			return true;
		} else if (m_mode == Mode::Rotate) {
			// Simple axis-based rotation from mouse delta
			float deltaAngle = 0.0f;
			if (m_selectedAxis == Axis::X) {
				// Up/down rotates around X
				deltaAngle = mouseDelta.y * 0.5f;
			} else if (m_selectedAxis == Axis::Y) {
				// Left/right rotates around Y
				deltaAngle = -mouseDelta.x * 0.5f;
			} else if (m_selectedAxis == Axis::Z) {
				// Left/right rotates around Z
				deltaAngle = -mouseDelta.x * 0.5f;
			}
			m_dragStartAngle += deltaAngle;

			Ogre::Quaternion rot(
				Ogre::Degree(m_dragStartAngle),
				m_dragAxisDir);
			Ogre::Quaternion newRot = rot * m_dragStartRotation;
			setOrientation(newRot);
			return true;
		}
	} else if (m_axisX->isVisible()) {
		Axis prevHover = m_hoveredAxis;
		m_hoveredAxis = hitTest(mouseRay);
		if (prevHover != m_hoveredAxis)
			createGeometry();
	}

	return false;
}

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