// Stable temporally-filtered sunlight shadows shared by terrain, entities and block entities.
#ifndef NOISE_SHADOWS_GLSL
#define NOISE_SHADOWS_GLSL

#include "/lib/noise_common.glsl"
#include "/lib/noise_options.glsl"
#include "/lib/noise_shadow_space.glsl"

float noiseShadowFilterRotation(int frameIndex, vec2 stableCell) {
	// The colour TAA history is not a shadow denoiser: changing the PCF kernel
	// every frame creates multiple partially accumulated silhouettes. Keep one
	// deterministic, symmetric disk until shadows get their own visibility and
	// variance history. The parameters remain for a shared call signature.
	return 0.754877666;
}

vec3 noiseShadowTransmission(
	sampler2D shadowMapAll,
	sampler2D shadowMapOpaque,
	sampler2D shadowColorMap,
	vec2 uv,
	float receiverDepth
) {
	vec2 safeUv = clamp(uv, vec2(0.0005), vec2(0.9995));
	float allCasterDepth = texture(shadowMapAll, safeUv).r;
	if (receiverDepth <= allCasterDepth) return vec3(1.0);

	// shadowtex0 contains every caster while shadowtex1 contains only opaque
	// casters. A receiver between those two depths is behind transparent material.
	float opaqueCasterDepth = texture(shadowMapOpaque, safeUv).r;
	if (receiverDepth > opaqueCasterDepth) return vec3(0.0);

	vec4 casterColor = texture(shadowColorMap, safeUv);
	vec3 transmittedColor = clamp(casterColor.rgb, vec3(0.0), vec3(1.0));
	return mix(vec3(1.0), transmittedColor, clamp(casterColor.a, 0.0, 1.0));
}

vec3 noiseSunShadow(
	sampler2D shadowMapAll,
	sampler2D shadowMapOpaque,
	sampler2D shadowColorMap,
	mat4 lightProjection,
	mat4 lightModelView,
	mat4 viewToPlayer,
	vec3 playerPosition,
	vec3 viewNormal,
	vec3 viewLightDirection,
	int frameIndex
) {
	#if SHADOW_SAMPLES == 0 || defined(NETHER)
	return vec3(1.0);
	#else
	vec3 safeNormal = noiseSafeNormalize(viewNormal, vec3(0.0, 1.0, 0.0));
	vec3 safeLight = noiseSafeNormalize(viewLightDirection, vec3(0.0, 1.0, 0.0));
	float normalFacing = clamp(dot(safeNormal, safeLight), 0.0, 1.0);
	float receiverDistance = length(playerPosition);

	// A small normal-space receiver offset prevents acne without detaching the
	// contact edge. It grows gently with distance where the map has less precision.
	vec3 playerNormal = noiseSafeNormalize(mat3(viewToPlayer) * safeNormal, vec3(0.0, 1.0, 0.0));
	float normalOffset = (0.014 + receiverDistance * 0.00032) * mix(1.45, 0.65, normalFacing);
	vec3 biasedPlayerPosition = playerPosition + playerNormal * normalOffset;

	vec4 shadowClip = lightProjection * lightModelView * vec4(biasedPlayerPosition, 1.0);
	if (abs(shadowClip.w) < 1e-6) return vec3(1.0);
	vec3 shadowNdc = shadowClip.xyz / shadowClip.w;
	shadowNdc.xy = noiseWarpShadowPosition(shadowNdc.xy);
	shadowNdc.z *= NOISE_SHADOW_DEPTH_SCALE;
	vec3 shadowPosition = shadowNdc * 0.5 + 0.5;

	if (shadowPosition.z <= 0.0 || shadowPosition.z >= 1.0 ||
		any(lessThanEqual(shadowPosition.xy, vec2(0.0))) ||
		any(greaterThanEqual(shadowPosition.xy, vec2(1.0)))) return vec3(1.0);

	// The shadow depth range is compressed in noise_shadow_space.glsl, so only
	// a very small residual depth bias is necessary after the normal offset.
	float receiverBias = mix(0.00014, 0.000025, normalFacing);
	receiverBias += smoothstep(shadowDistance * 0.60, shadowDistance, receiverDistance) * 0.000030;
	float receiverDepth = shadowPosition.z - receiverBias;

	#if SHADOW_SAMPLES == 1
	return noiseShadowTransmission(shadowMapAll, shadowMapOpaque, shadowColorMap,
		shadowPosition.xy, receiverDepth);
	#else
	ivec2 mapDimensions = textureSize(shadowMapAll, 0);
	float shadowMapSize = float(max(min(mapDimensions.x, mapDimensions.y), 1));
	float texelSize = 1.0 / shadowMapSize;
	float centerDepth = texture(shadowMapAll,
		clamp(shadowPosition.xy, vec2(0.0005), vec2(0.9995))).r;
	float blockerCount = centerDepth < receiverDepth ? 1.0 : 0.0;
	// The pre-test only decides whether the real filter is needed. Spending eight
	// taps before a four- or twelve-tap low-quality filter erased much of the
	// profile's intended saving, while Ultra retains the robust eight-tap test.
	#if SHADOW_SAMPLES <= 4
	const int blockerSamples = 2;
	#elif SHADOW_SAMPLES <= 12
	const int blockerSamples = 4;
	#else
	const int blockerSamples = 8;
	#endif

	// A cheap stable pre-test preserves the early-out over fully lit regions.
	// It only decides whether filtering is needed; unlike the previous PCSS
	// estimate it never changes the radius of the penumbra.
	vec2 stableCell = floor(shadowPosition.xy * shadowMapSize * 0.125);
	float blockerRotation = noiseHash(stableCell) * 6.28318530718;
	float filterRotation = noiseShadowFilterRotation(frameIndex, stableCell);
	// Keep the authored softness compact enough that small grass and leaf
	// shadows retain a readable core instead of growing a bright halo.
	float filterRadius = 1.65 * float(SHADOW_SOFTNESS);
	float searchRadius = filterRadius * texelSize;
	for (int blockerIndex = 0; blockerIndex < blockerSamples; ++blockerIndex) {
		float indexValue = float(blockerIndex) + 0.5;
		float angle = indexValue * 2.39996322973 + blockerRotation;
		float radius = sqrt(indexValue / float(blockerSamples));
		vec2 searchOffset = vec2(cos(angle), sin(angle)) * radius * searchRadius;
		vec2 sampleUv = clamp(shadowPosition.xy + searchOffset, vec2(0.0005), vec2(0.9995));
		float sampleDepth = texture(shadowMapAll, sampleUv).r;
		if (sampleDepth < receiverDepth) {
			blockerCount += 1.0;
		}
	}
	if (blockerCount < 0.5) return vec3(1.0);

	// Opposite tap pairs cancel directional bias in every individual frame.
	// Per-pixel rotation turns the former coherent penumbra bands into fine
	// temporal noise that the history buffer can actually average.
	const int filterPairs = SHADOW_SAMPLES / 2;
	vec3 transmission = vec3(0.0);
	for (int pairIndex = 0; pairIndex < filterPairs; ++pairIndex) {
		float pairNumber = float(pairIndex) + 0.5;
		float radius = sqrt(pairNumber / float(filterPairs));
		float angle = pairNumber * 2.39996322973 + filterRotation;
		vec2 diskOffset = vec2(cos(angle), sin(angle)) * radius * filterRadius * texelSize;
		transmission += noiseShadowTransmission(shadowMapAll, shadowMapOpaque, shadowColorMap,
			shadowPosition.xy + diskOffset, receiverDepth);
		transmission += noiseShadowTransmission(shadowMapAll, shadowMapOpaque, shadowColorMap,
			shadowPosition.xy - diskOffset, receiverDepth);
	}
	transmission /= float(filterPairs * 2);

	float mapEdge = min(min(shadowPosition.x, 1.0 - shadowPosition.x),
		min(shadowPosition.y, 1.0 - shadowPosition.y));
	float edgeFade = smoothstep(0.007, 0.040, mapEdge);
	float distanceFade = 1.0 - smoothstep(shadowDistance * 0.82, shadowDistance, receiverDistance);
	return mix(vec3(1.0), transmission, edgeFade * distanceFade);
	#endif
	#endif
}

#endif
