#define WATER_NOISE_BUFFER waterNoise

float gerstner_wave(vec2 Coords, float Amp, float Freq, float Speed, vec2 Dir, float Steepness) {
    float x = dot(Coords, Dir) * Freq + frameTimeCounter * Speed;
    return Amp * sin(x);
}

vec2 gerstner_wave_d(vec2 Coords, float Amp, float Freq, float Speed, vec2 Dir, float Steepness) {
    float x = dot(Coords, Dir) * Freq + frameTimeCounter * Speed;
    return Amp * Dir * Freq * cos(x);
}

float get_water_height(vec3 WorldPos) {
    vec2 Coords = WorldPos.xz;
    float Sum = 0.0;

    // Primary swell - large, slow ocean waves
    vec2 dir1 = normalize(vec2(0.7, 0.8));
    Sum += gerstner_wave(Coords, 0.08, 0.25, 0.6, dir1, 0.8);
    Sum += gerstner_wave(Coords, 0.04, 0.45, 0.5, rotate(dir1, 0.3), 0.7);

    // Secondary chop - medium detail
    vec2 dir2 = normalize(vec2(-0.5, 1.0));
    Sum += gerstner_wave(Coords, 0.035, 0.8, 0.9, dir2, 0.6);
    Sum += gerstner_wave(Coords, 0.02, 1.2, 0.8, rotate(dir2, -0.5), 0.5);
    Sum += gerstner_wave(Coords, 0.015, 1.5, 1.1, rotate(dir2, 1.2), 0.5);

    // Fine detail - fast ripples
    vec2 dir3 = normalize(vec2(1.0, -0.3));
    Sum += gerstner_wave(Coords, 0.008, 2.5, 1.5, dir3, 0.4);
    Sum += gerstner_wave(Coords, 0.005, 3.5, 1.8, rotate(dir3, 0.7), 0.3);
    Sum += gerstner_wave(Coords, 0.003, 5.0, 2.2, rotate(dir3, -1.1), 0.3);

    // Micro detail
    vec2 dir4 = normalize(vec2(-0.8, 0.6));
    Sum += gerstner_wave(Coords, 0.002, 7.0, 2.8, dir4, 0.2);
    Sum += gerstner_wave(Coords, 0.001, 10.0, 3.5, rotate(dir4, 2.0), 0.2);

    return Sum;
}

vec2 get_water_height_d(vec3 WorldPos) {
    vec2 Coords = WorldPos.xz;
    vec2 Sum = vec2(0.0);

    // Primary swell
    vec2 dir1 = normalize(vec2(0.7, 0.8));
    Sum += gerstner_wave_d(Coords, 0.08, 0.25, 0.6, dir1, 0.8);
    Sum += gerstner_wave_d(Coords, 0.04, 0.45, 0.5, rotate(dir1, 0.3), 0.7);

    // Secondary chop
    vec2 dir2 = normalize(vec2(-0.5, 1.0));
    Sum += gerstner_wave_d(Coords, 0.035, 0.8, 0.9, dir2, 0.6);
    Sum += gerstner_wave_d(Coords, 0.02, 1.2, 0.8, rotate(dir2, -0.5), 0.5);
    Sum += gerstner_wave_d(Coords, 0.015, 1.5, 1.1, rotate(dir2, 1.2), 0.5);

    // Fine detail
    vec2 dir3 = normalize(vec2(1.0, -0.3));
    Sum += gerstner_wave_d(Coords, 0.008, 2.5, 1.5, dir3, 0.4);
    Sum += gerstner_wave_d(Coords, 0.005, 3.5, 1.8, rotate(dir3, 0.7), 0.3);
    Sum += gerstner_wave_d(Coords, 0.003, 5.0, 2.2, rotate(dir3, -1.1), 0.3);

    // Micro detail
    vec2 dir4 = normalize(vec2(-0.8, 0.6));
    Sum += gerstner_wave_d(Coords, 0.002, 7.0, 2.8, dir4, 0.2);
    Sum += gerstner_wave_d(Coords, 0.001, 10.0, 3.5, rotate(dir4, 2.0), 0.2);

    return Sum;
}

vec3 change_flow_dir(vec3 Coords, vec3 WorldNormal) {
    // Fix normals for water falling vertically
    if(abs(WorldNormal.y) < 0.01) {
        if(abs(WorldNormal.x) < 0.5) {
            Coords.xz = Coords.xy * sign(-WorldNormal.z);
        } else {
            Coords.xz = Coords.yz * sign(-WorldNormal.x);
        }
    }
    // Increase flow speed in the direction the water flows
    Coords.xz -= frameTimeCounter * normalize(WorldNormal.xz) * 9.2 * (1 - pow4(WorldNormal.y)); 
    return Coords;
}

vec3 get_water_normal(vec3 Coords, vec3 WorldNormal, float Dist) {
    Coords = change_flow_dir(Coords, WorldNormal);
    vec2 H = get_water_height_d(Coords);
    
    // Multi-scale micro-ripples using noise texture
    vec2 scrollA = vec2(frameTimeCounter * 0.04, frameTimeCounter * 0.03);
    vec2 scrollB = vec2(-frameTimeCounter * 0.03, frameTimeCounter * 0.05);

    vec2 noiseA = texture(waterNoise, Coords.xz * 0.4 + scrollA).rg;
    vec2 noiseB = texture(waterNoise, Coords.xz * 0.9 - scrollB * 1.3).rg;
    vec2 noiseC = texture(waterNoise, Coords.xz * 2.0 + scrollA * 0.7).rg;

    vec2 microRipple = (noiseA - 0.5) * 0.06;
    microRipple += (noiseB - 0.5) * 0.035;
    microRipple += (noiseC - 0.5) * 0.015;

    H += microRipple;

    // Distance fade for wave detail
    float detailFade = smoothstep(64.0, 16.0, Dist);
    H = mix(vec2(0.0), H, detailFade);

    return normalize(vec3(H.x, H.y, 1.0));
}

float get_water_caustics(vec3 PlayerPos) {
    vec3 WorldPos = PlayerPos + cameraPosition;
    vec3 slpP = view_player(sLightPosN);
    vec3 PlayerPosS = WorldPos - slpP / slpP.y * WorldPos.y;
    float WaterHeight = get_water_height(PlayerPosS);
    float CausticsColor = 3 * exp(-abs(WaterHeight) * 10) + 0.5;
    return CausticsColor;
}