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

#include "/lib/specular.glsl"


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;
    bool waterMask = (iswater > 0.99);

    // 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);
    }
    //  L2.y = mix(L.y, -0.5, clamp(-cosThetaI, 0, 1));
    // L2 = normalize(L2);
    // 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 = mix(sky.rgb, vec3(sky.a * sunLight), clamp(sky.a, 0, 1));

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

    if (luma(rayContrib) > 0.02 && (rayQuality > 5.0))
    {
#ifndef DISTANT_HORIZONS

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

        vec3 rtPos = RT_SIMPLE_DH(mat3(gbufferModelView) * L, fragpos, roughness, noise, rayQuality);
#endif

#ifndef DISTANT_HORIZONS
        if (rtPos.z < 1.0)
#else
        if (rtPos.z < 1.0 && rtPos.z > 0)
#endif
        {
            vec3 previousPosition;
            vec2 previousUV;

#ifndef DISTANT_HORIZONS
            previousPosition = mat3(gbufferModelViewInverse) * toScreenSpace(rtPos) + gbufferModelViewInverse[3].xyz + cameraPosition - previousCameraPosition;
            previousPosition = mat3(gbufferPreviousModelView) * previousPosition + gbufferPreviousModelView[3].xyz;
            previousUV = projMAD(gbufferPreviousProjection, previousPosition).xy / -previousPosition.z * 0.5 + 0.5;

#else

            previousPosition = mat3(gbufferModelViewInverse) * toScreenSpaceDH(rtPos) + gbufferModelViewInverse[3].xyz + cameraPosition - previousCameraPosition;
            previousPosition = mat3(gbufferPreviousModelView) * previousPosition + gbufferPreviousModelView[3].xyz;
            previousUV = projMAD(dhPreviousProjection, previousPosition).xy / -previousPosition.z * 0.5 + 0.5;
#endif
            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 = textureLod(colortex4, previousUV.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);
}

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