#pragma once

#include "crStruct.h"

// 3D->screen projection result (visible == false when the point is behind the camera)
struct crScreenPoint
{
    float2 pos     = {};      // pixels, origin top-left, Y-down
    bool   visible = false;
};

// screen -> world picking ray (origin at the eye, unit direction)
struct crRay
{
    float3 origin = {};
    float3 dir    = {};
};

// screen -> gameplay-plane pick result (valid == false when the ray misses the plane)
struct crPlaneHit
{
    float3 pos   = {};
    bool   valid = false;
};

// camera — the world pose is the state; the orbit rig authors it on top
// (Unity-editor style controls live in crInputSystem)
class crCamera
{
public:
    static constexpr float FOV_Y_DEFAULT = 1.0472f;     // 60 deg
    static constexpr float FOV_Y_MIN     = 0.001745f;   // 0.1 deg
    static constexpr float FOV_Y_MAX     = 3.139847f;   // 179.9 deg
    static constexpr float NEAR_DEFAULT  = 0.1f;
    static constexpr float FAR_DEFAULT   = 200.0f;

    static constexpr float DISTANCE_MIN = 2.0f;
    static constexpr float DISTANCE_MAX = 1000.0f;

private:
    static constexpr float PITCH_LIMIT = 1.45f;   // ~83 deg — keep away from the poles

    int2 _viewport = { 1280, 720 };   // placeholder only — SetViewport overwrites it on the first frame

    // view, projection and picking all derive from this. the orbit rig below cannot express
    // every orientation — its up reference degenerates at the poles, which is why PITCH_LIMIT
    // exists — so a game that needs one (straight down, banked flight) sets the pose directly
    b3WorldTransform _pose;

    // orbit rig: authoring state for the dev camera. every mutation rebuilds _pose. setting the
    // pose directly leaves the rig where it was, so grabbing the free camera snaps back to it
    float3 _target   = { 0.0f, -4.0f, 0.0f };   // container center
    float  _distance = 150.0f;
    float  _yaw      = 0.0f;                    // about world +Y; 0 = looking from +Z toward -Z
    float  _pitch    = 0.3f;                    // about the view right axis; + looks down
    float  _roll     = 0.0f;                    // about the view axis; yaw/pitch alone never produce it

    float  _fovY      = FOV_Y_DEFAULT;
    float  _nearPlane = NEAR_DEFAULT;
    float  _farPlane  = FAR_DEFAULT;

public:
    void Init();
    void Cleanup();

    bool SetViewport( int2 pixelSize );          // true = size changed (resolution-dependent resources should refresh)
    void SetDistance( float distance );          // clamped
    void SetTarget( float3 target );
    void SetOrbit( float yaw, float pitch );     // absolute angles; pitch clamped
    void SetRoll( float radians );
    void SetWorldTransform( b3WorldTransform pose );   // bypasses the rig — any orientation, no pole limit
    void SetFovY( float radians );               // clamped; authored at crScreenRef::ASPECT
    void SetClip( float nearPlane, float farPlane );   // clamped; near is kept below far

    void Orbit( float dyaw, float dpitch );
    void Look( float dyaw, float dpitch );       // rotate the view with the eye fixed (the target moves)
    void Pan( float dxPixels, float dyPixels );
    void Fly( float3 localDir, float distance ); // move along the view basis: x = right, y = up, z = forward

    void Dolly( float factor );                  // clamped

    int2   Viewport() const { return _viewport; }
    float3 Target() const { return _target; }
    float  Distance() const { return _distance; }
    float  Yaw() const { return _yaw; }
    float  Pitch() const { return _pitch; }
    float  Roll() const { return _roll; }
    float  FovY() const { return _fovY; }
    float  EffectiveFovY() const;   // what actually projects — _fovY after the screen-reference fit
    float  NearPlane() const { return _nearPlane; }
    float  FarPlane() const { return _farPlane; }

    float3 EyePosition() const;
    float3 Forward() const;
    b3WorldTransform WorldTransform() const;   // eye pose; GL convention — the view looks down local -Z

    float4x4 PixelProjection() const;   // screen pixels (bottom-left origin, Y-up) -> NDC
    float4x4 ViewProjection() const;

    crScreenPoint WorldToScreen( float3 world ) const;   // 3D point -> screen pixels (top-left, Y-down)
    crRay         ScreenToRay( float2 pixel ) const;     // mouse pixels (top-left, Y-down) -> world ray from the eye
    crPlaneHit    ScreenToPlaneZ0( float2 pixel ) const; // mouse pixels -> hit on the z = 0 gameplay plane

private:
    float3  OrbitDir() const;     // unit direction from target to eye
    float3  RigEye() const;       // eye the rig implies — not _pose.p, which the game may have overwritten
    crBasis OrbitBasis() const;   // view basis about world +Y — the inverse of the lookAt, built geometrically
    crBasis PoseBasis() const;    // the same basis read back out of _pose
    void    RebuildPoseFromRig();
};
