#ifndef MODEL_H
#define MODEL_H

#include <vector>
#include <string>
#include <iostream>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/ext/matrix_transform.hpp>
#include "stb_image.h"
#include "tiny_obj_loader.h"
#include "shader.hpp"

struct MaterialData {
    glm::vec3 diffuseColor;
    glm::vec3 specularColor;
    float shininess;
    GLuint diffuseTextureID;
    GLuint specularTextureID;
};


class Model {
public:
    // Constructor: Loads a model from the given OBJ file
    Model(const std::string& filepath);

    // Destructor: Cleans up allocated resources
    ~Model();

    // Reload a new model at runtime
    bool reloadModel(const std::string& filepath);

    // Renders the model
    void draw(GLuint programID) const;

    // Returns the model's transformation matrix
    glm::mat4 getModelMatrix() const;

    // Sets the model's position
    void setPosition(const glm::vec3& position);

    // Sets the model's rotation (in degrees)
    void setRotation(float angle, const glm::vec3& axis);

    // Sets the model's scale
    void setScale(const glm::vec3& scale);

    // Gets the texture ID for the given material index
    GLuint getTextureID(size_t materialIndex) const;

    // Gets the specular texture ID for the given material index
    GLuint getSpecularTextureID(size_t materialIndex) const;

    // Returns the current position of the model
    glm::vec3 getPosition() const;

    // Gets the diffuse color from the material if no texture is available
    glm::vec3 getMaterialDiffuseColor(size_t materialIndex) const;

    std::vector<MaterialData> getModelMaterials() const;

    GLuint getVAO() const;

    const std::vector<unsigned int>& getIndices() const;

    const std::vector<int>& getFaceMaterialIDs() const;

    // Calculates and returns the lowest point (Y-coordinate) of the model
    float getLowestPoint() const;

    // Update the lowest point when the model is transformed
    void updateLowestPoint() const;

    // Getter for checking if the lowest point needs to be updated
    bool isLowestPointUpdateNeeded() const;

    // Get current model file path and info
    const std::string& getCurrentFilePath() const;
    size_t getVertexCount() const;
    size_t getFaceCount() const;

private:
    // Cleans up all OpenGL resources (called by destructor and reloadModel)
    void cleanup();

    // Loads the model from an OBJ file
    void loadModel(const std::string& filepath);

    // Loads textures associated with the model
    void loadTextures();

    // Sets up OpenGL buffers (VAO, VBO, NBO, TBO, EBO)
    void setupBuffers();

    // Calculates the model transformation matrix based on the position, rotation, and scale
    glm::mat4 calculateModelMatrix() const;

    // Calculates the bounding box of the model
    void calculateBoundingBox(glm::vec3& min, glm::vec3& max) const;

    // The model's vertices, normals, texture coordinates, and indices
    std::vector<float> vertices;
    std::vector<float> normals;
    std::vector<float> texcoords;
    std::vector<unsigned int> indices;

    // Material information
    std::vector<tinyobj::material_t> materials;
    std::vector<int> face_material_ids; // Stores material ID for each face

    // OpenGL handles for the model's buffers
    GLuint vao;
    GLuint vbo;
    GLuint nbo;
    GLuint tbo;
    GLuint ebo;

    // OpenGL handles for textures
    std::vector<GLuint> textures;
    std::vector<GLuint> specularTextures;

    // Material diffuse colors for each material
    std::vector<glm::vec3> diffuseColors;  // Store diffuse color for each material

    // Cache the lowest point to avoid recalculating each frame
    mutable float lowestPoint;

    // Flag to check if we need to recalculate the lowest point
    mutable bool needsLowestPointUpdate;

    // Model transformation attributes
    glm::vec3 position;
    glm::vec3 rotationAxis;
    float rotationAngle;
    glm::vec3 scale;

    // Current model file path
    std::string currentFilePath;
};

#endif // MODEL_H
