#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// Modern camera class using C++23 features
class Camera {
public:
    enum class ProjectionType {
        Perspective,
        Orthographic
    };

    // Constructor with designated initializers-like approach
    explicit Camera(ProjectionType type = ProjectionType::Perspective,
                   const glm::vec3& position = glm::vec3(0.0f, 0.0f, 3.0f),
                   const glm::vec3& target = glm::vec3(0.0f, 0.0f, 0.0f),
                   const glm::vec3& up = glm::vec3(0.0f, 1.0f, 0.0f));

    ~Camera() = default;

    // Delete copy constructor and assignment
    Camera(const Camera&) = delete;
    Camera& operator=(const Camera&) = delete;

    // Move semantics
    Camera(Camera&&) noexcept = default;
    Camera& operator=(Camera&&) noexcept = default;

    // View matrix management
    void SetPosition(const glm::vec3& position);
    void SetTarget(const glm::vec3& target);
    void SetUp(const glm::vec3& up);
    void LookAt(const glm::vec3& position, const glm::vec3& target, const glm::vec3& up = glm::vec3(0.0f, 1.0f, 0.0f));

    // Projection management
    void SetPerspective(float fov, float aspect_ratio, float near_plane, float far_plane);
    void SetOrthographic(float left, float right, float bottom, float top, float near_plane, float far_plane);
    void SetAspectRatio(float aspect_ratio);

    // Movement methods
    void Move(const glm::vec3& offset);
    void MoveForward(float distance);
    void MoveBackward(float distance);
    void MoveLeft(float distance);
    void MoveRight(float distance);
    void MoveUp(float distance);
    void MoveDown(float distance);

    // Rotation methods (first-person camera style)
    void Rotate(float yaw, float pitch);
    void SetYaw(float yaw);
    void SetPitch(float pitch);

    // Modern getters using [[nodiscard]]
    [[nodiscard]] const glm::mat4& GetViewMatrix() const;
    [[nodiscard]] const glm::mat4& GetProjectionMatrix() const;
    [[nodiscard]] glm::mat4 GetViewProjectionMatrix() const;

    [[nodiscard]] const glm::vec3& GetPosition() const { return position_; }
    [[nodiscard]] const glm::vec3& GetTarget() const { return target_; }
    [[nodiscard]] const glm::vec3& GetUp() const { return up_; }
    [[nodiscard]] glm::vec3 GetForward() const;
    [[nodiscard]] glm::vec3 GetRight() const;

    [[nodiscard]] float GetFOV() const { return fov_; }
    [[nodiscard]] float GetAspectRatio() const { return aspect_ratio_; }
    [[nodiscard]] float GetNearPlane() const { return near_plane_; }
    [[nodiscard]] float GetFarPlane() const { return far_plane_; }

    [[nodiscard]] float GetYaw() const { return yaw_; }
    [[nodiscard]] float GetPitch() const { return pitch_; }

    [[nodiscard]] ProjectionType GetProjectionType() const { return projection_type_; }

    // Utility functions
    [[nodiscard]] glm::vec3 ScreenToWorld(const glm::vec2& screen_pos, const glm::vec2& screen_size, float depth = 1.0f) const;
    [[nodiscard]] glm::vec2 WorldToScreen(const glm::vec3& world_pos, const glm::vec2& screen_size) const;

    // Debug functions
    void PrintInfo() const;

private:
    ProjectionType projection_type_;
    
    // View parameters
    glm::vec3 position_;
    glm::vec3 target_;
    glm::vec3 up_;
    
    // Euler angles for first-person camera
    float yaw_ = -90.0f;   // Initialized to look along negative Z-axis
    float pitch_ = 0.0f;
    
    // Projection parameters
    float fov_ = 45.0f;
    float aspect_ratio_ = 16.0f / 9.0f;
    float near_plane_ = 0.1f;
    float far_plane_ = 100.0f;
    
    // Orthographic parameters
    float ortho_left_ = -10.0f;
    float ortho_right_ = 10.0f;
    float ortho_bottom_ = -10.0f;
    float ortho_top_ = 10.0f;

    // Cached matrices (mutable for lazy evaluation)
    mutable glm::mat4 view_matrix_{1.0f};
    mutable glm::mat4 projection_matrix_{1.0f};
    mutable bool view_dirty_ = true;
    mutable bool projection_dirty_ = true;

    void UpdateViewMatrix() const;
    void UpdateProjectionMatrix() const;
    void UpdateFromEulerAngles();

    // Constrain pitch to avoid gimbal lock
    static constexpr float MAX_PITCH = 89.0f;
};