#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"
#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) {
    // Sample base color and tint.
    vec4 albedo = parameters.sampledColour * vec4(parameters.tinting.rgb, 1.0);
    
    // Discard fully transparent pixels immediately.
    if (albedo.a < 0.1) discard;

    // Reconstruct screen and view space coordinates.
    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 viewPos = viewPos4.xyz / viewPos4.w; 
    
    // Compute exact world position.
    vec3 relativeWorldPos = (vxModelViewInv * vec4(viewPos, 1.0)).xyz;
    vec3 worldPos = relativeWorldPos + cameraPosition;

    // Reconstruct block normal from Voxy face data.
    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;

    // Calculate sun direction in world space.
    vec3 rawSunDir = normalize(sunPosition);
    vec3 worldSunDir = (vxModelViewInv * vec4(rawSunDir, 0.0)).xyz;
    vec3 l = worldSunDir.y > 0.0 ? rawSunDir : -rawSunDir;
    float NdotL = max(dot(n, l), 0.0);

    // Identify basic plant materials for lighting tweaks.
    bool is_plant = (abs(material_id - 10002.0) < 0.5 || abs(material_id - 10003.0) < 0.5 || abs(material_id - 10004.0) < 0.5);
    bool is_leaf = (abs(material_id - 10005.0) < 0.5);

    // Force maximum light dot product for plants.
    #if PLANT_SHADING == 0 || PLANT_SHADING == 1
    if (is_plant) { NdotL = 1.0; }
    #endif

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

    // Retrieve dynamic light colors from the sky.
    vec3 lightSunColor, lightAmbColor;
    float currentShadowOpacity; 
    getLightColors(worldSunDir, lightSunColor, lightAmbColor, currentShadowOpacity);
    
    // Dim lighting globally during rain.
    float lightDimmer = mix(1.0, float(RAIN_LIGHT_DIMMER), rainStrength);
    lightSunColor *= lightDimmer;
    lightAmbColor *= lightDimmer;

    float blockLight = lmcoord.x;
    float skyLight   = lmcoord.y;

    // Calculate shadow mapping.
    float shadow = 1.0;
    bool checkShadow = (NdotL > 0.0) || is_leaf; 
    
    if (checkShadow) {
        vec4 worldPosShadow = vec4(relativeWorldPos, 1.0);
        
        // Shift shadow test position for leaves to reduce acne.
        if (is_leaf) worldPosShadow.xyz += worldSunDir * 0.9;

        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;
            float safeNdotL = is_leaf ? max(NdotL, 0.2) : NdotL;
            shadow = getHardShadow(shadowtex0, shadowSpacePos.xyz, safeNdotL, distortFactor);
        }
    }
    
    // Combine direct sunlight and shadows.
    float safeDirectLight = is_leaf ? max(NdotL, 0.0) : NdotL;
    float totalShadow = safeDirectLight * shadow;
    float lightIntensity = mix(1.0 - currentShadowOpacity, 1.0, totalShadow);
    
    // Compute sunlight, ambient light, and block light.
    float skyLightCurve = pow(skyLight, float(CAVE_LIGHT_FALLOFF));
    vec3 sunlightComp = linearAlbedo * lightSunColor * lightIntensity * skyLightCurve;
    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 torchColor = vec3(TORCH_R, TORCH_G, TORCH_B) * TORCH_INTENSITY;
    vec3 torchIllumination = linearAlbedo * torchColor * ((blockLight * blockLight) * sqrt(blockLight));
    
    // Sum all light components.
    vec3 finalColor = sunlightComp + skyAmbientComp + caveAmbientComp + torchIllumination;

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

    // Compute fog factors.
    float distance = length(viewPos);
    vec3 worldViewDir = normalize((vxModelViewInv * vec4(normalize(viewPos), 0.0)).xyz);
    vec3 finalFogColor;
    float finalFogFactor;
    float borderFog;
    float voxyFar = 32000.0;

    // Apply global fog.
    applyFog(distance, worldPos, worldViewDir, worldSunDir, skyLightCurve, rainStrength, voxyFar, lightSunColor, finalFogColor, finalFogFactor, borderFog);
    finalColor = mix(finalFogColor, finalColor, finalFogFactor);
    
    // Apply stylistic global 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));

    // Pack light data for auxiliary buffers.
    float lSun = clamp(lightIntensity * skyLightCurve, 0.0, 1.0);
    float lBlock = clamp(blockLight, 0.0, 1.0);
    float packedLight = (floor(lBlock * 15.0) + floor(lSun * 15.0) * 16.0) / 255.0;
    
    // Write outputs to Voxy buffers.
    voxyOut0 = vec4(finalColor, albedo.a);
    voxyOut1 = vec4(n * 0.5 + 0.5, packedLight);
    voxyOut2 = vec4(0.0, 0.0, 0.0, 0.0);
    voxyOut3 = vec4(is_plant ? 1.0 : 0.0, is_leaf ? 1.0 : 0.0, 0.0, 1.0);
}