#pragma once

#if defined( __EMSCRIPTEN__ ) || defined( __ANDROID__ )
#include <GLES3/gl3.h>
#else
#include <glad/glad.h>
#endif

#include "crShadowMap.h"
#include "crStruct.h"

// shared shader uniforms — std140 UBO on binding 0, uploaded once per frame.
// single home for everything the 3D passes share; the Data mirror below must match the
// GraphicsUniforms block declared in crassets/shaders/_graphics_uniforms.glsl (spliced in via #include)
class crGraphicsUniforms
{
public:
    static constexpr GLuint BINDING_POINT = 0;

    // shader time wraps here so float32 precision holds over long sessions. a whole number of
    // seconds — any integer-Hz fixed timestep divides it exactly, so the wrap lands on a tick
    static constexpr uint64_t WRAP_SECONDS = 600;

    // runtime lighting state — edited live by crDevGraphics, uploaded every Update().
    // ambient is a hemisphere: +Y normals take the sky color, -Y the ground color
    float3 lightDir       = { 0.4f, 1.0f, 0.3f };           // toward the light; normalized on upload
    color4 lightColor     = { 1.0f, 1.0f, 1.0f, 0.65f };    // rgb = color, a = intensity
    color4 ambientSky     = { 1.0f, 1.0f, 1.0f, 0.40f };    // rgb = color, a = strength
    color4 ambientGround  = { 0.60f, 0.55f, 0.50f, 0.20f }; // rgb = color, a = strength
    float  specShininess      = 32.0f;
    float  specStrength       = 0.35f;
    float  shadowBias         = 0.0f;   // world meters — the shader converts per cascade (depth ranges differ).
                                        // anti-acne lives in the shadow pass polygon offset (slope-scaled);
                                        // raise this only as a rescue knob — it shrinks coverage uniformly
    float  shadowStrength     = 1.0f;   // 0 = shadows off
    float  shadowNormalOffset = 1.0f;   // in shadow-map texels — the shader scales by each cascade's texel world size (anti-acne backup)
    float  shadowDarkness     = 0.4f;   // how much shadow cuts the ambient too — 0 = physical, 1 = pitch black

private:
    // std140: mat4 = 64B, each vec3 starts on a 16B boundary (hence the explicit pads)
    struct Data
    {
        float4x4 viewProjection;
        float4x4 lightVP[ crShadowMap::CASCADE_COUNT ];
        float3   cameraPos;
        float    _pad0;
        float3   lightDir;
        float    _pad1;
        color4   lightColor;
        color4   ambientSky;
        color4   ambientGround;
        float    specShininess;
        float    specStrength;
        float    _pad2;
        float    _pad3;
        float    shadowBias;
        float    shadowStrength;
        float    shadowNormalOffset;
        float    shadowDarkness;
        color4   shadowCascadeParams[ crShadowMap::CASCADE_COUNT ];   // x = texel world size, y = bias (depth01), zw = texel uv size
        color4   fogColor;      // rgb linear (converted on upload), a = enabled
        float    fogStart;
        float    fogEnd;
        float    _pad4;
        float    _pad5;
        float    simTime;       // wrapped seconds — see WRAP_SECONDS
        float    realTime;
        float    _pad6;
        float    _pad7;
    };
    static_assert( sizeof( Data ) == 464, "GraphicsUniforms: std140 layout mismatch" );

    GLuint _ubo = 0;

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

    void Update( float4x4 viewProjection, float3 cameraPos, const float4x4* lightVPs, float3 fogColor, float fogStart, float fogEnd, bool fogEnabled, float simTime, float realTime );   // once per frame; lightVPs = CASCADE_COUNT entries; times pre-wrapped by the caller
    void BindTo( GLuint program );   // binds the program's GraphicsUniforms block to BINDING_POINT; no-op if the block is absent
};
