#ifndef FOXY_CONTRACT_WATER_GLSL
#define FOXY_CONTRACT_WATER_GLSL

#include "/lib/contracts/endpoint.glsl"
#include "/lib/contracts/images.glsl"
#include "/lib/contracts/water_surface.glsl"

struct WaterProducerPacket {
	float frontRayDistance;
	float frontViewDistance;
	float backRayDistance;
	float owner;
};

WaterProducerPacket WaterProducerInvalid() {
	WaterProducerPacket packet;
	packet.frontRayDistance = FOXY_ENDPOINT_INFINITY;
	packet.frontViewDistance = FOXY_ENDPOINT_INFINITY;
	packet.backRayDistance = FOXY_ENDPOINT_INFINITY;
	packet.owner = FOXY_ENDPOINT_OWNER_NONE;
	return packet;
}

WaterProducerPacket WaterProducerUnpack(const in vec4 encoded) {
	WaterProducerPacket packet;
	packet.frontRayDistance = encoded.x;
	packet.frontViewDistance = encoded.y;
	packet.backRayDistance = encoded.z;
	packet.owner = encoded.w;
	return packet;
}

vec4 WaterProducerPack(
	const in float frontRayDistance,
	const in float frontViewDistance,
	const in float backRayDistance,
	const in float owner
) {
	return vec4(
		max(frontRayDistance, 0.0),
		max(frontViewDistance, 0.0),
		max(backRayDistance, 0.0),
		owner
	);
}

#if defined(FOXY_IMAGE_MAIN_WATER_PRODUCER_CURRENT)
WaterProducerPacket ResolveWaterProducer(const in vec2 sampleUv) {
	WaterProducerPacket mainPacket = WaterProducerUnpack(LoadMainWaterProducer(sampleUv));
	float mainValid = step(0.5, mainPacket.owner);
	if (mainValid > 0.5) {
		return mainPacket;
	}
#if defined(VOXY)
	float voxyFrontRaw = BackendLodFrontRaw(sampleUv);
	if (BackendHasLodSurface(voxyFrontRaw)) {
		// Use the same Voxy raster coordinate for depth selection and inverse
		// projection.  Logical view UV is intentionally not substituted here:
		// it is offset from Voxy's depth grid while temporal jitter is active.
		vec2 voxyViewUv = BackendVoxyDepthViewUv(sampleUv);
		vec3 voxyFrontView = BackendLodViewPosition(voxyViewUv, voxyFrontRaw);
		WaterProducerPacket voxyPacket;
		voxyPacket.frontRayDistance = length(voxyFrontView);
		voxyPacket.frontViewDistance = max(-voxyFrontView.z, 0.0);
		voxyPacket.backRayDistance = voxyPacket.frontRayDistance;
		voxyPacket.owner = FOXY_ENDPOINT_OWNER_VOXY_WATER;
		float voxyBackRaw = BackendLodSolidRaw(sampleUv);
		if (BackendHasLodSurface(voxyBackRaw)) {
			voxyPacket.backRayDistance = length(BackendLodViewPosition(voxyViewUv, voxyBackRaw));
		}
		return voxyPacket;
	}
#elif defined(FOXY_IMAGE_LOD_WATER_PRODUCER_CURRENT)
	WaterProducerPacket lodPacket = WaterProducerUnpack(LoadLodWaterProducer(sampleUv));
	float lodValid = step(0.5, lodPacket.owner);
	if (lodValid > 0.5) {
		return lodPacket;
	}
#endif
	return WaterProducerInvalid();
}
#endif

float WaterProducerIsValid(const in WaterProducerPacket packet) {
	return step(0.5, packet.owner);
}

float WaterProducerVisibleBeforeOpaque(
	const in WaterProducerPacket packet,
	const in Endpoint opaqueEndpoint
) {
	float valid = WaterProducerIsValid(packet);
	float inFront = 1.0 - step(opaqueEndpoint.rayDistance, packet.frontRayDistance);
	return valid * inFront;
}

vec3 WaterProducerViewPosition(
	const in vec2 viewUv,
	const in vec2 sampleUv,
	const in WaterProducerPacket packet,
	const in mat4 mainProjectionInverse,
	const in mat4 mainModelView
) {
	if (WaterProducerIsValid(packet) < 0.5) {
		return vec3(0.0);
	}
	vec3 viewRay;
	#if defined(VOXY)
	if (abs(packet.owner - FOXY_ENDPOINT_OWNER_VOXY_WATER) < 0.5) {
		// Keep the position on the exact Voxy raster ray which produced the
		// translucent depth. Replacing this direction with the main camera ray
		// changes world position every jitter phase and destabilizes both waves
		// and reflection origins.
		vec2 voxyViewUv = BackendVoxyDepthViewUv(sampleUv);
		vec3 voxyViewPosition = BackendLodViewRay(voxyViewUv) * max(packet.frontRayDistance, 0.0);
		return BackendLodViewToMainView(voxyViewPosition, mainModelView);
	} else
	#endif
	if (abs(packet.owner - FOXY_ENDPOINT_OWNER_LOD_WATER) < 0.5) {
		viewRay = BackendLodViewRay(viewUv);
	} else {
		viewRay = normalize(EndpointMainViewPosition(viewUv, 1.0, mainProjectionInverse));
	}
	return viewRay * max(packet.frontRayDistance, 0.0);
}

vec2 ProducerScreenUv(const in vec2 viewportSize) {
	return clamp(gl_FragCoord.xy / max(viewportSize, vec2(1.0)), vec2(0.0), vec2(0.999999));
}

#endif
