
#include "/lib/ssr/ssr_common.glsl"


vec3 RT_SIMPLE(vec3 dir, vec3 position, float roughness, float dither, float quality)
{
    vec3 rtPos = vec3(1.1);

    vec3 farthestClipPos = toClipSpace3(position + dir * 64);

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

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

    if (roughness > 0.3)
        return rtPos;

    vec3 clipPosition = toClipSpace3(position);
    vec3 direction = normalize(toClipSpace3(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;

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

        float depthSample = texelFetch2D(depthtex1, ivec2(spos.xy / texelSize), 0).r;

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

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

    return rtPos;
}
vec3 RT5(vec3 dir, vec3 position, float dither, float steps)
{

    float quality = steps;
#ifdef DH

    vec3 clipPosition = toClipSpace3DH(position);
    vec3 direction = normalize(toClipSpace3DH(position + dir) - clipPosition);
#else
    vec3 clipPosition = toClipSpace3(position);
    vec3 direction = normalize(toClipSpace3(position + dir) - clipPosition);

#endif
    vec3 maxLengths = (step(0.0, direction) - clipPosition) / direction;
    float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);

    vec3 stepv = (direction * minLength) / quality;

    float tolerance = max(abs(stepv.z) * 2.0, 0.5 / (position.z * position.z));

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

        vec3 spos = clipPosition + stepv * (i + dither);

        spos.xy += offsets[framemod8] * texelSize * 0.5;

#ifdef DH
        float depthSample = texelFetch2D(dhDepthTex1, ivec2(spos.xy / texelSize), 0).r;
#else
        float depthSample = texelFetch2D(depthtex1, ivec2(spos.xy / texelSize), 0).r;
#endif

        if (depthSample < spos.z && abs(spos.z - depthSample) < tolerance)
        {
            return vec3(spos.xy, depthSample);
        }
    }

    return vec3(1.1); // Indicates no intersection found
}
vec3 RT5LQ(vec3 dir, vec3 position, float dither, float steps)
{

    float quality = steps;
#ifdef DH

    vec3 clipPosition = toClipSpace3DH(position);
    vec3 direction = normalize(toClipSpace3DH(position + dir) - clipPosition);
#else
    vec3 clipPosition = toClipSpace3(position);
    vec3 direction = normalize(toClipSpace3(position + dir) - clipPosition);

#endif
    vec3 maxLengths = (step(0.0, direction) - clipPosition) / direction;
    float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);

    vec3 stepv = (direction * minLength) / quality;

    float tolerance = max(abs(stepv.z) * 4.0, 0.75 / (position.z * position.z));

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

        vec3 spos = clipPosition + stepv * (i + dither);

#ifdef DH
        float depthSample = texelFetch2D(dhDepthTex1, ivec2(spos.xy / texelSize), 0).r;
#else
        float depthSample = texelFetch2D(depthtex1, ivec2(spos.xy / texelSize), 0).r;
#endif

        if (depthSample < spos.z && abs(spos.z - depthSample) < tolerance)
        {
            return vec3(spos.xy, depthSample);
        }
    }

    return vec3(1.1); // Indicates no intersection found
}
vec3 RT5XLQ(vec3 dir, vec3 position, float dither, float steps)
{
#ifdef DH
    vec3 clipPosition = toClipSpace3DH(position);
    vec3 direction = normalize(toClipSpace3DH(position + dir) - clipPosition);
#else
    vec3 clipPosition = toClipSpace3(position);
    vec3 direction = normalize(toClipSpace3(position + dir) - clipPosition);
#endif

    // Calculate step size in clip space
    vec3 maxLengths = (step(0.0, direction) - clipPosition) / direction;
    float minLength = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = direction * (minLength / steps);

    float tolerance = abs(stepv.z) * 4.0;

    // Precompute texel size scaling
    vec2 texCoordScale = 1.0 / texelSize;

    float scaledDither0 = mix(0.1, 1.0, dither);
    float scaledDither1 = mix(0.9, 1.0, dither);
    float scaledDither = mix(scaledDither1, scaledDither0, float(steps >= 4));
    for (int i = 0; i < steps; ++i)
    {
        vec3 samplePosition = clipPosition + stepv * (float(i) + scaledDither);

        // Use integer coordinates for sampling directly
        ivec2 texCoords = ivec2(samplePosition.xy * texCoordScale);
#ifdef DH
        float depthSample = texelFetch2D(dhDepthTex1, texCoords, 0).r;

#else
        float depthSample = texelFetch2D(depthtex1, texCoords, 0).r;
#endif
        if (depthSample < samplePosition.z && abs(samplePosition.z - depthSample) < tolerance)
        {
            return vec3(samplePosition.xy, depthSample);
        }
    }

    return vec3(1.1); // No valid depth found
}

// GGX distribution function G1
float g(float NdotL, float roughness)
{
    float k = (roughness * roughness) / 2.0;
    return NdotL / (NdotL * (1.0 - k) + k);
}
float g2(float NdotL)
{
    return NdotL / (NdotL * 0.9999 + 0.0001);
}

vec2 R2_samples(int n)
{
    vec2 alpha = vec2(0.75487765, 0.56984026);
    return fract(alpha * n);
}

#include "/lib/specular.glsl"

const bool colortex4MipmapEnabled = true;

vec4 calculateSpecularLightingHQ(
    vec3 normal,
    vec3 np3,
    vec3 albedo,
    float shading,
    float skylight,
    float roughness,
    vec3 f0,
    float porosity,
    vec3 fragpos,
    vec4 scene,
    float alpha0)
{
    bool inWater = (isEyeInWater == 1);
    vec3 reflectionColorScene = scene.rgb;

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

    // Compute metal index from f0.y
    int metalidx = int((f0.y * 255));
    bool isMetal = (metalidx > 229);

    f0 = CalculateF0(f0, isMetal, metalidx, albedo);
    // Sun specular term
    vec3 specTerm = shading * GGX2(normal, -np3, lightPos, roughness, f0);

    // Indirect specular calculation
    vec3 indirectSpecular = vec3(0.0);
    vec3 fresnelDiffuse = vec3(0.0);
    float noise = mix((alpha0 < 0.1) ? 0.5 : blueNoise(gl_FragCoord.xy), 0.5, luma(f0));

    mat3 basis = CoordBase(normal);
    vec3 normSpaceView = normalize(-np3 * basis);

    // Sample GGX VNDF
    vec3 H = SampleGGXVNDF2(normSpaceView, roughness, noise);
    vec3 Ln = reflect(-normSpaceView, H);
    vec3 L = basis * Ln;

    vec3 L2 = L;
    // Fresnel Schlick with TIR handling
    float cosThetaI = dot(-Ln, H); // cosine of the angle between incident light and half-vector

    // Calculate sin^2 of the transmitted angle using Snell's law
    float sinThetaTSq = etaI * etaI * (1.0 - cosThetaI * cosThetaI);

    // Fresnel-Schlick approximation with TIR handling
    vec3 F = f0 + (1.0 - f0) * pow(clamp(1.0 + dot(-Ln, H), 0.0, 1.0), 5.0);
    if (sinThetaTSq > 1.0)
    {
        L2.y = -L2.y;
        F = vec3(1.0);
    }

    // Geometry term
    float NoL = clamp(dot(normal, L), 0.0, 1.0);
    float G1 = g(NoL, roughness);

    vec3 rayContrib = F * G1;

    vec4 sky = skyFromTexLod(L2, colortex6, 0);
    sky.rgb += sky.a * sunLight;

    vec3 reflectionColor = mix(reflectionColorScene, sky.rgb, pow(skylight, 3.0));
    float rayQuality = 35.0 * sqrt(luma(rayContrib));
    float lod = mix(3, 8.0, roughness);

    if (luma(rayContrib) > 0.02 && (rayQuality > 5.0))
    {

        // vec3 rtPos = RT5(mat3(gbufferModelView) * L, fragpos, noise, rayQuality);
        vec3 rtPos = RT_SIMPLE(mat3(gbufferModelView) * L, fragpos, roughness, noise, rayQuality);

#ifndef DISTANT_HORIZONS
        if (rtPos.z < 1.0)
#else
        if (rtPos.z < 1.1)
#endif
        {
            vec3 previousPosition = mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz + cameraPosition - previousCameraPosition;
            previousPosition = mat3(gbufferPreviousModelView) * previousPosition + gbufferPreviousModelView[3].xyz;
            previousPosition.xy = projMAD(gbufferPreviousProjection, previousPosition).xy / -previousPosition.z * 0.5 + 0.5;

            if (previousPosition.x > 0.0 && previousPosition.y > 0.0 && previousPosition.x < 1.0 && previousPosition.y < 1.0)
            {
                reflectionColor = textureLod(colortex4, previousPosition.xy, lod).rgb;
            }
        }

        indirectSpecular += reflectionColor * rayContrib;
        fresnelDiffuse += rayContrib;
    }

    // Average indirect specular and fresnel diffuse terms
    vec3 a = (indirectSpecular) + (specTerm * sunLight);
    vec3 b = (isMetal ? vec3(0.0) : (vec3(1.0) - fresnelDiffuse));

    //////////////////

    float fresnel = luma(fresnelDiffuse);
    float effectiveAlpha = scene.a * (1.0 - fresnel) + fresnel;

    a *= (inWater ? scene.a : 1.0);

    vec3 baseColorCorrected = scene.rgb * alpha0 * (1.0 - fresnel);
    if (!isMetal)
        albedo = vec3(1.0);
    vec3 outcolor = (a + b * baseColorCorrected.rgb) * albedo;
    float outalpha = effectiveAlpha;

    return vec4(outcolor, outalpha);
}

#ifdef DISTANT_HORIZONS

vec4 calculateSpecularLightingDH(

    vec3 normal,
    vec3 np3,
    float shading,
    float skylight,
    vec3 fragpos,
    vec4 scene,
    float alpha0)
{
    // Precomputed constants
    const float f0 = 0.02;

    bool inWater = (isEyeInWater == 1);
    const float refractiveIndexAir = 1.0;
    const float refractiveIndexWater = 1.333;
    float etaI = inWater ? refractiveIndexWater : refractiveIndexAir;

    // Precompute texture coordinates scaling
    vec2 texcoordScale = viewResolution;
    vec2 texcoord = gl_FragCoord.xy * texcoordScale;

    // Calculate the specular term using the GGX distribution with precomputed roughness
    vec3 specTerm = shading * GGX2(normal, -np3, lightPos, 0.025, vec3(f0));

    // Reflect the view direction over the normal
    vec3 L = reflect(np3, normal);
    vec3 L2 = L;

    // Calculate Fresnel term with precomputed f0
    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);

    #ifdef WATER
    // 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;
    }
    #endif

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

    // Calculate the ray's contribution to the final color
    float rayContrib = F * G1;
    vec3 reflectionColor = vec3(0.0);

    // Sample the sky color
    vec4 sky = skyFromTexLod(L2, colortex6, 0);
    sky.rgb += sky.a * sunLight;
    reflectionColor = mix(scene.rgb + sky.a * 0.1, sky.rgb, skylight);

    // Trace reflection ray
    vec3 rtPos = RT5(mat3(gbufferModelView) * L, fragpos, blueNoise(gl_FragCoord.xy), SSR_STEPS);
    // Reproject on previous frame

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

    // Calculate indirect specular reflection
    vec3 indirectSpecular = reflectionColor * rayContrib;
    float fresnelDiffuse = rayContrib;

    // Combine the specular and diffuse components
    vec3 a = indirectSpecular + specTerm * sunLight;
    float b = 1 - fresnelDiffuse;

    #ifdef WATER
    float fresnel = fresnelDiffuse;
    float effectiveAlpha = mix(scene.a, 1.0, fresnel);

    a *= inWater ? scene.a : 1.0;

    vec3 baseColorCorrected = scene.rgb * alpha0 * (1.0 - fresnel);
    vec3 outcolor = a + b * baseColorCorrected;
    float outalpha = effectiveAlpha;

    return vec4(outcolor, outalpha);
    #else
    return encodeTwoVec3(a, vec3(b));
    #endif
}
#endif
vec4 reflections(float skyLight, vec3 normal, vec3 fragpos, vec4 materialProps, vec4 outcolor, float shading, vec3 albedo, float alpha0)
{

    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), f0, porosity, fragpos, outcolor, alpha0);

    return reflection;
}

#ifdef DISTANT_HORIZONS

vec4 reflections_DH(float skyLight, vec3 normal, vec3 fragpos, vec4 materialProps, vec4 outcolor, float shading, vec3 albedo, float alpha0)
{
    vec3 p3 = mat3(gbufferModelViewInverse) * fragpos;
    vec4 reflection = calculateSpecularLightingDH(normal, normalize(p3), shading, skyLight, fragpos, outcolor, alpha0);

    return reflection;
}
#endif
