#version 460

layout (location = 0) out vec4 accum;
layout (location = 1) out float reveal;

in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in vec3 world_pos;
in float roughness;
flat in int tex_layer;
in float v_depth;
layout (binding = 0) uniform sampler2DArray samp;
uniform sampler2D sceneColorTex;   // binding 1
uniform sampler2D sceneDepthTex;   // binding 2

uniform mat4 proj_matrix;
uniform mat4 inv_proj_matrix;
uniform mat4 inv_view_matrix;

uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 ambientColor;
uniform vec3 sunlightDir;
uniform bool shader_on;
uniform float specularStrength;

uniform vec3 skyTop;
uniform vec3 skyBottom;
uniform vec3 sunColor;
uniform float horizonSharpness;
uniform float cloudWhiteMix;
uniform float cloudThresholdLow;
uniform float cloudThresholdHigh;
uniform float time;

uniform vec3 sunDir; // world!

uniform bool underwater;

uniform float refractStrength;

uniform bool enablePerturb;
uniform bool enableDepthFade;

#include "compute_sky_color.glsl"

float weight(float z, float a) {
    float intermediate = 0.03 / (1e-5 + pow(z / 200.0, 4.0));
    
    return a * clamp(intermediate, 1e-2, 3e2);
}


// Reconstruct eye-space coordinates from screen UV and depth buffer value
vec3 reconstructViewPos(vec2 uv, float depth) {
    vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
    vec4 view = inv_proj_matrix * clip;
    return view.xyz / view.w;
}
// Screen-space ray marching, origin/dir are in eye space
bool traceSSR(vec3 origin, vec3 dir, out vec2 hitUV) {
    const int COARSE_STEPS = 32;
    const float COARSE_STEP = 0.6;   
    const int REFINE_STEPS = 8;
    const float THICKNESS = 0.3;

    vec3 pos = origin;
    vec3 prevPos = origin;
    bool foundCoarse = false;


    for (int i = 0; i < COARSE_STEPS; ++i) {
        prevPos = pos;
        pos += dir * COARSE_STEP;

        vec4 clip = proj_matrix * vec4(pos, 1.0);
        if (clip.w <= 0.0) return false;
        vec3 ndc = clip.xyz / clip.w;
        if (abs(ndc.x) > 1.0 || abs(ndc.y) > 1.0) return false;

        vec2 uv = ndc.xy * 0.5 + 0.5;
        float sceneDepth = texture(sceneDepthTex, uv).r;
        vec3 scenePos = reconstructViewPos(uv, sceneDepth);

        if (pos.z - scenePos.z < 0.0) {
            foundCoarse = true;
            break;   
        }
    }

    if (!foundCoarse) return false;

 
    vec3 lo = prevPos;
    vec3 hi = pos;

    for (int i = 0; i < REFINE_STEPS; ++i) {
        vec3 mid = (lo + hi) * 0.5;

        vec4 clip = proj_matrix * vec4(mid, 1.0);
        vec3 ndc = clip.xyz / clip.w;
        vec2 uv = ndc.xy * 0.5 + 0.5;
        float sceneDepth = texture(sceneDepthTex, uv).r;
        vec3 scenePos = reconstructViewPos(uv, sceneDepth);

        if (mid.z - scenePos.z < 0.0) {
            hi = mid;  
        } else {
            lo = mid; 
        }
    }

    vec4 finalClip = proj_matrix * vec4(hi, 1.0);
    vec3 finalNdc = finalClip.xyz / finalClip.w;
    vec2 finalUV = finalNdc.xy * 0.5 + 0.5;

    float finalDepth = texture(sceneDepthTex, finalUV).r;
    vec3 finalScenePos = reconstructViewPos(finalUV, finalDepth);

    if (abs(hi.z - finalScenePos.z) > THICKNESS) return false;

    hitUV = finalUV;
    return true;
}
void main() {

    vec4 objectColor = texture(samp, vec3(tc, tex_layer));

    if (!shader_on) {
        vec4 color = objectColor;
        float alpha = color.a;
        if (alpha < 1e-4) discard;


        float w = weight(v_depth, alpha);

        accum = vec4(color.rgb * alpha * w, alpha * w);

        reveal = alpha;
        return;
    }
    // Normal perturbation
    vec3 N;
    if (enablePerturb) {
        const float wave_speed_scale = 40.0;
        vec2 waveUV1 = world_pos.xz * 8.0 + vec2(time * 0.05 * wave_speed_scale, time * 0.03 * wave_speed_scale);
        vec2 waveUV2 = world_pos.xz * 13.0 + vec2(-time * 0.04 * wave_speed_scale, time * 0.06 * wave_speed_scale);

        float wave1 = noise(waveUV1);
        float wave2 = noise(waveUV2);

        vec2 normalPerturb = vec2(wave1 - 0.5, wave2 - 0.5) * 0.1;
        N = normalize(normal + vec3(normalPerturb.x, 0.0, normalPerturb.y));
    } else {
        N = normalize(normal);
    }

    vec3 V = normalize(-vert_pos); 

    vec3 reflectColor;
    vec3 refractColor;
    float fresnel;

    vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(sceneDepthTex, 0));

    // water depth
    float sceneDepthRaw = texture(sceneDepthTex, screenUV).r;
    vec3 sceneViewPos = reconstructViewPos(screenUV, sceneDepthRaw);
    float waterDepth = vert_pos.z - sceneViewPos.z;  
    waterDepth = max(waterDepth, 0.0);  

    const float DEPTH_FADE_DISTANCE = 10; 
    float depthFactor = clamp(waterDepth / DEPTH_FADE_DISTANCE, 0.0, 1.0);

    if (underwater) {
        reflectColor = vec3(0.0);
        refractColor = objectColor.rgb; 
        fresnel = 0.0;
    } else {
        vec3 R = reflect(-V, N);
        vec3 origin = vert_pos + N * 0.05;
        vec2 hitUV;

        if (traceSSR(origin, R, hitUV)) {
            reflectColor = texture(sceneColorTex, hitUV).rgb;
        } else {
            vec3 worldR = mat3(inv_view_matrix) * R;
            reflectColor = computeSkyColor(worldR);
        }

        float effectiveRefractStrength = refractStrength * (1.0 - depthFactor * 0.5);
        vec2 refractOffset = N.xz * effectiveRefractStrength;
        vec2 refractUV = clamp(screenUV + refractOffset, vec2(0.001), vec2(0.999));

        refractColor = texture(sceneColorTex, refractUV).rgb;

        fresnel = pow(1.0 - max(dot(N, V), 0.0), 5.0);
        fresnel = mix(0.02, 1.0, fresnel);
    }
   
    vec3 lightDir = normalize(-sunlightDir);
    vec3 H = normalize(lightDir + V);
    vec3 ambient = ambientStrength * ambientColor;
    float diff = max(dot(N, lightDir), 0.0);
    vec3 diffuse = diff * sunlightColor;

    float r = clamp(roughness, 0.0, 1.0);
    float shininess = mix(512.0, 4.0, r);
    float ks = mix(0.8, 0.02, r);

    float spec = 0.0;
    if (diff > 0.0) {
        spec = ks * pow(max(dot(N, H), 0.0), shininess);
    }
    vec3 specular = spec * sunlightColor * specularStrength;

    vec3 tintedRefract;

    if (enableDepthFade) {
        vec3 shallowColor = vec3(0.4, 0.75, 0.7);   
        vec3 deepColor = vec3(0.0, 0.015, 0.045); 

        vec3 waterTint = mix(shallowColor, deepColor, depthFactor);

        tintedRefract = mix(refractColor, refractColor * waterTint, depthFactor);
        tintedRefract = mix(tintedRefract, objectColor.rgb, 0.4);  // Forcefully blend 40% texture color, independent of depth
    } else {
        tintedRefract = mix(refractColor, objectColor.rgb, 0.4);
    }

    //vec3 baseWaterColor = (ambient + diffuse) * objectColor.rgb + specular;
    vec3 baseWaterColor = (ambient + diffuse) * tintedRefract + specular;
  
    vec3 finalColor = mix(baseWaterColor, reflectColor, fresnel);

    float alpha = objectColor.a;
    if (alpha < 1e-4) discard;


    float w = weight(v_depth, alpha);

    accum = vec4(finalColor * alpha * w, alpha * w);

    reveal = alpha;

    return;
}