static const int kProbeCount = 8;
static const int kMetadataIndex = 8;

float2 probeUvForIndex(int index)
{
    if (index == 0)
        return float2(0.18, 0.28);
    if (index == 1)
        return float2(0.39, 0.28);
    if (index == 2)
        return float2(0.61, 0.28);
    if (index == 3)
        return float2(0.82, 0.28);
    if (index == 4)
        return float2(0.18, 0.72);
    if (index == 5)
        return float2(0.39, 0.72);
    if (index == 6)
        return float2(0.61, 0.72);
    return float2(0.82, 0.72);
}

float2 cellCenterPixelForIndex(int index)
{
    return float2(1.0 + float(index) * 3.0, 1.0);
}

float2 cellCenterUvForIndex(ShaderContext context, int index)
{
    return (cellCenterPixelForIndex(index) + 0.5) / context.outputResolution;
}

bool pixelIsInsideCell(float2 pixelCoord, int index)
{
    float minX = float(index) * 3.0;
    float maxX = minX + 3.0;
    return pixelCoord.x >= minX && pixelCoord.x < maxX && pixelCoord.y >= 0.0 && pixelCoord.y < 3.0;
}

float4 readStoredCell(ShaderContext context, int index)
{
    if (context.feedbackAvailable <= 0)
        return float4(0.0, 0.0, 0.0, 0.0);
    return sampleFeedback(cellCenterUvForIndex(context, index));
}

bool shouldRefreshStoredData(ShaderContext context)
{
    if (context.feedbackAvailable <= 0)
        return true;

    float4 metadata = readStoredCell(context, kMetadataIndex);
    float previousRefreshBucket = metadata.r;
    float previousTriggerCount = metadata.g;
    float refreshInterval = max(refreshSeconds, 0.001);
    float currentRefreshBucket = floor(context.time / refreshInterval);
    float currentTriggerCount = float(refresh);

    return currentRefreshBucket > previousRefreshBucket + 0.5 || currentTriggerCount > previousTriggerCount + 0.5;
}

float4 metadataValueForFrame(ShaderContext context, bool refreshNow)
{
    float refreshInterval = max(refreshSeconds, 0.001);
    float currentRefreshBucket = floor(context.time / refreshInterval);
    float currentTriggerCount = float(refresh);

    if (!refreshNow && context.feedbackAvailable > 0)
        return readStoredCell(context, kMetadataIndex);

    return float4(currentRefreshBucket, currentTriggerCount, refreshTime, 1.0);
}

float4 storedProbeValueForFrame(ShaderContext context, int index, bool refreshNow)
{
    float3 liveColor = sampleLayerInput(probeUvForIndex(index)).rgb;
    if (refreshNow || context.feedbackAvailable <= 0)
        return float4(liveColor, 1.0);
    return readStoredCell(context, index);
}

float4 storeProbeData(ShaderContext context)
{
    // Reserve nine 3x3 texel cells along the top edge of the feedback surface:
    // eight cells for visible probe colors and one hidden metadata cell that
    // tracks the timed refresh bucket and last trigger count.
    float2 pixelCoord = floor(context.uv * context.outputResolution);
    bool refreshNow = shouldRefreshStoredData(context);

    for (int index = 0; index < kProbeCount; ++index)
    {
        if (pixelIsInsideCell(pixelCoord, index))
            return storedProbeValueForFrame(context, index, refreshNow);
    }

    if (pixelIsInsideCell(pixelCoord, kMetadataIndex))
        return metadataValueForFrame(context, refreshNow);

    return float4(0.0, 0.0, 0.0, 1.0);
}

float rectMask(float2 uv, float2 minUv, float2 maxUv)
{
    if (uv.x < minUv.x || uv.x > maxUv.x)
        return 0.0;
    if (uv.y < minUv.y || uv.y > maxUv.y)
        return 0.0;
    return 1.0;
}

float borderMask(float2 uv, float2 minUv, float2 maxUv, float thickness)
{
    float outer = rectMask(uv, minUv, maxUv);
    float inner = rectMask(uv, minUv + thickness, maxUv - thickness);
    return saturate(outer - inner);
}

float4 displayProbeData(ShaderContext context)
{
    float3 baseColor = sampleLayerInput(context.uv).rgb;
    float3 swatchColor = baseColor;
    float swatchMask = 0.0;

    float2 panelOrigin = float2(0.03, 0.04);
    float2 gap = float2(swatchSize.x + 0.012, swatchSize.y + 0.012);
    float borderThickness = min(swatchSize.x, swatchSize.y) * 0.08;

    for (int index = 0; index < kProbeCount; ++index)
    {
        int column = index % 4;
        int row = index / 4;
        float2 swatchMin = panelOrigin + float2(float(column) * gap.x, float(row) * gap.y);
        float2 swatchMax = swatchMin + swatchSize;
        float3 storedColor = sampleVideo(cellCenterUvForIndex(context, index)).rgb;
        float fill = rectMask(context.uv, swatchMin, swatchMax);
        float outline = borderMask(context.uv, swatchMin, swatchMax, borderThickness);
        if (fill > 0.5)
        {
            swatchColor = storedColor;
            swatchMask = 1.0;
        }
        if (outline > 0.5)
        {
            swatchColor = float3(0.0, 0.0, 0.0);
            swatchMask = 1.0;
        }
    }

    float opacity = saturate(overlayOpacity) * swatchMask;
    float3 displayColor = lerp(baseColor, swatchColor, opacity);
    return float4(saturate(displayColor), 1.0);
}
