/* This file contains functions for calculating reflections in water
#include "/lib/ssr/ssr_common.glsl"
*/
#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)
#define projMAD(m, v) (diagonal3(m) * (v) + (m)[3].xyz)
#define viewMAD(m, v) (mat3(m) * (v) + (m)[3].xyz)
#define WATER_ROUGHNESS 0.01

vec3 toClipSpace3_voxy(vec3 viewSpacePosition)
{
    return projMAD(vxProj, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}
vec3 RT_SIMPLE(vec3 dir, vec3 position, float roughness, float dither, float quality)
{
    float lod_near = near;
    float lod_far = float(16 * vxRenderDistance);
    vec3 rtPos = vec3(1.1);

    vec3 farthestClipPos = toClipSpace3_voxy(position + dir * 1024);

    if (all(greaterThan(farthestClipPos.xy, vec2(0.0))) && all(lessThan(farthestClipPos.xy, vec2(1.0))))
    {
        float sampledDepth = texture(vxDepthTexOpaque, farthestClipPos.xy).r;
        float currentDepth = (farthestClipPos.z * lod_far);

        if (sampledDepth < 1.0 && currentDepth <= (sampledDepth * lod_far) + 0.1)
        {
            rtPos = farthestClipPos;
        }
    }
    // return rtPos;

    vec3 clipPosition = toClipSpace3_voxy(position);
    vec3 direction = normalize(toClipSpace3_voxy(position + dir) - clipPosition);
    vec3 maxLengths = (step(0.0, direction) - clipPosition) / direction;

    float mult = min(maxLengths.x, min(maxLengths.y, maxLengths.z));

    vec3 stepv = direction * mult / quality;
    vec3 spos = clipPosition + stepv * dither;
    float tolerance = abs(stepv.z) * 4.0;

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

        float depthSample = texelFetch(vxDepthTexOpaque, ivec2(spos.xy / texelSize), 0).r;

        if (depthSample < spos.z)
        {
            float dist = abs(depthSample - spos.z) / spos.z;
            if (dist <= 0.5)

                rtPos = vec3(spos.xy, depthSample);
            break;
        }
        spos += stepv;
    }

    return rtPos;
}

float luma(vec3 color)
{
    return dot(color, vec3(0.299, 0.587, 0.114));
}
vec3 GGX1(
    vec3 N,
    vec3 V,
    vec3 L,
    float roughness,
    vec3 F0)
{
    // half‐vector + dot products
    vec3 H = normalize(V + L);
    float NdotV = clamp(dot(N, V), 0.0, 1.0);
    float NdotL = clamp(dot(N, L), 0.0, 1.0);
    float NdotH = clamp(dot(N, H), 0.0, 1.0);
    float VdotH = clamp(dot(V, H), 0.0, 1.0);

    // sun angular radius: blend between dry & wet limits, then clamp
    float minSun = 0.005; // ~0.57°
    float maxSun = 0.200; // ~11.5°
    float sunAngRad = mix(minSun, maxSun, clamp(rainStrength, 0.0, 1.0));
    sunAngRad = clamp(sunAngRad, minSun, maxSun);

    // combined variance = roughness² + sunAngRad², with a floor
    float rou = clamp(roughness, 0.0, 1.0);
    float rou2 = rou * rou + sunAngRad * sunAngRad;
    rou2 = max(rou2, 1e-6);
    float a = sqrt(rou2);
    float a2 = a * a;

    // GGX NDF
    float denomD = NdotH * NdotH * (a2 - 1.0) + 1.0;
    denomD = max(denomD, 1e-6);
    float D = a2 / (3.1415 * denomD * denomD);

    // Smith‐Schlick geometry
    float k = (a + 1.0) * (a + 1.0) * 0.125;
    float Gv = NdotV / (NdotV * (1.0 - k) + k);
    float Gl = NdotL / (NdotL * (1.0 - k) + k);
    float G = Gv * Gl;

    // Schlick Fresnel
    vec3 F = mix(F0, vec3(1.0), pow(1.0 - VdotH, 5.0));

    // keep denominators safe
    float denom = max(4.0 * NdotV * NdotL, 1e-6);

    // final specular, modulated by N·L
    return D * G * F * (NdotL / denom);
}

#define HALF_ANGLE 0.05 // radians
#define TILT_ANGLE 1.5  // radians

vec3 GGX2(
    vec3 N,
    vec3 V,
    vec3 sunDir,
    float roughness,
    vec3 F0)
{
    vec3 outval = GGX1(
        N,
        V,
        sunDir,
        roughness,
        F0);

    return outval;
}
vec4 mergeSpecularLighting(
    vec3 reflectionColor,
    vec3 rayContrib, // fresnel
    vec3 specTerm,
    vec3 albedo,
    vec3 sunLight,
    vec4 scene,
    float alpha0,
    bool inWater)
{
    vec3 indirectSpecular = reflectionColor * rayContrib;
    vec3 fresnelDiffuse = rayContrib;

    vec3 specularContribution = indirectSpecular + specTerm * sunLight;
    vec3 inverseFresnel = 1.0 - fresnelDiffuse;

    float effectiveAlpha = mix(scene.a, 1.0, luma(fresnelDiffuse));

    if (inWater)
        specularContribution *= scene.a;

    vec3 baseColorCorrected = scene.rgb * alpha0 * inverseFresnel;
    vec3 outColor = (specularContribution + inverseFresnel * baseColorCorrected) * albedo;

    return vec4(outColor, effectiveAlpha);
}

float R2_dither(vec2 coord)
{
    vec2 alpha = vec2(0.75487765, 0.56984026);
    return fract(alpha.x * coord.x + alpha.y * coord.y + 1.0 / 1.6180339887 * frameCounter);
}

vec2 spherical_coordinates(vec3 xyz)
{
    // Cartesian coordinates
    float x = xyz.x;
    float y = xyz.y;
    float z = xyz.z;

    // Compute spherical coordinates
    float theta = acos(z / length(xyz)); // Polar angle
    float phi = (atan(y, x) + 1.5708);   // Azimuthal angle

    // Normalize to [0, 1] for UV mapping
    float u = phi / TWO_PI;
    if (u < 0.0)
        u += 1.0;
    float v = theta / PI;

    return vec2(u, v);
}
vec4 skyFromTex2(vec3 pos, sampler2D sampler)
{
    vec2 p = spherical_coordinates(normalize(pos));

    return texture2D(sampler, p);
}
vec3 toScreenSpace(vec3 position)
{
    position = position * 2.0 - 1.0;
    position.xy = vec2(vxProjInv[0].x, vxProjInv[1].y) * position.xy + vxProjInv[3].xy;
    return vec3(position.xy, vxProjInv[3].z) /
           (vxProjInv[2].w * position.z + vxProjInv[3].w);
}
vec3 ViewSpaceFromScreenSpace(vec3 pos, mat4 inverseProjection)
{
    pos = pos * 2.0 - 1.0;
    vec3 viewPosition = vec3(vec2(inverseProjection[0].x, inverseProjection[1].y) * pos.xy + inverseProjection[3].xy, inverseProjection[3].z);
    viewPosition /= inverseProjection[2].w * pos.z + inverseProjection[3].w;
    return viewPosition;
}

vec3 ScreenSpaceFromViewSpace(vec3 viewPosition, mat4 projection)
{
    vec3 screenPosition = vec3(projection[0].x, projection[1].y, projection[2].z) * viewPosition + projection[3].xyz;
    return screenPosition * (0.5 / -viewPosition.z) + 0.5;
}

vec3 toWorldSpaceNoLock(vec3 p3)
{
    p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;
    return p3;
}
vec3 reprojectDH(vec3 sceneSpace, bool hand)
{
    vec3 prevScreenPos = hand ? vec3(0.0) : cameraPosition - previousCameraPosition;
    prevScreenPos = sceneSpace + prevScreenPos;
    prevScreenPos = viewMAD(gbufferPreviousModelView, prevScreenPos);
    prevScreenPos = viewMAD(vxProjPrev, prevScreenPos) * (0.5 / -prevScreenPos.z) + 0.5;
    return prevScreenPos;
}

vec4 calculateSpecularLightingHQ(
    vec3 normal,
    vec3 np3,
    vec3 albedo,
    float shading,
    float skylight,
    float roughness,
    float porosity,
    vec3 fragpos,
    vec4 scene,
    float alpha0,
    bool isice)
{
    float iswater = 1.0;
    bool inWater = (isEyeInWater == 1);
    vec3 reflectionColorScene = scene.rgb;
    bool waterMask = (iswater > 0.99);
    const float f0 = 0.035;

    // Determine the incident and transmitted refractive indices based on the environment
    float etaI = inWater ? 1.33 : 1.0;

    vec3 specTerm = shading * GGX2(normal, -np3, lightPos, WATER_ROUGHNESS, vec3(f0));
    if (isice)
    {

        vec4 final = mergeSpecularLighting(reflectionColorScene*1.2, vec3(1), specTerm, vec3(1.0), sunLight, scene, alpha0, inWater);
        // return vec4(vec3(specTerm), 1);
        return final;
    }
    float noise = mix(R2_dither(gl_FragCoord.xy), 0.5, 0.0);
    vec3 L = (reflect(np3, normal));

    vec3 L2 = L;

    float cosThetaI = dot(np3, normal);
    float clampedCosThetaI = clamp(1.0 + cosThetaI, 0.0, 1.0);
    float F = f0 + (1.0 - f0) * pow(clampedCosThetaI, 5.0);

    // Precompute etaI squared
    float etaISq = etaI * etaI;
    float sinThetaTSq = etaISq * (1.0 - cosThetaI * cosThetaI);
    if (sinThetaTSq > 1.0)
    {
        L2.y = -L2.y;
        F = 1.0;
    }
    //  L2.y = mix(L.y, -0.5, clamp(-cosThetaI, 0, 1));
    // L2 = normalize(L2);

    // Geometry term simplified for smooth surfaces with precomputed k
    float NoL = clamp(dot(normal, L), 0.0, 1.0);
    float eps = fwidth(NoL);
    float G1 = NoL / (NoL * 0.99995 + 0.00005 + eps);

    // Calculate the ray's contribution to the final color
    float rayContrib = F * G1;

    vec4 sky = skyFromTex2(L, colortex6);
    sky.rgb = mix(sky.rgb, vec3(sky.a * sunLight), clamp(sky.a, 0, 1));

    vec3 reflectionColor = mix(scene.rgb, sky.rgb, skylight);

    vec3 rtPos = RT_SIMPLE(mat3(gbufferModelView) * L, fragpos, roughness, noise, 32);

    if (rtPos.z < 1.0)

    {
        vec3 previousPosition =
            reprojectDH(mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz, false);
        if (previousPosition.x > 0.0 && previousPosition.y > 0.0 && previousPosition.x < 1.0 && previousPosition.y < 1.0)
        {
            reflectionColor = texture(colortex4, previousPosition.xy).rgb;
        }
    }

    vec4 final = mergeSpecularLighting(reflectionColor, vec3(rayContrib), specTerm, vec3(1.0), sunLight, scene, alpha0, inWater);
    // return vec4(vec3(specTerm), 1);
    return final;
}

vec4 reflections(float skyLight, vec3 normal, vec3 fragpos, vec4 materialProps, vec4 outcolor, float shading, vec3 albedo, float alpha0, bool isice)
{

    vec3 p3 = mat3(gbufferModelViewInverse) * fragpos;
    vec3 f0 = vec3(materialProps.z);

    float porosity = 0;

    vec4 reflection = calculateSpecularLightingHQ(normal, normalize(p3), albedo, shading, skyLight, pow(1.0 - materialProps.w, 2.0), porosity, fragpos, outcolor, alpha0, isice);
    return reflection;
}
