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

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
}

// 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;
vec3 rotateAroundXAxis(vec3 v)
{

    float c = 0.999962; // cos(0.5 degrees)
    float s = 0.0087;   // sin(0.5 degrees)

    return vec3(
        v.x,
        c * v.y - s * v.z,
        s * v.y + c * v.z);
}

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

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

    // Precompute texture coordinates scaling

    // 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 = SampleVNDF(np3, normal);¨
    float noise = mix(noise_standard(gl_FragCoord.xy), 0.5, 0.0);
    vec3 L = rotateAroundXAxis(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);

#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);

#if SSR_STEPS > 0
    if (isEyeInWater != 1)
    {
        // Trace reflection ray for screen-space reflections
    #ifndef DISTANT_HORIZONS
        vec3 rtPos = RT_SIMPLE(mat3(gbufferModelView) * L, fragpos, noise, SSR_STEPS);

        if (rtPos.z < 1.0)
    #else
        vec3 rtPos = RT5LQ(mat3(gbufferModelView) * L, fragpos, noise, SSR_STEPS);

        if (rtPos.z < 1.1)
    #endif
        {
            vec3 previousPosition = mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz + cameraPosition - previousCameraPosition;
            previousPosition = mat3(gbufferPreviousModelView) * previousPosition + gbufferPreviousModelView[3].xyz;
            vec2 previousUV = projMAD(gbufferPreviousProjection, previousPosition).xy / -previousPosition.z * 0.5 + 0.5;
            vec2 texcoord = gl_FragCoord.xy * viewResolution;

            vec2 delta = texcoord - rtPos.xy;
            if (dot(delta, delta) > 0.0001)
            {
                if (all(greaterThan(previousUV, vec2(0.0))) && all(lessThan(previousUV, vec2(1.0))))
                {
                    reflectionColor = texture(colortex4, previousUV).rgb;
                }
            }
        }
    }
#endif

    // 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;

    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;
    // outcolor = vec3(1 - L.y * 100);
    // outalpha = 1;
    return vec4(outcolor, outalpha);
}

vec4 reflections(float skyLight, vec3 normal, vec3 fragpos, vec4 materialProps, vec4 outcolor, float shading, vec3 albedo, float alpha0)
{
    vec3 p3 = mat3(gbufferModelViewInverse) * fragpos;
    vec4 reflection = calculateSpecularLightingLQ(normal, normalize(p3), shading, skyLight, fragpos, outcolor, alpha0);
    return reflection;
}

//////////
