
#include "/lib/shadows/shadow_misc.glsl"

// Precompute any other static values if possible
float shadowFunc(
    float diffuseSun,
    float sssAmount,
    vec3 p3,
    float noise,
    vec2 lightmap,
    vec3 fragpos,
    inout vec3 sAlbedo,
    vec3 albedo,
    vec3 normals,
    inout vec3 colored,
    float entity)
{
    // Early exit if there is effectively no lightmap contribution
    if(lightmap.y < 0.001) return 0.0;

    // Precalculate common values
    vec2 absp3 = abs(p3.xz);
    float shading = 1.0;
    float sssBackScatter = 1.0;

    // Precalculate shadow matrices and projection components
    mat3 shadowMV3 = mat3(shadowModelView);
    vec3 shadowProjDiag = vec3(shadowProjection[0].x, shadowProjection[1].y, shadowProjection[2].z);
    vec3 shadowProjTrans = shadowProjection[3].xyz;
    
    // Compute mix1 using interpolated value from shadowPosWorld.y
    float mix1 = interpolate(shadowPosWorld.y).x;
#ifdef DISTANT_HORIZONS
    mix1 = 1.0;
#else
    mix1 *= 0.01 * (1.0 - sssAmount);
#endif

    // Precalculate a tentative SSS value
    float SSS = sssAmount * 0.75 * max(diffuseSun, 0.5);

    // Offset p3 to reduce shadow acne
    p3 += normalize(shadowPosWorld) * 0.05;

    // Transform p3 and normals into shadow space
    vec3 shadowMV_P3 = shadowMV3 * p3;
    vec3 shadowMV_Normals = shadowMV3 * normals;
    vec3 shadowMV_Trans = shadowModelView[3].xyz;

    // Compute and distort projected shadow position
    vec3 ProjShadowPos = shadowProjDiag * (shadowMV_P3 + shadowMV_Trans) + shadowProjTrans;
    float distortFac = calcDistort(ProjShadowPos.xy);

    // Precalculate mix2 (offset based on SSS amount and distortion)
    float mix2;
#ifdef DISTANT_HORIZONS
    mix2 = 0.0;
#else
    float pow_sss = 1.0 + 0.01 * (sssAmount - 1.0);
    mix2 = mix(distortFac * 0.01, 0.04, pow_sss);
#endif
    ProjShadowPos += shadowProjDiag * shadowMV_Normals * mix2;
    
    // Depth defaults (overridden below)
    float pdepth = 0.0;
    float avgDepth = 0.3;
    
#ifndef DISTANT_HORIZONS
    if(absp3.x < shadowDistance && absp3.y < shadowDistance)
#else
    if(true)
#endif
    {
        // Disable SSS when inside shadow volume for now
        SSS = 0.0;

        // Sample blue noise once and cache its RG components
        vec4 noise4 = blueNoise4(gl_FragCoord.xy);
        vec2 noise2 = noise4.rg;
        
        // Apply distortion and re-map to [0,1]
        ProjShadowPos.xy *= distortFac;
        ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.083333) + 0.5;

        shading = 0.0;
        avgDepth = 0.0;

        // Precalculate values that remain constant inside the loop
        float invShadowRes = 1.0 / shadowMapResolution;
        float rdMul0 = 0.2 * distortFac * invShadowRes;
        const int iterations = SHADOW_SAMPLES;
        float scaleFac = distortFac;
#ifdef DISTANT_HORIZONS
        vec3 filterDepth = vec3(3.0, 16.0, 128.0);
#else
        vec3 filterDepth = vec3(1.0, 16.0, 128.0);
#endif
        float RadiusMax = distortFac * invShadowRes;
        float blockerCount = 0.0, avgBlockerDepth0 = 0.0;
        float filterDepthRange = filterDepth.y - filterDepth.x;
        float threshMulMult2 = (eyeAltitude > 500.0) ? 8192.0 : ((eyeAltitude > 400.0) ? 4096.0 : 2048.0);
        float threshMul = max(threshMulMult2 * invShadowRes * shadowDistance * 0.01, 0.95);

        // Precompute distortion threshold values
        float oneMinusDiffuseSunSq = 1.0 - diffuseSun * diffuseSun;
        float distortThresh = clamp(sqrt(oneMinusDiffuseSunSq) / diffuseSun, 0.0, mix1) / scaleFac;
        float diffthresh = (distortThresh / 6000.0) * threshMul;
        float diffthreshHalf = 0.5 * diffthresh;
        
        float invIterations = 1.0 / float(iterations);
        int counter = (frameCounter % 40000) * iterations;
        float prtecomp = RadiusMax * 2.7 / (float(iterations) * shadowMapResolution * scaleFac);
        
        // Initialize accumulator for colored shadows (if used)
        vec4 shadowcolor = vec4(0.0);
        
        // Loop over shadow samples
        for (int i = 0; i < iterations; i++)
        {
            // Compute scattering factor based on current accumulated shading
            float shadingIter = shading * invIterations;
            float scatterFactor = clamp(diffuseSun * 16.0 * shadingIter + shadingIter, 0.0, 1.0);
            sssBackScatter = mix(mix(1.0, 16.0, sssAmount), 1.0, scatterFactor);

            // Generate jittered offsets using blue noise (reuse noise2)
            vec2 ij = fract(R2_samples3(int(counter + i)) + noise2);
            vec2 offset = tapLocationBlueNoise(i, iterations, 80, ij);
            
            // Sample the first shadow texture and accumulate depth
            float d = texture(shadowtex0, ProjShadowPos.xy + offset * RadiusMax).x;
            float zDiff = ProjShadowPos.z - d;
            avgDepth += max(zDiff, 0.0) * mix(2048.0, 512.0, sssAmount);
            
            float weightpu = 3.0 + (float(i) + noise) * prtecomp;
            float shadowWeight = smoothstep(weightpu * diffthreshHalf, weightpu * diffthresh, zDiff);
            blockerCount += shadowWeight;
            avgBlockerDepth0 += d * shadowWeight;
            
            float avgBlockerDepth = (blockerCount > 0.0) ? (avgBlockerDepth0 / blockerCount) : ProjShadowPos.z;
            float t0 = max((ProjShadowPos.z - avgBlockerDepth) / filterDepth.z, 0.0) * 1500.0;
            pdepth = (t0 * filterDepthRange + filterDepth.x) * (blockerCount > 0.9 ? 1.0 : 0.0) * rdMul0;
            
            // Offset shadow coordinate for sub-pixel SSS distortion
            vec3 shadowCoordWeighted = ProjShadowPos +
                vec3(offset * pdepth * sssBackScatter,
                     -diffthresh * (1.0 + noise * shadowMapResolution * pdepth * invIterations));
            
            // Sample second shadow texture
            float shadow0 = texture(shadowtex1, shadowCoordWeighted);
#ifdef COLORED_SHADOWS
            float shadow1 = texture(shadow, shadowCoordWeighted);
            float transparentShadow = abs(shadow1 - shadow0);
            if(transparentShadow > 0.0)
            {
                shading += clamp(shadow1 + clamp(1.0 - shadowcolor.a, 0.0, 1.0), 0.0, 1.0);
                shadowcolor += texture(shadowcolor0, shadowCoordWeighted.xy);
            }
            else
            {
                shading += shadow1;
            }
#else
            shading += shadow0;
#endif
        }
        
        avgDepth *= invIterations;
        shading   *= invIterations;
        
#ifdef COLORED_SHADOWS
        colored = (shadowcolor.rgb * invIterations) * ambientA;
#endif
#ifdef CLOUD_SHADOWS
        shading *= cloudShadow(p3, noise);
#endif

#if defined(RSM)
        if(entity < 0.5)
            sAlbedo = calculateAlbedo(ProjShadowPos, noise4, distortFac, ambientA, lightmap);
#endif

        // Precompute lightmap^4 with multiplications instead of pow()
        float lightmapY4 = lightmap.y * lightmap.y;
        lightmapY4 *= lightmapY4;
        if(sssAmount > 0.0)
            SSS = computeSSS(sssAmount, albedo, p3, lightPos, avgDepth, min(diffuseSun, shading)) * lightmapY4;
    }
    
#ifdef EXTENDED_SHADOWS
    float mixamount = smoothstep(far * 0.9, shadowDistance, max(length(p3.xz), abs(p3.y)));
    if(mixamount > 0.0)
    {
        float shading2 = rayTraceShadowAlt(diffuseSun, worldToView(lightPos), fragpos, noise, (sssAmount > 0.0));
        shading = mix(shading, shading2, mixamount);
        if(sssAmount > 0.0)
        {
            float distortFac2 = calcDistort(ProjShadowPos.xy);
            vec3 ProjShadowPos2 = ProjShadowPos;
            ProjShadowPos2.xy *= distortFac2;
            ProjShadowPos2 = ProjShadowPos2 * vec3(0.5, 0.5, 0.083333) + vec3(0.5);
            float avgDepth2 = max(ProjShadowPos2.z - shading * 0.65, 0.0);
            float lightmapFactor = lightmap.y * lightmap.y;
            lightmapFactor *= lightmapFactor;
            float SSS2 = computeSSS(sssAmount, albedo, p3, lightPos, avgDepth2, diffuseSun) * lightmapFactor;
            SSS = mix(SSS, SSS2, mixamount);
        }
    }
#endif

    if(diffuseSun * shading > 0.001)
    {
#ifdef CONTACT_SHADOWS
        if(sssAmount == 0.0)
            shading = min(rayTraceShadow(diffuseSun, worldToView(lightPos), fragpos, noise), shading);
#endif
        shading = mix(0.0, shading, min(eyeBrightnessSmooth.y * recip + lightmap.y, 1.0));
    }
    
    // Clamp and blend final shadow value
    diffuseSun = min(diffuseSun, shading);
    diffuseSun = mix(diffuseSun * (1.0 - sssAmount), SSS, sssAmount) *
                 shadowblend * lightmap.y * (1.0 - 0.9 * rainStrength);
    
    return diffuseSun;
}
