#pragma once

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

#include "crStruct.h"

class crCamera;

// directional-light cascaded shadow map (CSM) — depth-only passes rendered once per frame each.
// pure frustum fitting: every cascade covers a view-depth slice of the camera frustum through a
// rotation-stable bounding sphere (texel-snapped). shadows exist only out to maxDistance —
// there is no world-bound clamping, so the quality knobs are the split distances, maxDistance, and resolution.
class crShadowMap
{
public:
    static constexpr int32_t CASCADE_COUNT   = 3;   // the GraphicsUniforms u_lightVP[] size follows this
    static constexpr int32_t SHADOW_MAP_SIZE = 2048;

    // dev-tweakable (crDevGraphics)
    float maxDistance                         = 64.0f;          // shadow distance, view-space meters — past it shadows cut off (fade: future work)
    float splitDistances[ CASCADE_COUNT - 1 ] = { 4.0f, 16.0f };// cascade boundaries, view-space meters

private:
    static constexpr float CASTER_MARGIN = 30.0f;   // extra light-space depth toward the light — off-screen casters still shadow visible receivers
    static constexpr float Z_NEAR        = 1.0f;

    GLuint _fbo          = 0;
    GLuint _depthTexture = 0;   // TEXTURE_2D_ARRAY, one layer per cascade

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

    float4x4 LightVP( float3 lightDir, const crCamera* camera, int32_t cascade ) const;   // lightDir: toward the light, any length
    void     BeginPass( int32_t cascade );   // attaches the cascade layer + viewport + clear; the caller rebinds the scene target after

    GLuint DepthTexture() const;             // compare-mode array texture — sample with sampler2DArrayShadow

private:
    float SplitDistance( int32_t boundary, float nearPlane ) const;   // view-space distance of cascade boundary 0..CASCADE_COUNT
};
