#pragma once
#include <glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
#include <memory>
#include <functional>
#include "ThreadPool.h"
#include <glm/gtc/matrix_access.hpp>


// Forward declarations
class Primitive;
class GameObject;


class Engine {
public:
    Engine(int window_width = 1280, int window_height = 720, const char* title = "C++ Engine");
    virtual ~Engine();


    virtual bool OnInitialize();
    virtual void OnShutdown();
    virtual void OnUpdate(float delta_time);
    virtual void OnRender();


    void Run();


    // Primitive factory methods
    std::shared_ptr<Primitive> CreateCube(const glm::vec3& color = glm::vec3(1.0f));
    std::shared_ptr<Primitive> CreateSphere(int segments = 32, const glm::vec3& color = glm::vec3(1.0f));
    std::shared_ptr<Primitive> CreatePlane(float width = 2.0f, float height = 2.0f, const glm::vec3& color = glm::vec3(1.0f));
    std::shared_ptr<Primitive> CreateGrid(int size = 20, float spacing = 1.0f, const glm::vec3& color = glm::vec3(0.6f));
    std::shared_ptr<Primitive> CreateOBJModel(const std::string& objPath, const std::string& texturePath = "");


    // GameObject management
    std::shared_ptr<GameObject> CreateGameObject(std::shared_ptr<Primitive> primitive, const glm::vec3& position = glm::vec3(0.0f));
    void AddGameObject(std::shared_ptr<GameObject> obj);
    void RemoveGameObject(std::shared_ptr<GameObject> obj);


    // Camera control
    void SetCameraPosition(const glm::vec3& position);
    void SetCameraTarget(const glm::vec3& target);
    void SetCameraOrbit(float distance, float angle, float height = 5.0f);

    GLuint text_vao = 0, text_vbo = 0;


    GLFWwindow* GetWindow() const { return window_; }
    float GetTime() const { return (float)glfwGetTime(); }


   
private:
    ThreadPool threadPool_;
    
    // Culling result structure
    struct CullingBatch {
        Primitive* primitive;
        std::vector<glm::mat4> modelMatrices;
    };
    
    using CullingResult = std::vector<CullingBatch>;
    
    // Helper methods
    CullingResult CullObjectsSubset(
        const std::vector<GameObject*>& objects,
        size_t startIdx, 
        size_t endIdx,
        const glm::mat4& viewProjection
    );
    
    void MergeCullingResults(
        const std::vector<CullingResult>& results,
        std::unordered_map<Primitive*, std::vector<glm::mat4>>& outBatches
    );

    static bool CullSphere(const glm::mat4& vp,
                           const glm::vec3& center,
                           float radius);



   
protected:
    void InitializeShader();
    void RenderScene();
    void RenderSceneInstanced();
    void RenderOverlay();
    void RenderOverlayTestPoint();
    void InitOverlay();                    

    static void FramebufferSizeCallback(GLFWwindow* window, int width, int height);

    int width_, height_;
    GLFWwindow* window_;
    float last_time_ = 0.0f;
    
    // Camera
    glm::vec3 camera_position_ = glm::vec3(0, 5, 10);
    glm::vec3 camera_target_   = glm::vec3(0, 0, 0);
    glm::vec3 camera_up_       = glm::vec3(0, 1, 0);
    
    // Rendering

   
    GLuint shader_program_ = 0;
    std::vector<std::shared_ptr<GameObject>> game_objects_;

    // Overlay VAO/VBO
    GLuint overlay_vao = 0;                 
    GLuint overlay_vbo = 0;                  
    GLuint overlay_program = 0;
    
    // Performance stats
    static constexpr int PERF_HISTORY = 300;
    float perf_times[PERF_HISTORY] = {0};
    int perf_pos = 0;
    int perf_count = 0;

    // Projection matrix for overlay
    glm::mat4 ortho2D;

    // Font buffer for stb_easy_font
    static const int FONT_BUFFER_SIZE = 9999;
    char font_buffer[FONT_BUFFER_SIZE];



};


// Primitive base class
class Primitive {
public:
    virtual ~Primitive() = default;
    virtual void Render() = 0;
    virtual void Bind() = 0; 
    virtual void Cleanup() = 0;

    virtual GLenum GetDrawMode() const { return GL_TRIANGLES; }
    virtual GLsizei GetIndexCount() const { return index_count_; }
    virtual GLsizei GetVertexCount() const { return vertex_count_; }
    virtual void Bind() const { glBindVertexArray(vao_); }


protected:
    GLuint vao_ = 0;
    GLuint vbo_ = 0;
    GLuint ebo_ = 0;
    GLsizei index_count_ = 0;
    GLsizei vertex_count_ = 0;
    GLenum draw_mode_ = GL_TRIANGLES;
    glm::vec3 color_;
};

// GameObject class
class GameObject {
public:
    GameObject(std::shared_ptr<Primitive> prim, const glm::vec3& pos);
    // Add:
    Primitive* GetPrimitive() const { return primitive_.get(); }
    float GetBoundingRadius() const;

    glm::vec3 GetPosition() const { return position_; }
    glm::mat4 GetModelMatrix() const;
    void Render();
    void Cleanup();

    void SetPosition(const glm::vec3& p) { position_ = p; }
    void SetRotation(const glm::vec3& r) { rotation_ = r; }
    void SetScale(const glm::vec3& s) { scale_ = s; }

private:
    std::shared_ptr<Primitive> primitive_;
    glm::vec3 position_;
    glm::vec3 rotation_;
    glm::vec3 scale_{1.0f};
};
