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

float computeMix1(float sssAmount)
{
    float mix1 = interpolate(shadowPosWorld.y).x;
#ifdef DISTANT_HORIZONS
    return 1.0;
#else
    return mix1 * 0.01 * (1.0 - sssAmount);
#endif
}

// Compute mix2 based on distortion and SSS
float computeMix2(float distortFac, float sssAmount)
{
#ifdef DISTANT_HORIZONS
    return 0.0;
#else
    float pow_sss = 1.0 + 0.01 * (sssAmount - 1.0);
    return mix(distortFac * 0.01, 0.04, pow_sss);
#endif
}

// Transform the position and normals into shadow space and compute the projected shadow position.
vec3 computeProjectedShadowPos(vec3 p3, vec3 normals, mat3 shadowMV3, vec3 shadowProjDiag, vec3 shadowProjTrans, float mix2)
{
    vec3 shadowMV_P3 = shadowMV3 * p3;
    vec3 shadowMV_Normals = shadowMV3 * normals;
    vec3 shadowMV_Trans = shadowModelView[3].xyz;
    vec3 ProjShadowPos = shadowProjDiag * (shadowMV_P3 + shadowMV_Trans) + shadowProjTrans;
    ProjShadowPos += shadowProjDiag * shadowMV_Normals * mix2;
    return ProjShadowPos;
}

// Perform the main shadow sampling loop.
// Outputs: avgDepth and pdepth are updated; returns the accumulated shading.
float sampleShadowLoop(inout vec3 colored, inout float avgDepth, inout float pdepth,
                       vec3 ProjShadowPos, float distortFac, float sssAmount, float diffuseSun, float noise, vec3 p3)
{
    float shading = 0.0;
    avgDepth = 0.0;

    const float invShadowRes = 1.0 / shadowMapResolution;
    const int iterations = SHADOW_SAMPLES;
    const float invIterations = 1.0 / float(iterations);
#ifdef DISTANT_HORIZONS
    const vec3 filterDepth = vec3(3.0, 16.0, 128.0);
#else
    const vec3 filterDepth = vec3(1.0, 16.0, 128.0);
#endif

    float rdMul0 = 0.2 * distortFac * invShadowRes;

    float RadiusMax = distortFac * invShadowRes;
    float blockerCount = 0.0, avgBlockerDepth0 = 0.0;
    float filterDepthRange = filterDepth.y - filterDepth.x;
    
    vec2 noise2 = blueNoise4(gl_FragCoord.xy).rg;


    float t = (eyeAltitude - 300.0) * 0.003333;
    t = clamp(t, 0.0, 1.0);
    float threshMulMult2 = 2048.0 + 6144.0 * t;

    float threshMul = max(threshMulMult2 * invShadowRes * shadowDistance * 0.01, 0.95);

    float oneMinusDiffuseSunSq = 1.0 - diffuseSun * diffuseSun;
    float mix1 = computeMix1(sssAmount);
    float distortThresh = clamp(sqrt(oneMinusDiffuseSunSq) / diffuseSun, 0.0, mix1) / distortFac;
    float diffthresh = (distortThresh / 6000.0) * threshMul;
    float diffthreshHalf = 0.5 * diffthresh;

    int counter = (frameCounter % 40000) * iterations;
    float prtecomp = RadiusMax * 2.7 / (float(iterations) * shadowMapResolution * distortFac);

#ifdef COLORED_SHADOWS
    vec4 shadowcolor = vec4(0.0);
#endif
    float sssIntermediate = mix(1.0, 16.0, sssAmount);

    for (int i = 0; i < iterations; i++)
    {
        float shadingIter = shading * invIterations;
        vec2 ij = fract(R2_samples3(int(counter + i)) + noise2);
        vec2 offset = tapLocationBlueNoise(i, iterations, 80, ij);

        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;

        float scatterFactor = clamp(diffuseSun * 16.0 * shadingIter + shadingIter, 0.0, 1.0);
        float sssBackScatter = mix(sssIntermediate, 1.0, scatterFactor);

        vec3 shadowCoordWeighted = ProjShadowPos +
                                   vec3(offset * pdepth * sssBackScatter,
                                        -diffthresh * (1.0 + noise * shadowMapResolution * pdepth * invIterations));

#ifdef COLORED_SHADOWS
        float shadow0 = texture(shadowtex1, shadowCoordWeighted); // non scalar
        float shadow1 = texture(shadow, shadowCoordWeighted);     // non scalar
        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 += texture(shadowtex1, shadowCoordWeighted);
#endif
    }

    avgDepth *= invIterations;
    shading *= invIterations;

#ifdef COLORED_SHADOWS
    colored = (shadowcolor.rgb * invIterations) * ambientA;
#endif
#ifdef CLOUD_SHADOWS
    shading *= cloudShadow(p3, noise);
#endif

    return shading;
}

// Main shadow function refactored to use helper functions.
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;

    vec2 absp3 = abs(p3.xz);
    float shading = 1.0;
    float SSS = sssAmount * 0.75 * max(diffuseSun, 0.5);

    // Precompute shadow matrices and related values
    mat3 shadowMV3 = mat3(shadowModelView);
    vec3 shadowProjDiag = vec3(shadowProjection[0].x, shadowProjection[1].y, shadowProjection[2].z);
    vec3 shadowProjTrans = shadowProjection[3].xyz;

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

    // Compute initial projected shadow position (without mix2 yet)
    vec3 tempProj = shadowProjDiag * (shadowMV3 * p3 + shadowModelView[3].xyz) + shadowProjTrans;
    float distortFacInit = calcDistort(tempProj.xy);
    float mix2 = computeMix2(distortFacInit, sssAmount);

    // Final projected shadow position with mix2 applied
    vec3 ProjShadowPos = computeProjectedShadowPos(p3, normals, shadowMV3, shadowProjDiag, shadowProjTrans, mix2);
    float distortFac = calcDistort(ProjShadowPos.xy);

    float pdepth = 0.0;
    float avgDepth = 0.3;

#ifndef DISTANT_HORIZONS
    if (absp3.x < shadowDistance && absp3.y < shadowDistance)
#else
    if (true)
#endif
    {
        // Disable SSS inside the shadow volume
        SSS = 0.0;

        ProjShadowPos.xy *= distortFac;
        ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.083333) + 0.5;

        shading = sampleShadowLoop(colored, avgDepth, pdepth, ProjShadowPos, distortFac, sssAmount, diffuseSun, noise, p3);

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

        if (sssAmount > 0.0)
            SSS = computeSSS(sssAmount, albedo, p3, lightPos, avgDepth, min(diffuseSun, shading)) *
                  (lightmap.y * lightmap.y * lightmap.y * lightmap.y);
    }

#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)
        {
            ProjShadowPos.xy *= distortFac;
            ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.083333) + vec3(0.5);
            float avgDepth2 = max(ProjShadowPos.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));
    }

    float rainFactor = -0.9 * rainStrength + 1.0;
    float invSSS = -sssAmount + 1.0;

    diffuseSun = min(diffuseSun, shading);
    diffuseSun = mix(diffuseSun * invSSS, SSS, sssAmount) * shadowblend * lightmap.y * rainFactor;

    return diffuseSun;
}
