#define VOXY_PROGRAM
#define VOXY_HAS_SSBO
#define VOXY_PATCH

// --- GLSL Compatibility for Voxy's modern environment ---
#define texture2D texture
#define texture2DLod textureLod

// --- Map OptiFine variables to Voxy matrices ---
#define gbufferProjection vxProj
#define gbufferProjectionInverse vxProjInv
#define gbufferModelView vxModelView
#define gbufferModelViewInverse vxModelViewInv

#include "/lib/settings.glsl"

// Disable shore foam on Voxy to prevent artifacts on distant chunk borders.
#undef WATER_SHORE
#define WATER_SHORE 0

#include "/lib/water.glsl"
#include "/lib/sky.glsl"
#include "/lib/shadow_filter.glsl"
#include "/lib/fog.glsl"

layout(location = 0) out vec4 voxyOut0;
layout(location = 1) out vec4 voxyOut1;
layout(location = 2) out vec4 voxyOut2;
layout(location = 3) out vec4 voxyOut3;

void voxy_emitFragment(VoxyFragmentParameters parameters) {
    vec4 albedo = parameters.sampledColour * vec4(parameters.tinting.rgb, 1.0);
    
    // Discard transparent pixels immediately.
    if (albedo.a < 0.1) discard;

    vec3 screenPos = vec3(gl_FragCoord.xy / vec2(viewWidth, viewHeight), gl_FragCoord.z);
    vec3 ndcPos = screenPos * 2.0 - 1.0;
    vec4 viewPos4 = vxProjInv * vec4(ndcPos, 1.0);
    vec3 localViewPos = viewPos4.xyz / viewPos4.w;

    vec3 relativeWorldPos = (vxModelViewInv * vec4(localViewPos, 1.0)).xyz;
    vec3 worldPos = relativeWorldPos + cameraPosition;

    // Reconstruct basic normal.
    vec3 normal = vec3(0.0);
    switch (uint(parameters.face) >> 1u) {
        case 0u: normal = vxModelView[1].xyz; break;
        case 1u: normal = vxModelView[2].xyz; break;
        case 2u: normal = vxModelView[0].xyz; break;
    }
    if ((parameters.face & 1u) == 0u) normal = -normal;
    vec3 n = normalize(normal);

    float material_id = float(parameters.customId);
    vec2 lmcoord = parameters.lightMap;
    vec3 outputNormal = n;
    
    // Default PBR and SSR variables.
    float smoothness = 0.0;
    float reflective_strength = 0.0;
    float is_water = 0.0;
    float f0 = 0.0;
    float sparklesOut = 0.0;

    vec3 rawSunDir = normalize(sunPosition);
    vec3 worldSunDir = (vxModelViewInv * vec4(rawSunDir, 0.0)).xyz;

    // ========================================================================
    // WATER MATERIAL EXECUTION
    // ========================================================================
    if (abs(material_id - 10006.0) < 0.5) {
        is_water = 1.0;
        smoothness = 1.0;
        
        // Face 1 is strictly the TOP face. This prevents rendering sparkles on vertical LOD skirts.
        bool is_top_face = (parameters.face == 1u);

        if (is_top_face) {
            vec2 screenUV = gl_FragCoord.xy / vec2(viewWidth, viewHeight);
            vec3 viewDir = normalize(localViewPos);
            float fresnel = 0.0;

            // Force perfectly flat normals and tangents to eliminate chunk border lighting seams.
            vec3 worldNormal = vec3(0.0, 1.0, 0.0);
            vec3 flatViewNormal = normalize(mat3(vxModelView) * worldNormal);
            outputNormal = flatViewNormal; 
            
            vec3 worldTangent = vec3(1.0, 0.0, 0.0);
            vec3 viewTangent = normalize(mat3(vxModelView) * worldTangent);
            vec4 flatTangent = vec4(viewTangent, -1.0); 

            // Calculate perfect ray direction to avoid depth buffer quantization.
            vec3 ndcPos = vec3(screenUV, 1.0) * 2.0 - 1.0;
            vec4 farPos = vxProjInv * vec4(ndcPos.xy, 1.0, 1.0);
            vec3 rayViewDir = normalize(farPos.xyz / farPos.w);
            vec3 rayWorldDir = normalize((vxModelViewInv * vec4(rayViewDir, 0.0)).xyz);
            
            // Snap approximate Y to the nearest 1/16th block for a stable continuous plane.
            float stableWorldY = floor((relativeWorldPos.y + cameraPosition.y) * 16.0 + 0.5) / 16.0;
            
            // Intersect ray with the horizontal plane to get flawless coordinates.
            float t = (stableWorldY - cameraPosition.y) / min(rayWorldDir.y, -0.00001);
            vec3 perfectRelativePos = rayWorldDir * t;

            // Apply precision fix using the mathematically perfect position.
            vec3 preciseWorldPos = perfectRelativePos;
            preciseWorldPos.xz += mod(cameraPosition.xz, 100000.0);

            applyWaterMaterial(
                albedo, outputNormal, fresnel, sparklesOut,
                water, depthtex1, screenUV, gl_FragCoord.z, 
                preciseWorldPos, flatViewNormal, flatTangent, viewDir, worldSunDir,
                frameTimeCounter, near, far
            );
            reflective_strength = fresnel;
        } else {
            // Apply flat base color to the vertical skirt walls to hide holes cleanly.
            albedo.rgb = vec3(WATER_R, WATER_G, WATER_B) * 0.5;
            albedo.a = WATER_A;
        }
    }

    // ========================================================================
    // GLOBAL LIGHTING AND DIRECTION
    // ========================================================================
    vec3 l = worldSunDir.y > 0.0 ? rawSunDir : -rawSunDir;
    float NdotL = max(dot(outputNormal, l), 0.0);

    // Convert to linear space to match main terrain lighting.
    vec3 linearAlbedo = pow(albedo.rgb, vec3(2.2)) * 0.35;

    vec3 lightSunColor, lightAmbColor;
    float currentShadowOpacity;
    getLightColors(worldSunDir, lightSunColor, lightAmbColor, currentShadowOpacity);
    float lightDimmer = mix(1.0, float(RAIN_LIGHT_DIMMER), rainStrength);
    lightSunColor *= lightDimmer;
    lightAmbColor *= lightDimmer;
	
    float blockLight = lmcoord.x;
    float skyLight   = lmcoord.y;

    float shadow = 1.0;
    if (NdotL > 0.0) {
        vec4 worldPosShadow = vec4(relativeWorldPos, 1.0);
        vec4 shadowSpacePos = shadowModelView * worldPosShadow;
        shadowSpacePos = shadowProjection * shadowSpacePos;
        shadowSpacePos /= shadowSpacePos.w;
        
        float dist = length(shadowSpacePos.xy);
        float distortFactor = mix(1.0, dist, SHADOW_DISTORTION);
        shadowSpacePos.xy /= distortFactor;
        shadowSpacePos.z /= 3.0;

        if (abs(shadowSpacePos.x) < 0.99 && abs(shadowSpacePos.y) < 0.99) {
            shadowSpacePos = shadowSpacePos * 0.5 + 0.5;
            shadow = getHardShadow(shadowtex0, shadowSpacePos.xyz, NdotL, distortFactor);
        }
    }
    
    float totalShadow = NdotL * shadow;
    float lightIntensity = mix(1.0 - currentShadowOpacity, 1.0, totalShadow);
    float skyLightCurve = pow(skyLight, float(CAVE_LIGHT_FALLOFF));
    float sunlightForce = lightIntensity * skyLightCurve;
    
    vec3 sunlightComp = linearAlbedo * lightSunColor * sunlightForce;
    vec3 skyAmbientComp = linearAlbedo * lightAmbColor * skyLightCurve;
    vec3 caveBaseColor = vec3(CAVE_FOG_R, CAVE_FOG_G, CAVE_FOG_B);
    vec3 caveAmbientComp = linearAlbedo * caveBaseColor * float(MIN_CAVE_LIGHT) * (1.0 - skyLightCurve);
    vec3 ambientComp = skyAmbientComp + caveAmbientComp;
    vec3 torchColor = vec3(TORCH_R, TORCH_G, TORCH_B) * TORCH_INTENSITY * pow(blockLight, 2.5);
    
    vec3 finalColor = sunlightComp + ambientComp + linearAlbedo * torchColor;

    // Convert back to sRGB space before applying fog.
    finalColor = pow(finalColor, vec3(1.0 / 2.2));

    // ========================================================================
    // FOG APPLICATION
    // ========================================================================
    float distance = length(localViewPos);
    vec3 worldViewDir = normalize((vxModelViewInv * vec4(normalize(localViewPos), 0.0)).xyz);
    
    vec3 finalFogColor;
    float finalFogFactor;
    float borderFog;
    float voxyFar = 32000.0;

    applyFog(distance, worldPos, worldViewDir, worldSunDir, skyLightCurve, rainStrength, voxyFar, lightSunColor, finalFogColor, finalFogFactor, borderFog);
    
    // Force water to become fully opaque in deep fog to hide the sky behind it.
    if (is_water > 0.5) {
        albedo.a = mix(1.0, albedo.a, finalFogFactor);
    }
    
    // Fade out SSR reflections in deep fog to prevent ghosting.
    reflective_strength *= (1.0 - finalFogFactor);

    if (borderFog < 0.001) discard;
    finalColor = mix(finalFogColor, finalColor, finalFogFactor);

    // Apply global stylistic hue shift.
    vec3 hueTint = vec3(HUE_SHIFT_R, HUE_SHIFT_G, HUE_SHIFT_B);
    vec3 normalizedTint = hueTint / max(dot(hueTint, vec3(0.333)), 0.001);
    finalColor = mix(finalColor, finalColor * normalizedTint, float(HUE_SHIFT_INTENSITY));

    // ========================================================================
    // DATA PACKING AND EXPORT
    // ========================================================================
    float lSky = clamp(skyLightCurve, 0.0, 1.0);
    
    // Dampen normal map specifically for SSR stability.
    vec3 ssrNormal = normalize(mix(n, outputNormal, 0.2)); 

    float luma = dot(albedo.rgb, vec3(0.299, 0.587, 0.114));
    float luma_mask = smoothstep(float(BLOOM_COLOR_DETECTION) - 0.2, float(BLOOM_COLOR_DETECTION) + 0.2, luma);
    bool is_emissive = (abs(material_id - 10010.0) < 0.5);
    float emissive_flag = (is_emissive ? 1.0 : 0.0) * luma_mask;

    // Combine emission flag with water sparkles for the bloom pass.
    float final_bloom = max(emissive_flag, sparklesOut);

    /* DRAWBUFFERS:0123 */
    voxyOut0 = vec4(finalColor, albedo.a);
    voxyOut1 = vec4(ssrNormal * 0.5 + 0.5, lSky);
    voxyOut2 = vec4(smoothness, reflective_strength, is_water, f0);
    voxyOut3 = vec4(1.0, 0.0, final_bloom, 0.0);
}