#pragma once

#include <pxr/base/gf/camera.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/bbox3d.h>
#include <pxr/base/gf/range3d.h>
#include <pxr/base/gf/ray.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usd/stage.h>
#include <utility>

namespace UsdLayerManager {

/// Free camera ported from Pixar's usdview FreeCamera (freeCamera.py).
/// Supports Tumble/Truck/AdjustDistance with Z-up stage handling,
/// automatic near/far clipping plane computation from scene bbox,
/// and USD prim-camera passthrough mode.
class ViewportCamera {
public:
    enum class CameraMode { Free, UsdCamera };

    static constexpr double kDefaultNear        = 1.0;
    static constexpr double kDefaultFar         = 2000000.0;
    static constexpr double kMaxSafeZResolution = 1e6;
    static constexpr double kMaxGoodZResolution = 5e4;

    ViewportCamera();

    // -----------------------------------------------------------------------
    // Stage (sets Z-up flag and YZUp matrices)
    // -----------------------------------------------------------------------
    void SetStage(pxr::UsdStageRefPtr stage);

    // -----------------------------------------------------------------------
    // Free Camera Operations (mirrors FreeCamera.py)
    // -----------------------------------------------------------------------

    /// Orbit (tumble) around center by dTheta (horizontal) and dPhi (vertical) degrees.
    void Tumble(double dTheta, double dPhi);

    /// Scale distance from center by scaleFactor.  Prevents getting stuck near zero.
    void AdjustDistance(double scaleFactor);

    /// Pan (truck) in camera-local right/up directions, world-unit deltas.
    void Truck(double deltaRight, double deltaUp);

    /// Returns pixels-to-world factor for correct Truck() scaling.
    double ComputePixelsToWorldFactor(double viewportHeight);

    /// Frame a bounding box.  frameFit=1.1 gives ~10% margin (usdview default).
    void FrameSelection(const pxr::GfBBox3d& selBBox, double frameFit = 1.1);

    // -----------------------------------------------------------------------
    // Camera Resolution: returns GfCamera with updated transform + clipping
    // -----------------------------------------------------------------------

    /// Returns the GfCamera with up-to-date transform.
    /// If autoClip=true, near/far are computed from stageBBox for best precision.
    pxr::GfCamera ComputeGfCamera(const pxr::GfBBox3d& stageBBox,
                                  bool autoClip = false);

    // -----------------------------------------------------------------------
    // Convenience Matrix Accessors
    // -----------------------------------------------------------------------
    pxr::GfMatrix4d GetViewMatrix();
    pxr::GfMatrix4d GetProjectionMatrix();

    // -----------------------------------------------------------------------
    // Camera Mode
    // -----------------------------------------------------------------------
    CameraMode          GetMode()          const { return m_mode; }
    const pxr::SdfPath& GetUsdCameraPath() const { return m_usdCameraPath; }

    /// Switch to USD prim camera mode (renderer will call SetCameraPath).
    void SetUsdCamera(const pxr::SdfPath& cameraPath);

    /// Switch back to free camera, optionally initializing state from lastGfCamera.
    void SwitchToFreeCamera(const pxr::GfCamera* lastGfCamera = nullptr);

    // -----------------------------------------------------------------------
    // Camera Settings
    // -----------------------------------------------------------------------
    double GetFOV()         const;  ///< Vertical FOV in degrees
    void   SetFOV(double fov);
    double GetAspectRatio() const;
    void   SetAspectRatio(double aspect);

    /// Hint for autoClip: the closest visible geometry point from pick result.
    void SetClosestVisibleDistFromPoint(const pxr::GfVec3d& point);

    // -----------------------------------------------------------------------
    // State Queries
    // -----------------------------------------------------------------------
    double GetDist() const { return m_dist; }
    bool   IsZUp()   const { return m_isZUp; }

    // -----------------------------------------------------------------------
    // Compatibility accessors (for tests and legacy callers)
    // -----------------------------------------------------------------------

    /// Returns camera position in world space.
    pxr::GfVec3d GetEye();

    /// Returns the look-at focal point (center of orbit).
    const pxr::GfVec3d& GetFocalPoint() const { return m_center; }

    /// Set the look-at focal point directly.
    /// Used by orthographic view panning to move the center in correct
    /// view-space directions without going through the perspective Truck() path.
    void SetFocalPoint(const pxr::GfVec3d& center) {
        m_center = center;
        m_cameraTransformDirty = true;
    }

    /// Set the orbit distance directly.
    /// Used when initialising from a USD camera prim to override the camera's
    /// authored focus distance (which is a DOF attribute, not an orbit radius).
    void SetDist(double d) {
        m_dist = d;
        m_cameraTransformDirty = true;
    }

    /// Set orbital state from eye position and orbit center without Euler decomposition.
    /// Computes theta/phi directly from the eye→center vector in the Y-up orbital frame;
    /// zeroes roll.  Use this after SwitchToFreeCamera() when initialising from a USD
    /// camera prim to avoid gimbal-lock artifacts from the Decompose() path.
    void InitOrbitFromEyeAndCenter(const pxr::GfVec3d& eye,
                                   const pxr::GfVec3d& center,
                                   double dist);

    /// Returns the current near-clip distance.
    double GetNearClip() const {
        return static_cast<double>(m_camera.GetClippingRange().GetMin());
    }

    /// Returns the current far-clip distance.
    double GetFarClip() const {
        return static_cast<double>(m_camera.GetClippingRange().GetMax());
    }

    /// Legacy: frame a GfRange3d (wraps it in a unit-matrix GfBBox3d).
    void FrameBoundingBox(const pxr::GfRange3d& range, double frameFit = 1.1) {
        FrameSelection(pxr::GfBBox3d(range), frameFit);
    }

    /// Legacy: orbit alias (matches old Orbit(deltaYaw, deltaPitch) signature).
    void Orbit(double deltaYaw, double deltaPitch) {
        Tumble(deltaYaw, deltaPitch);
    }

private:
    void PushToCameraTransform();
    void PullFromCameraTransform();
    void SetClippingPlanes(const pxr::GfBBox3d& stageBBox);
    void ResetClippingPlanes();
    std::pair<double,double> RangeOfBoxAlongRay(
        const pxr::GfRay& camRay, const pxr::GfBBox3d& bbox) const;

    // Core camera object (owns aperture/projection/clipping/transform state)
    pxr::GfCamera m_camera;
    bool          m_cameraTransformDirty;

    // Orbital / tumble state
    double       m_rotTheta;  // horizontal orbit (degrees, around Y)
    double       m_rotPhi;    // vertical orbit   (degrees, around X)
    double       m_rotPsi;    // roll              (degrees, usually 0)
    pxr::GfVec3d m_center;    // look-at center in world space
    double       m_dist;      // distance camera → center
    double       m_selSize;   // extent of last framed selection

    // Stage up-axis handling
    bool            m_isZUp;
    pxr::GfMatrix4d m_YZUpMatrix;    // Y-up → Z-up  (rotate -90° around X)
    pxr::GfMatrix4d m_YZUpInvMatrix; // Z-up → Y-up  (inverse)

    // Auto-clip state
    bool   m_hasClosestVisibleDist;
    double m_closestVisibleDist;
    double m_lastFramedDist;
    double m_lastFramedClosestDist;
    double m_overrideNear;  // ≤0 means "no override"
    double m_overrideFar;   // ≤0 means "no override"

    // Camera mode
    CameraMode          m_mode;
    pxr::SdfPath        m_usdCameraPath;
    pxr::UsdStageRefPtr m_stage;
};

} // namespace UsdLayerManager
