#include "/lib/all_the_libs.glsl"
#include "/lib/distort.glsl"

uniform sampler2D gcolor;
uniform sampler2D lightmap;

// Uniforms needed for slope bias


// --- Distant Horizons Uniforms ---
#ifdef DISTANT_HORIZONS
uniform sampler2D dhDepthTex0;
#endif

varying vec2 texcoord;

// --- Noise Helper for Soft Shadows ---
float InterleavedGradientNoise(vec2 position) {
    vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189);
    return fract(magic.z * fract(dot(position, magic.xy)));
}

vec3 projectAndDivide(mat4 projectionMatrix, vec3 position){
  vec4 homPos = projectionMatrix * vec4(position, 1.0);
  return homPos.xyz / homPos.w;
}

float get_nostalgic_caustics(vec3 worldPos) {
    vec2 p = mod(worldPos.xz, 1000.0) * 1.5; 
    float t = frameTimeCounter * 0.4;

    p.x += sin(p.y * 1.2 + t) * 0.5;
    p.y += sin(p.x * 1.2 + t) * 0.5;

    // Standard Caustic Layer
    float grid1 = sin(p.x * 2.5 + t) + sin(p.y * 2.5 - t);
    float grid2 = sin(p.x * 5.0 - t) + sin(p.y * 5.0 + t);
    float baseCaustics = 1.0 - abs((grid1 + grid2 * 0.2) * 0.4);
    baseCaustics = pow(max(0.0, baseCaustics), 4.0);

    // --- HOTSPOT LAYER ---
    float sparkleGrid = sin(p.x * 6.0 + t * 2.0) * sin(p.y * 6.0 - t * 1.5);
    float hotspots = pow(max(0.0, sparkleGrid), 16.0) * 2.5; 

    return baseCaustics + (hotspots * baseCaustics);
}

// --- UTILS ---

float check_is_underwater(vec2 texcoord) {
    float depthAll = texture2D(depthtex0, texcoord).r;
    float depthSolid = texture2D(depthtex1, texcoord).r;
    if (depthAll < depthSolid - 0.0001) return 1.0;
    if (isEyeInWater == 1 && depthAll > depthSolid - 0.0001) return 1.0;
    return 0.0;
}
vec3 GetShadowColorSoft(vec3 shadowCoord, float NdotL) {
    if (clamp(shadowCoord, 0.0, 1.0) != shadowCoord) return vec3(1.0);

    // 1. Robust Bias
    float baseBias = SHADOW_BIAS / SHADOW_MAP_RESOLUTION;
    float bias = baseBias * tan(acos(NdotL)); 
    bias = clamp(bias, baseBias, baseBias * 8.0);

    // 2. Controlled Softness
    float shadowMapDepth = texture2D(shadowtex1, shadowCoord.xy).r;
    float distToCaster = max(shadowCoord.z - shadowMapDepth, 0.0);
    
    // If shadows are too pixelated, reduce the 400.0 to 200.0
    float spread = (distToCaster * 400.0 + 1.0) / SHADOW_MAP_RESOLUTION;
    spread = clamp(spread, 0.5 * SHADOW_MAP_RESOLUTION, 20.0 / SHADOW_MAP_RESOLUTION);

    // 3. 32-Tap Golden Spiral
    // We use a high-frequency noise to break up the banding
    float noise = InterleavedGradientNoise(gl_FragCoord.xy);
    float angle = noise * 6.2831853;
    mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));

    vec3 shadowAccum = vec3(0.0);
    const int SAMPLES = 32; // Optimized middle ground

    for(int i = 0; i < SAMPLES; i++) {
        // Fermat's Spiral for perfect distribution
        float r = sqrt(float(i + 0.5) / float(SAMPLES));
        float theta = float(i) * 2.3999632; // Golden Angle
        vec2 offset = rot * vec2(cos(theta), sin(theta)) * r * spread;
        
        vec2 sampleCoord = shadowCoord.xy + offset;
        float dAll = texture2D(shadowtex0, sampleCoord).r;
        float dOpaque = texture2D(shadowtex1, sampleCoord).r;
        
        float currentZ = shadowCoord.z - bias;

        if (currentZ < dAll) {
            shadowAccum += 1.0;
        } else if (currentZ < dOpaque) {
            shadowAccum += texture2D(shadowcolor0, sampleCoord).rgb;
        }
    }

    return shadowAccum / float(SAMPLES);
}

vec3 GetShadowColor(vec3 shadowCoord, float NdotL) {

    if (shadowCoord.x < 0.0 || shadowCoord.x > 1.0 ||

        shadowCoord.y < 0.0 || shadowCoord.y > 1.0 ||

        shadowCoord.z < 0.0 || shadowCoord.z > 1.0) {

        return vec3(1.0);

    }

   

    // 1. Calculate the base bias from your distort.glsl library

    float baseBias = SHADOW_BIAS/SHADOW_MAP_RESOLUTION;

   

    // 2. Gentle slope bias to fix the "rake" stripes on the ground

    float slope = sqrt(1.0 - NdotL * NdotL) / max(NdotL, 0.001);

    float slopeBias = clamp(slope, 0.0, 3.0);

    float finalBias = baseBias * (1.0 + slopeBias * 1.5);

   

    // 3. The Nostalgic Haze Setup

    float blurRadius = 2.5 / SHADOW_MAP_RESOLUTION;

   

    float noise = InterleavedGradientNoise(gl_FragCoord.xy);

    float angle = noise * 6.2831853;

    mat2 rotationMatrix = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));



    // A fast, 4-tap cross pattern rotated by the noise

    vec2 offsets[4] = vec2[4](

        vec2( 1.0,  1.0),

        vec2(-1.0, -1.0),

        vec2(-1.0,  1.0),

        vec2( 1.0, -1.0)

    );



    vec3 shadowTotal = vec3(0.0);

   

    for(int i = 0; i < 4; i++) {

        vec2 offset = rotationMatrix * offsets[i] * blurRadius;

        vec2 sampleCoord = shadowCoord.xy + offset;

       

        float depthAll = texture2D(shadowtex0, sampleCoord).r;

        float depthOpaque = texture2D(shadowtex1, sampleCoord).r;

       

        if (shadowCoord.z - finalBias < depthAll) {

            shadowTotal += vec3(1.0); // Sun hits it fully

        }

        else if (shadowCoord.z - finalBias < depthOpaque) {

            // Sun is blocked by translucent block (glass/water)

            shadowTotal += texture2D(shadowcolor0, sampleCoord).rgb;

        }

        else {

            shadowTotal += vec3(0.0); // Blocked by solid block

        }

    }

   

    return shadowTotal / 4.0;

}

void main() {
    vec3 Color = texture2D(colortex0, texcoord).rgb;
    float depth = texture2D(depthtex1, texcoord).r;

    vec3 NDCPos = vec3(texcoord.xy, depth) * 2.0 - 1.0;
vec3 viewPos = projectAndDivide(gbufferProjectionInverse, NDCPos);
vec3 feetPlayerPos = (gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz;
vec3 shadowViewPos = (shadowModelView * vec4(feetPlayerPos, 1.0)).xyz;
vec4 shadowClipPos = shadowProjection * vec4(shadowViewPos, 1.0);
shadowClipPos.z -= 0.001; // bias
shadowClipPos.xyz = distortShadowClipPos(shadowClipPos.xyz); // distortion
vec3 shadowNDCPos = shadowClipPos.xyz / shadowClipPos.w;
vec3 shadowCoords = shadowNDCPos * 0.5 + 0.5;

    bool isDH = false;
    #ifdef DISTANT_HORIZONS
    float depthDH = texture2D(dhDepthTex1, texcoord).r;
    if (depth >= 1.0 && depthDH < 1.0) {
        depth = depthDH;
        isDH = true;
    }
    #endif

 
 

    // --- Calculate NdotL for the Slope Bias ---
    vec3 viewNormal = texture2D(colortex9, texcoord).rgb * 2.0 - 1.0;
    vec3 viewLightDir = normalize(shadowLightPosition);
    float NdotL = clamp(dot(viewNormal, viewLightDir), 0.0, 1.0);

  

    if (depth < 1.0) {
        vec2 lmCoord = texture2D(lightmap, texcoord).rg;
        float blockLight = lmCoord.x;

        vec3 shadowFloor = vec3((blockLight)); 
        
        #ifdef SOFT_SHADOWS
        vec3 incomingLight = GetShadowColorSoft(shadowCoords, NdotL) * (1.0-thunderStrength);
        #else
        vec3 incomingLight = GetShadowColor(shadowCoords, NdotL) * (1.0-thunderStrength);
        #endif

        float nightShadowFactor = 0.4; 
        float shadowPower = mix(1.0, nightShadowFactor, nightStrength);
        vec3 finalShadow = max(incomingLight, shadowFloor);
        finalShadow = mix(vec3(1.0), finalShadow, shadowPower); 

        if(thunderStrength>0.5){
            finalShadow=vec3(blockLight * 15.0);
        }

        const vec3 SUN_COLOR = vec3(1.6, 1.0, 0.6) * SUN_COLOR_MULT;
        const vec3 MOON_COLOR = vec3(0.820, 0.761, 0.647); 
        vec3 currentLightColor = mix(SUN_COLOR, MOON_COLOR, nightStrength);
        float timeWeight = dayStrength + sunriseStrength + sunsetStrength + (nightStrength * 0.5);

        #ifdef GODRAYS
            vec3 finalSunlight = currentLightColor * finalShadow;
        #else
            vec3 finalSunlight = currentLightColor * finalShadow * timeWeight;
        #endif
        
        vec3 dayShadow = vec3(0.05, 0.05, 0.15); 
        vec3 nightLight = vec3(1.20, 1.20, 1.35); 
        float timeMix = clamp(timeWeight, 0.0, 1.0);
        vec3 ambientColor = mix(nightLight, dayShadow, timeMix); 

        Color *= (ambientColor + finalSunlight);
        
        float gray = dot(Color, vec3(0.299, 0.587, 0.114));
        Color = mix(vec3(gray), Color, 1.15); 
        
        Color *= 0.6; 
        Color *= vec3(1.05, 1.0, 0.95);
        Color = clamp(Color,0.0,1.0);

        #ifdef SPECULAR
        //skip hand
        if(depth >= 0.56)   {
            // 1. Reconstruct vectors needed for specular
            vec3 viewDir = normalize(-viewPos); // viewPos is already calculated in your code
            vec3 lightDir = normalize(shadowLightPosition);
            vec3 reflectDir = reflect(-lightDir, viewNormal);

            // 2. Grab the smoothness/f0 data you saved in terrain.fsh
            vec4 specData = texture2D(colortex8, texcoord);
            float smoothness = specData.r;
            float f0 = specData.g;
            bool isMetal = f0 > 0.5; // Or however you define metalness

            // 3. Calculate the Specular Strength
            float NdotH = max(0.0, dot(reflectDir, viewDir));
            float specularStrength = pow(NdotH, 10.0 + (100.0 * smoothness)) * smoothness;

            // 4. THE FIX: Mask it by the shadow (incomingLight)
            // Use the shadow variable you calculated earlier in the file
            specularStrength *= incomingLight.r; 

            // 5. Calculate the final color using your original formula
            vec3 specularCol = (isMetal ? Color : vec3(1.0)) * specularStrength * 2.0;

            // 6. Add it to the final scene color
            Color += specularCol * currentLightColor * timeWeight;
        }

        #endif
    }

 #ifdef WATER_CAUSTICS
        // --- CAUSTIC LOGIC ---
        float isUnderwater = check_is_underwater(texcoord);
        
        // Updated to pass NdotL so it compiles!
        float isSunlit = GetShadowColor(shadowCoords, NdotL).r; 

        // Grab the color of the transparent layer (water/glass)
        vec3 transColor = texture2D(colortex0, texcoord).rgb;
        bool isWater = (transColor.b > transColor.r + 0.05); // Simple color mask

        if (isUnderwater > 0.5 && isSunlit > 0.1 && (isWater || isEyeInWater == 1)) {
            
            float floorDepth = texture2D(depthtex1, texcoord).r;
            vec4 cPos = vec4(texcoord * 2.0 - 1.0, floorDepth * 2.0 - 1.0, 1.0);
            vec4 vPos = gbufferProjectionInverse * cPos;
            if (abs(vPos.w) > 0.00001) {
                vPos /= vPos.w; 
            }
            vec4 floorPos = gbufferModelViewInverse * vPos; 

            vec3 absoluteWorldPos = floorPos.xyz + cameraPosition;

            float rPattern = get_nostalgic_caustics(absoluteWorldPos + 0.04);
            float gPattern = get_nostalgic_caustics(absoluteWorldPos);
            float bPattern = get_nostalgic_caustics(absoluteWorldPos - 0.04);
            
            vec3 causticColor;
            causticColor.r = rPattern * 1.1; 
            causticColor.g = gPattern;
            causticColor.b = bPattern * 1.2; 

            vec3 sunTint = vec3(1.0, 0.9, 0.7); 
            vec3 finalCaustic = causticColor * sunTint;

            float intensity = (dayStrength + 0.001) * CAUSTICS_STRENGTH * (1.0-rainStrength);
            Color.rgb += finalCaustic * isSunlit * intensity;

            if (gPattern > 0.8) {
                Color.rgb += vec3(0.1, 0.08, 0.05) * gPattern * isSunlit;
            }
        }
    #endif

    gl_FragColor = vec4(Color, 1.0);
}