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

// Constants
const float INV_SHADOW_RES = 1.0 / shadowMapResolution;

//----------------------------------------------------------------------------
// Shadow Loop: Performs PCF filtering and approximate SSS/Translucency
//----------------------------------------------------------------------------
float sampleShadowLoop(inout vec3 colored, inout float avgDepth, inout float penumbraDepth,
                       vec3 shadowScreenPos, float distortFac, float sssAmount,
                       float diffuseSun, float noise)
{
    // Initialize outputs
    float accumShading = 0.0;
    avgDepth = 0.0;

    // Setup constants and limits
    const int iterations = SHADOW_SAMPLES;
    const float invIterations = 1.0 / float(iterations);

    // Filter depth definition (previously duplicated in #ifdefs)
    const vec3 filterDepth = vec3(1.0, 16.0, 128.0);
    float filterRange = filterDepth.y - filterDepth.x;

    // Distortion and Radius calculations
    float radiusMax = distortFac * INV_SHADOW_RES;
    float depthRadiusMultiplier = 0.2 * distortFac * INV_SHADOW_RES; // old rdMul0

    // Calculate adaptive threshold based on altitude and sun
    float altitudeFactor = clamp((eyeAltitude - 300.0) / 300.0, 0.0, 1.0);
    float threshBase = 2048.0 + 6144.0 * altitudeFactor;
    float threshMul = max(threshBase * INV_SHADOW_RES * shadowDistance * 0.01, 0.95);

    // Distortion thresholding for PCF
    float oneMinusSunSq = 1.0 - diffuseSun * diffuseSun;
    float distortMix = interpolate(shadowPosWorld.y).x * 0.01 * (1.0 - sssAmount);
    float distortThresh = clamp(sqrt(oneMinusSunSq) / diffuseSun, 0.0, distortMix) / distortFac;

    float diffThresh = (distortThresh / 6000.0) * threshMul;
    float diffThreshHalf = 0.5 * diffThresh;

    // Precompute loop constants
    int seedCounter = (frameCounter % 40000) * iterations;
    float prteComp = radiusMax * 2.7 * invIterations / (shadowMapResolution * distortFac);
    vec2 dither = blueNoise4(gl_FragCoord.xy).rg;

    // SSS Helpers
    float sssDepthScale = mix(2048.0, 512.0, sssAmount);
    float sssIntermediate = mix(1.0, 16.0, sssAmount);

    // Blocker search accumulators
    float blockerCount = 0.0;
    float blockerDepthSum = 0.0;

#ifdef COLORED_SHADOWS
    vec4 accumShadowColor = vec4(0.0);
#endif

    for (int i = 0; i < iterations; i++)
    {
        // Generate offset
        vec2 ij = fract(R2_samples3(seedCounter + i) + dither);
        vec2 offset = tapLocationBlueNoise(i, iterations, 80, ij);

        // Sample Shadow Map
        vec2 sampleUV = shadowScreenPos.xy + offset * radiusMax;
        float depthSample = texture(shadowtex0, sampleUV).x;

        float zDiff = shadowScreenPos.z - depthSample;
        avgDepth += max(zDiff, 0.0) * sssDepthScale;

        // Calculate Blocker Weight
        float weightScale = 3.0 + (float(i) + noise) * prteComp;
        float shadowWeight = smoothstep(weightScale * diffThreshHalf, weightScale * diffThresh, zDiff);

        blockerCount += shadowWeight;
        blockerDepthSum += depthSample * shadowWeight;

        // Calculate Penumbra Depth (Contact Hardening logic)
        // If no blockers, assume surface depth is the blocker
        float currentBlockerDepth = (blockerCount > 0.0) ? (blockerDepthSum / blockerCount) : shadowScreenPos.z;
        float t0 = max((shadowScreenPos.z - currentBlockerDepth) / filterDepth.z, 0.0) * 1500.0;

        // If we have significant blockers, update penumbra depth
        penumbraDepth = (t0 * filterRange + filterDepth.x) * ((blockerCount > 0.9) ? 1.0 : 0.0) * depthRadiusMultiplier;

        // SSS / Translucency Scatter
        // shadingIter represents current accumulated light normalized.
        float shadingNormalized = accumShading * invIterations;
        float scatterFactor = clamp(diffuseSun * 16.0 * shadingNormalized + shadingNormalized, 0.0, 1.0);
        float sssBackScatter = mix(sssIntermediate, 1.0, scatterFactor);

        // Weighted coordinate for the second sample (Colored/Soft shadows)
        vec3 weightedCoord = shadowScreenPos;
        weightedCoord.xy += offset * penumbraDepth * sssBackScatter;
        weightedCoord.z -= diffThresh * (1.0 + noise * shadowMapResolution * penumbraDepth * invIterations);

#ifdef COLORED_SHADOWS
        float s0 = texture(shadowtex1, weightedCoord);
        float s1 = texture(shadow, weightedCoord);

        // Check for transparency (difference between opaque and translucent shadow maps)
        if (abs(s1 - s0) > 0.0)
        {
            // Approximate colored transmission
            accumShading += clamp(s1 + clamp(1.0 - accumShadowColor.a, 0.0, 1.0), 0.0, 1.0);
            accumShadowColor += texture(shadowcolor0, weightedCoord.xy);
        }
        else
        {
            accumShading += s1;
        }
#else
        accumShading += texture(shadowtex1, weightedCoord);
#endif
    }

    // Normalize results
    avgDepth *= invIterations;
    accumShading *= invIterations;

#ifdef COLORED_SHADOWS
    colored = (accumShadowColor.rgb * invIterations) * ambientA;
#endif

    // Note: Removed Cloud Shadows here. They are better applied once in the main function.

    return accumShading;
}

//----------------------------------------------------------------------------
// Main Shadow Function
//----------------------------------------------------------------------------
float shadowFunc(
    float diffuseSun,
    float sssAmount,
    vec3 viewPos, // Originally p3
    float noise,
    vec2 lightmap,
    vec3 fragPos,
    inout vec3 sAlbedo,
    vec3 albedo,
    vec3 normals,
    inout vec3 colored,
    float entityId)
{
    // 1. Lightmap Early Exit
    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;

    // 2. Setup Variables
    vec2 absPosXZ = abs(viewPos.xz);
    float shading = 1.0;
    float SSS = 0;
    float avgDepth = 0.3;
    float penumbraDepth = 0.0;
    float distMeters = length(viewPos);

// 5. Standard Shadow Mapping (Near)
// Check if within standard shadow distance
#ifndef DISTANT_HORIZONS
    bool isStandardShadow = absPosXZ.x < min(shadowDistance, farorg) && absPosXZ.y < min(shadowDistance, farorg);
#else
    bool isStandardShadow = true;
#endif

    if (isStandardShadow)
    {
        // Shadow Matrices
        mat3 shadowMV3 = mat3(shadowModelView);
        vec3 shadowProjDiag = vec3(shadowProjection[0].x, shadowProjection[1].y, shadowProjection[2].z);
        vec3 shadowProjTrans = shadowProjection[3].xyz;

        // 3. Bias Calculation
        float NdotL = dot(normals, lightPos);
        float slopeBias = clamp(1.0 - NdotL, 0.0, 1.0);

        // Distortion-aware bias
        float distortStrength = calcDistortMainDH(abs(viewPos.xy));
        float distortComp = 1.0 + distortStrength * 1.5;
        float baseBias = max(0.004, 0.05 * slopeBias * distortComp);

#ifdef POM
        // POM specific bias adjustment
        float NdotUp = abs(dot(normals, vec3(0.0, 1.0, 0.0)));
        baseBias += noise * 0.06 * (1.0 - NdotUp);
#endif

        // Distance-based bias blending (16m to 64m transition)
        float biasBlend = smoothstep(16.0, 64.0, distMeters);

        // Mix bias based on distance and sun angle
        baseBias = mix(mix(baseBias, diffuseSun * 0.2, biasBlend), baseBias, clamp(sunAngle, 0.0, 1.0));

        // 4. Project to Shadow Space
        // Offset position by normal to prevent acne
        vec3 offsetPos = viewPos + normals * baseBias;
        vec3 projShadowPos = computeProjectedShadowPos(offsetPos, normals, shadowMV3, shadowProjDiag, shadowProjTrans);
        float distortFac = calcDistort(projShadowPos.xy);

        // Inside shadow volume, SSS is recalculated by the loop
        SSS = 0.0;

        // Apply distortion to coords
        projShadowPos.xy *= distortFac;
        projShadowPos = projShadowPos * vec3(0.5, 0.5, 0.083333) + 0.5;

        // Sample
        shading = sampleShadowLoop(colored, avgDepth, penumbraDepth, projShadowPos, distortFac, sssAmount, diffuseSun, noise);

#if defined(RSM)
        if (entityId < 0.5)
        {
            sAlbedo = calculateAlbedo(projShadowPos, distortFac, ambientA, lightmap);
        }
#endif
    }

// 6. Extended Shadows / VOXY Integration
#if defined(EXTENDED_SHADOWS) || defined VOXY
    float blendStart = min(shadowDistance, farorg);
    float blendFactor = 1.0 - clamp((blendStart - distMeters) * 0.03125, 0.0, 1.0);

    #ifdef VOXY
    // Check Voxy specific depth/borders
    float vDepth = texture(vxDepthTexOpaque, texcoord, 0).x;
    bool isVanilla = vDepth >= 1.0;

    // Reconstruct world pos for Voxy boundary check
    vec3 worldPosVoxy = toWorldSpace(toScreenSpace_og(vec3(texcoord, vDepth)));
    bool atVoxyBorder = (max(length(worldPosVoxy.xz), abs(worldPosVoxy.y)) / far) < (farorg - 16.0) / far;

    if (entityId > 0.5)
        blendFactor = 0.0;

    else if (!isVanilla || atVoxyBorder)
        blendFactor = 1.0;

    #endif
    // Blend with RayTraced Shadows if needed
    if (blendFactor > 0.0)
    {
        float lmSq = lightmap.y * lightmap.y;
        vec2 rtShadow = rayTraceShadowAlt(diffuseSun, worldToView(lightPos), fragPos, noise, sssAmount > 0.0);

        shading = mix(shading, rtShadow.x * lmSq, blendFactor);
        avgDepth = mix(avgDepth, clamp(rtShadow.y * PI, 0.0, 100.0), blendFactor);
    }
#endif

    // 7. Contact Shadows & Final Masking
    if (diffuseSun * shading > 0.001)
    {
#ifdef CONTACT_SHADOWS
        if (sssAmount == 0.0)
        {
            shading = min(rayTraceShadow(diffuseSun, worldToView(lightPos), fragPos, noise), shading);
        }
#endif

        // Fade out shadows based on eye brightness/lightmap
        shading = mix(0.0, shading, min(eyeBrightnessSmooth.y * recip + lightmap.y, 1.0));
    }

    if (sssAmount > 0.0)
    {
        // Re-inject SSS based on calculated avgDepth
        float lm4 = pow(lightmap.y, 4.0);
        SSS = computeSSS(sssAmount, albedo, viewPos, lightPos, avgDepth, min(diffuseSun, shading)) * lm4;
    }
#if !defined EXTENDED_SHADOWS && !defined VOXY || !defined VOXY
    if (!isStandardShadow)
        SSS = sssAmount * 0.75 * max(diffuseSun, 0.5);
#endif

    // 8. Global Environmental Factors (Rain, Clouds)
    float rainFactor = 1.0 - (0.9 * rainStrength);

#ifdef CLOUD_SHADOWS
    float cloudShade = cloudShadow(viewPos, noise);
    shading = min(cloudShade, shading);
    SSS = min(cloudShade, SSS);
#endif

    // 9. Final Composition
    // Blend between pure diffuse shadow and SSS
    float finalLight = min(diffuseSun, shading);
    float invSSS = 1.0 - sssAmount;

    finalLight = mix(finalLight * invSSS, SSS, sssAmount);

    // Apply global masks
    finalLight *= shadowblend * skyLimit * rainFactor;

    return finalLight;
}