#pragma once
#include <string>
#include <unordered_map>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <iostream>
#include <optional>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "glad.h"

// Modern shader class using C++23 features
class Shader {
public:
    // Constructor with path validation using std::filesystem (C++17)
    explicit Shader(const std::filesystem::path& vertex_path, 
                   const std::filesystem::path& fragment_path);
    
    // Constructor overload for geometry shaders
    Shader(const std::filesystem::path& vertex_path,
           const std::filesystem::path& geometry_path, 
           const std::filesystem::path& fragment_path);

    ~Shader();

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

    // Move semantics
    Shader(Shader&& other) noexcept;
    Shader& operator=(Shader&& other) noexcept;

    void Bind() const;
    void Unbind() const;

    [[nodiscard]] bool IsValid() const { return program_id_ != 0; }

    // Modern uniform setters using function templates and concepts (C++20)
    template<typename T>
    requires std::same_as<T, int>
    void SetUniform(const std::string& name, T value);

    template<typename T>
    requires std::same_as<T, float>
    void SetUniform(const std::string& name, T value);

    template<typename T>
    requires std::same_as<T, bool>
    void SetUniform(const std::string& name, T value);

    void SetUniform(const std::string& name, const glm::vec2& value);
    void SetUniform(const std::string& name, const glm::vec3& value);
    void SetUniform(const std::string& name, const glm::vec4& value);
    void SetUniform(const std::string& name, const glm::mat3& value);
    void SetUniform(const std::string& name, const glm::mat4& value);

    // Array uniforms
    void SetUniformArray(const std::string& name, const float* values, int count);
    void SetUniformArray(const std::string& name, const glm::vec3* values, int count);

    // Utility functions using C++20 features
    [[nodiscard]] int GetUniformLocation(const std::string& name);
    [[nodiscard]] static std::optional<std::string> ReadFile(const std::filesystem::path& path);
    
    // Debug information
    void PrintActiveUniforms() const;
    void PrintActiveAttributes() const;

private:
    GLuint program_id_ = 0;
    mutable std::unordered_map<std::string, GLint> uniform_location_cache_;

    // Compilation and linking
    [[nodiscard]] static GLuint CompileShader(GLenum type, const std::string& source);
    [[nodiscard]] static GLuint CreateProgram(const std::vector<GLuint>& shaders);
    
    // Error handling using std::expected-like pattern (manual implementation for pre-C++23)
    struct CompileResult {
        bool success;
        std::string error_message;
        GLuint shader_id = 0;
    };
    
    [[nodiscard]] static CompileResult TryCompileShader(GLenum type, const std::string& source, const std::string& shader_name);
    static void CheckCompileErrors(GLuint shader, const std::string& type);
    static void CheckLinkErrors(GLuint program);
    
    void Cleanup();
};

// Template implementations
template<typename T>
requires std::same_as<T, int>
void Shader::SetUniform(const std::string& name, T value) {
    glUniform1i(GetUniformLocation(name), value);
}

template<typename T>
requires std::same_as<T, float>
void Shader::SetUniform(const std::string& name, T value) {
    glUniform1f(GetUniformLocation(name), value);
}

template<typename T>
requires std::same_as<T, bool>
void Shader::SetUniform(const std::string& name, T value) {
    glUniform1i(GetUniformLocation(name), static_cast<int>(value));
}