#include "EditorCamera.hpp"
#include <OgreRay.h>
#include <OgrePlane.h>
#include <OgreMath.h>
#include <algorithm>
#include <cmath>

EditorCamera::EditorCamera(Ogre::SceneManager *sceneMgr,
               Ogre::RenderWindow *window)
    : mSceneMgr(sceneMgr)
    , mWindow(window)
    , mYaw(0.0f)
    , mPitch(Ogre::Math::DegreesToRadians(
          45.0f)) // Convert 45 degrees to radians
    , mDistance(15.0f)
    , mTargetPosition(0, 0, 0)
    , mCursorPosition(0, 0, 0)
    , mActive(true)
    , mMiddleMouseDown(false)
    , mRightMouseDown(false)
    , mLastMouseX(0)
    , mLastMouseY(0)
    , mMoveSpeed(10.0f)
    , mRotateSpeed(0.005f)
    , mZoomSpeed(0.5f)
{
    // Create camera
    mCamera = mSceneMgr->createCamera("EditorCamera");
    mCamera->setNearClipDistance(0.1f);
    mCamera->setFarClipDistance(1000.0f);
    mCamera->setAutoAspectRatio(true);

    // Create camera node and attach camera
    mCameraNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
    mCameraNode->attachObject(mCamera);

    // Create target node for looking at
    mTargetNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
    mTargetNode->setPosition(mTargetPosition);

    // Set viewport
    Ogre::Viewport *vp = mWindow->addViewport(mCamera);
    vp->setBackgroundColour(Ogre::ColourValue(0.1f, 0.1f, 0.1f));
    mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) /
                Ogre::Real(vp->getActualHeight()));

    // Initialize camera position
    updateCameraTransform();
}

EditorCamera::~EditorCamera()
{
    if (mSceneMgr) {
        if (mCamera) {
            mCameraNode->detachObject(mCamera);
            mSceneMgr->destroyCamera(mCamera);
        }
        if (mCameraNode) {
            mSceneMgr->destroySceneNode(mCameraNode);
        }
        if (mTargetNode) {
            mSceneMgr->destroySceneNode(mTargetNode);
        }
    }
}

void EditorCamera::update(float deltaTime)
{
    if (!mActive)
        return;
    // Camera transform is updated in real-time through input handlers
}

void EditorCamera::handleMouseMove(const OgreBites::MouseMotionEvent &evt)
{
    if (!mActive)
        return;

    int deltaX = evt.x - mLastMouseX;
    int deltaY = evt.y - mLastMouseY;

    if (mMiddleMouseDown) {
        // Pan - move target position
        Ogre::Vector3 right =
            mCameraNode->getOrientation() * Ogre::Vector3::UNIT_X;
        Ogre::Vector3 up =
            mCameraNode->getOrientation() * Ogre::Vector3::UNIT_Y;

        float moveScale = mMoveSpeed * 0.01f;
        Ogre::Vector3 panDelta = (right * static_cast<float>(-deltaX) +
                      up * static_cast<float>(deltaY)) *
                     moveScale;

        mTargetPosition += panDelta;
        mTargetNode->setPosition(mTargetPosition);
        updateCameraTransform();
    } else if (mRightMouseDown) {
        // Orbit - rotate around target
        // Convert delta to radians (delta is in pixels, mRotateSpeed is in radians per pixel)
        orbit(static_cast<float>(deltaX) * mRotateSpeed,
              static_cast<float>(deltaY) * mRotateSpeed);
    }

    mLastMouseX = evt.x;
    mLastMouseY = evt.y;
}

void EditorCamera::handleMousePress(const OgreBites::MouseButtonEvent &evt)
{
    if (evt.button == OgreBites::BUTTON_MIDDLE) {
        mMiddleMouseDown = true;
    } else if (evt.button == OgreBites::BUTTON_RIGHT) {
        mRightMouseDown = true;
    }

    mLastMouseX = evt.x;
    mLastMouseY = evt.y;
}

void EditorCamera::handleMouseRelease(const OgreBites::MouseButtonEvent &evt)
{
    if (evt.button == OgreBites::BUTTON_MIDDLE) {
        mMiddleMouseDown = false;
    } else if (evt.button == OgreBites::BUTTON_RIGHT) {
        mRightMouseDown = false;
    }
}

void EditorCamera::handleKeyboard(const OgreBites::KeyboardEvent &evt)
{
    if (!mActive)
        return;

    float moveDelta = mMoveSpeed * 0.016f; // Assume 60fps

    Ogre::Vector3 forward =
        mCameraNode->getOrientation() * Ogre::Vector3::NEGATIVE_UNIT_Z;
    Ogre::Vector3 right =
        mCameraNode->getOrientation() * Ogre::Vector3::UNIT_X;
    forward.y = 0; // Keep movement horizontal
    right.y = 0;
    forward.normalise();
    right.normalise();

    Ogre::Vector3 moveDeltaVec = Ogre::Vector3::ZERO;

    // Use character constants instead of SDLK_ constants
    if (evt.keysym.sym == 'w') {
        moveDeltaVec += forward * moveDelta;
    } else if (evt.keysym.sym == 's') {
        moveDeltaVec -= forward * moveDelta;
    } else if (evt.keysym.sym == 'a') {
        moveDeltaVec -= right * moveDelta;
    } else if (evt.keysym.sym == 'd') {
        moveDeltaVec += right * moveDelta;
    } else if (evt.keysym.sym == 'q') {
        moveDeltaVec.y += moveDelta;
    } else if (evt.keysym.sym == 'e') {
        moveDeltaVec.y -= moveDelta;
    }

    if (moveDeltaVec != Ogre::Vector3::ZERO) {
        mTargetPosition += moveDeltaVec;
        mTargetNode->setPosition(mTargetPosition);
        updateCameraTransform();
    }
}

void EditorCamera::setPosition(const Ogre::Vector3 &pos)
{
    // Calculate distance and angles from target
    Ogre::Vector3 offset = pos - mTargetPosition;
    mDistance = offset.length();

    // Calculate yaw and pitch from the offset
    mYaw = std::atan2(offset.z, offset.x);
    mPitch = std::asin(offset.y / mDistance);

    updateCameraTransform();
}

void EditorCamera::setTarget(const Ogre::Vector3 &target)
{
    mTargetPosition = target;
    mTargetNode->setPosition(mTargetPosition);
    updateCameraTransform();
}

void EditorCamera::orbit(float deltaYaw, float deltaPitch)
{
    mYaw += deltaYaw;
    mPitch += deltaPitch;

    // Clamp pitch to avoid gimbal lock (-89 to 89 degrees)
    // Convert degree limits to radians using Ogre::Math::DegreesToRadians
    float maxPitch = Ogre::Math::DegreesToRadians(89.0f);
    float minPitch = Ogre::Math::DegreesToRadians(-89.0f);
    mPitch = std::clamp(mPitch, minPitch, maxPitch);

    updateCameraTransform();
}

void EditorCamera::pan(const Ogre::Vector3 &delta)
{
    mTargetPosition += delta;
    mTargetNode->setPosition(mTargetPosition);
    updateCameraTransform();
}

void EditorCamera::zoom(float delta)
{
    mDistance -= delta;
    mDistance = std::clamp(mDistance, 1.0f, 500.0f);
    updateCameraTransform();
}

void EditorCamera::updateCameraTransform()
{
    // Calculate camera position based on spherical coordinates
    float cosPitch = std::cos(mPitch);
    float sinPitch = std::sin(mPitch);
    float cosYaw = std::cos(mYaw);
    float sinYaw = std::sin(mYaw);

    Ogre::Vector3 offset;
    offset.x = mDistance * cosPitch * cosYaw;
    offset.y = mDistance * sinPitch;
    offset.z = mDistance * cosPitch * sinYaw;

    // Set camera node position
    Ogre::Vector3 cameraPosition = mTargetPosition + offset;
    mCameraNode->setPosition(cameraPosition);

    // Make camera look at target
    mCameraNode->lookAt(mTargetPosition, Ogre::Node::TS_WORLD);
}

Ogre::Vector3 EditorCamera::getMouseRay(float screenX, float screenY)
{
    if (!mCamera)
        return Ogre::Vector3::ZERO;

    // Create ray from camera through mouse position
    Ogre::Ray ray = mCamera->getCameraToViewportRay(screenX, screenY);

    // Create a plane at Y=0 for ground intersection
    Ogre::Plane groundPlane(Ogre::Vector3::UNIT_Y, 0);

    // Calculate intersection with ground plane
    std::pair<bool, Ogre::Real> intersection = ray.intersects(groundPlane);

    if (intersection.first) {
        // Return the intersection point
        return ray.getPoint(intersection.second);
    }

    // If no intersection, return a point at a default distance along the ray
    return ray.getPoint(100.0f);
}
