#ifndef MODERNPSX_VOXY_MAIN_GLSL
#define MODERNPSX_VOXY_MAIN_GLSL

#include "/lib/settings.glsl"

uniform mat4 vxProjInv;
uniform int vxRenderDistance;

// Every ordinary Iris uniform listed in voxy.json is injected by Voxy in its
// ShaderUniformBindings block. Only Voxy's own projection/distance uniforms
// need declarations here; redeclaring the JSON entries makes NVIDIA reject
// the fragment patch and forces Voxy back to its unshaded fallback pipeline.

vec3 voxyProjectAndDivide(mat4 matrix, vec3 position) {
    vec4 projected = matrix * vec4(position, 1.0);
    return projected.xyz / max(abs(projected.w), 0.00001);
}

vec3 voxyPlayerPosition() {
    vec2 screenPosition = gl_FragCoord.xy /
        max(vec2(viewWidth, viewHeight), vec2(1.0));
    vec3 ndcPosition = vec3(
        screenPosition * 2.0 - 1.0,
        gl_FragCoord.z * 2.0 - 1.0
    );
    vec3 viewPosition = voxyProjectAndDivide(vxProjInv, ndcPosition);
    return (gbufferModelViewInverse * vec4(viewPosition, 1.0)).xyz;
}

vec3 voxyFaceNormal(uint face) {
    // Voxy encodes the axis in face >> 1 and the sign in the low bit.
    vec3 axis = vec3(
        uint((face >> 1) == 2u),
        uint((face >> 1) == 0u),
        uint((face >> 1) == 1u)
    );
    return axis * (float(int(face & 1u)) * 2.0 - 1.0);
}

vec3 voxyWorldSunDirection() {
    return normalize(mat3(gbufferModelViewInverse) * sunPosition);
}

vec3 voxyWorldShadowLight() {
    return normalize(mat3(gbufferModelViewInverse) * shadowLightPosition);
}

float voxyDaylightFactor(vec3 sunDirection) {
    return smoothstep(-0.08, 0.12, sunDirection.y);
}

float voxyNightExposure() {
    return clamp(
        NIGHT_BRIGHTNESS * mix(0.72, 1.10, screenBrightness),
        0.20,
        0.95
    );
}

vec3 voxyApplyLighting(vec3 albedo, vec3 normal, vec2 lightCoord) {
    vec3 sunDirection = voxyWorldSunDirection();
    vec3 shadowLight = voxyWorldShadowLight();
    float daylight = voxyDaylightFactor(sunDirection);
    float skyLight = clamp(lightCoord.y, 0.0, 1.0);
    float blockLight = clamp(lightCoord.x, 0.0, 1.0);
    float sunDiffuse = max(dot(normal, sunDirection), 0.0);
    float moonDiffuse = max(dot(normal, shadowLight), 0.0);

    vec3 dayLight =
        vec3(0.50, 0.54, 0.61) +
        vec3(0.31, 0.29, 0.24) * skyLight +
        vec3(0.30, 0.26, 0.20) * sunDiffuse *
        (1.0 - rainStrength * 0.62);
    vec3 nightLight =
        vec3(0.042, 0.052, 0.070) +
        vec3(0.070, 0.088, 0.118) * skyLight +
        vec3(0.090, 0.112, 0.148) * moonDiffuse *
        (1.0 - rainStrength * 0.42);
    nightLight *= voxyNightExposure();

    vec3 localLight = vec3(1.00, 0.55, 0.24) *
        pow(blockLight, 1.45) * 0.72 * BLOCKLIGHT_STRENGTH;
    return albedo * (mix(nightLight, dayLight, daylight) + localLight) *
        LIGHTING_STRENGTH;
}

vec3 voxyAtmosphereColor(
    vec3 viewDirection,
    float horizonWeight,
    vec3 sunDirection,
    vec3 shadowLight,
    float daylight
) {
    float sunHeight = clamp(sunDirection.y, 0.0, 1.0);
    vec3 dayHorizon = mix(fogColor, skyColor, 0.18);
    dayHorizon = mix(dayHorizon, vec3(0.66, 0.74, 0.82), 0.16);
    dayHorizon *= mix(
        vec3(0.92, 0.95, 1.02),
        vec3(1.04, 1.00, 0.92),
        sunHeight
    );
    vec3 nightHorizon = mix(
        vec3(0.064, 0.084, 0.108),
        fogColor * 0.16,
        0.14
    ) * mix(0.78, 1.04, voxyNightExposure());
    vec3 atmosphere = mix(nightHorizon, dayHorizon, daylight);
    float luminance = dot(atmosphere, vec3(0.2126, 0.7152, 0.0722));
    atmosphere = mix(atmosphere, vec3(luminance), rainStrength * 0.58);

    float sunFacing = max(dot(viewDirection, sunDirection), 0.0);
    sunFacing *= sunFacing;
    float moonFacing = max(dot(viewDirection, shadowLight), 0.0);
    moonFacing = moonFacing * moonFacing * moonFacing;
    atmosphere += vec3(0.070, 0.055, 0.038) *
        sunFacing * sunFacing * daylight *
        (1.0 - rainStrength * 0.82) * horizonWeight;
    atmosphere += vec3(0.040, 0.060, 0.090) *
        moonFacing * (1.0 - daylight) *
        NIGHT_HAZE_STRENGTH * horizonWeight;
    return atmosphere;
}

vec3 voxyApplyAtmosphere(
    vec3 color,
    vec3 playerPosition,
    vec3 normal,
    vec2 lightCoord
) {
#if !defined(DISTANCE_FOG) && !defined(ATMOSPHERIC_HAZE)
    return color;
#else
    float viewDistance = length(playerPosition);
    vec3 viewDirection = playerPosition / max(viewDistance, 0.001);
    float horizonWeight =
        1.0 - smoothstep(0.08, 0.66, abs(viewDirection.y));
    vec3 sunDirection = voxyWorldSunDirection();
    vec3 shadowLight = voxyWorldShadowLight();
    float daylight = voxyDaylightFactor(sunDirection);

    // Voxy reports its render radius in chunks; convert it to blocks before
    // matching the existing Distant Horizons fog response.
    float fullDistance = max(float(vxRenderDistance) * 16.0, far);
    float hazeAmount = 0.0;
#ifdef ATMOSPHERIC_HAZE
    float hazeDistanceScale = mix(far, fullDistance, 0.18);
    float hazeDistance = sqrt(clamp(
        viewDistance / max(hazeDistanceScale, 1.0),
        0.0,
        1.0
    ));
    float activeHazeStrength = mix(
        NIGHT_HAZE_STRENGTH,
        DAY_HAZE_STRENGTH,
        daylight
    );
    hazeAmount = smoothstep(0.015, 0.92, hazeDistance) *
        activeHazeStrength *
        mix(0.52, 1.0, horizonWeight) *
        mix(0.92, 1.08, daylight) *
        mix(1.0, 1.24, rainStrength);
    hazeAmount = clamp(hazeAmount, 0.0, 0.84);

    float lowLocalSky = 1.0 - smoothstep(
        0.04,
        0.34,
        clamp(lightCoord.y, 0.0, 1.0)
    );
    float lowLocalBlock = 1.0 - smoothstep(
        0.02,
        0.22,
        clamp(lightCoord.x, 0.0, 1.0)
    );
    float downwardSurface = smoothstep(0.05, 0.72, -normal.y);
    float litLuminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
    float darkSurface = 1.0 - smoothstep(0.035, 0.16, litLuminance);
    float shelteredSurface = lowLocalSky * lowLocalBlock *
        max(downwardSurface, darkSurface * 0.65);
    hazeAmount *= mix(1.0, 0.38, shelteredSurface);
#endif

    float fogAmount = 0.0;
#ifdef DISTANCE_FOG
    fogAmount = clamp(smoothstep(
        FOG_START,
        1.015,
        viewDistance / max(fullDistance, 1.0)
    ), 0.0, 1.0);
#endif
    float atmosphereAmount = hazeAmount + fogAmount - hazeAmount * fogAmount;
    return mix(
        color,
        voxyAtmosphereColor(
            viewDirection,
            horizonWeight,
            sunDirection,
            shadowLight,
            daylight
        ),
        atmosphereAmount
    );
#endif
}

#endif
