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

// 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(1.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 distortMix = interpolate(shadowPosWorld.y).x * 0.01 * (1.0 - sssAmount);
    float distortThresh = clamp(sqrt(oneMinusDiffuseSunSq) / diffuseSun, 0.0, distortMix) / 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
    float eyeBright = pow(clamp(eyeBrightnessSmooth.y / 240.0, 0.0, 1.0), 0.01);

    float skylimit = mix(lightmap.y, 1.0, eyeBright);

    if (skylimit < 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;

    // === Shadow Bias ===
    // === Basic slope bias
    float ndotl = dot(normals, lightPos);
    float slope = clamp(1.0 - ndotl, 0.0, 1.0);

    // === Sample shadow distortion strength
    float distort = calcDistortMainDH(abs(p3.xy));

    // === Compensate for nonlinear warping
    float distortComp = 1.0 + distort * 1.5; // distortion-aware scaling

    // === Final bias (tunable)

    float bias = max(0.004, 0.02 * slope * distortComp);
#ifdef POM
    // If Parallax Occlusion Mapping is enabled, adjust bias for POM depth and normals
    bias += noise * 0.06 * (1.0 - abs(dot(normals, vec3(0.0, 1.0, 0.0))));

#endif // Assume p3 is a point in world space or view space
    float distanceMeters = length(p3);

    // Define near and far distances in meters where blending should occur
    float biasNear = 16;  // start blending at 10 cm
    float biasFar = 64.0; // fully transitioned at 1 meter

    // Remap distance to 0..1 range with smoothstep for a smooth blend
    float blendFactor = clamp(smoothstep(biasNear, biasFar, distanceMeters), 0, 1);

    // Mix between x and y based on this factor
    bias = mix(mix(bias, diffuseSun * 0.2, blendFactor), bias, clamp(sunAngle, 0.0, 1.0));
    // === Offset along normal (NOT lightDir, avoids acne on vertical blocks)
    vec3 newp3 = p3 + normals * bias;

    // Final projected shadow position with mix2 applied
    vec3 ProjShadowPos = computeProjectedShadowPos(newp3, normals, shadowMV3, shadowProjDiag, shadowProjTrans);
    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 blendEdge = 8.0;
    float mixamount = 1.0 - clamp((shadowDistance - distanceMeters) / blendEdge, 0.0, 1.0);

    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;
#ifdef CLOUD_SHADOWS
    float cloudshadow = cloudShadow(p3, noise);
    shading = min(cloudshadow, shading);
    SSS = min(cloudshadow, SSS);

#endif

    diffuseSun = min(diffuseSun, shading);
    diffuseSun = mix(diffuseSun * invSSS, SSS, sssAmount) * shadowblend * skylimit * rainFactor;

    return diffuseSun;
}
