namespace ytLive.Services.Compositor;

/// <summary>
/// Pure pixel math shared by the compositor: the WPF "UniformToFill" cover-crop
/// (what the preview's <c>Stretch="UniformToFill"</c> does) and a clamped bilinear
/// sample/scale. Pure and deterministic — the unit-tested half of the compositor.
/// </summary>
public static class StretchMath
{
    /// <summary>
    /// UniformToFill: the drawn content covers the destination while preserving the
    /// source aspect, centered; overflow is cropped. Returns the scale and the
    /// source-origin offset (in destination pixels) of the drawn content within the
    /// destination rect.
    /// </summary>
    public static (float Scale, float OffsetX, float OffsetY) UniformToFill(float dstW, float dstH, int srcW, int srcH)
    {
        var scale = Math.Max(dstW / srcW, dstH / srcH);
        var drawnW = srcW * scale;
        var drawnH = srcH * scale;
        return (scale, (dstW - drawnW) / 2f, (dstH - drawnH) / 2f);
    }

    /// <summary>Clamped bilinear scale into a new tightly-packed BGRA8 frame; returns the input unchanged when the sizes already match.</summary>
    public static VideoFrame BilinearScale(VideoFrame src, int outW, int outH)
    {
        if (outW == src.Width && outH == src.Height) return src;

        var outPixels = new byte[outW * outH * 4];
        for (var y = 0; y < outH; y++)
        {
            var sy = (y + 0.5f) / outH * src.Height - 0.5f;
            for (var x = 0; x < outW; x++)
            {
                var sx = (x + 0.5f) / outW * src.Width - 0.5f;
                var (b, g, r, a) = SampleBgra(src.BgraPixels, src.Width, src.Height, sx, sy);
                var di = (y * outW + x) * 4;
                outPixels[di] = b;
                outPixels[di + 1] = g;
                outPixels[di + 2] = r;
                outPixels[di + 3] = a;
            }
        }
        return new VideoFrame(outW, outH, outPixels);
    }

    /// <summary>
    /// Clamped bilinear sample of one BGRA8 pixel at fractional (sx, sy). Coordinates
    /// outside [0, w-1]×[0, h-1] clamp to the edge, so a caller can sample freely
    /// without bounds checks.
    /// </summary>
    public static (byte B, byte G, byte R, byte A) SampleBgra(byte[] src, int w, int h, float sx, float sy)
    {
        sx = Math.Clamp(sx, 0, w - 1);
        sy = Math.Clamp(sy, 0, h - 1);
        var x0 = (int)sx;
        var y0 = (int)sy;
        var x1 = Math.Min(x0 + 1, w - 1);
        var y1 = Math.Min(y0 + 1, h - 1);
        var fx = sx - x0;
        var fy = sy - y0;

        var p00 = (y0 * w + x0) * 4;
        var p10 = (y0 * w + x1) * 4;
        var p01 = (y1 * w + x0) * 4;
        var p11 = (y1 * w + x1) * 4;

        float r = Lerp(Lerp(src[p00 + 2], src[p10 + 2], fx), Lerp(src[p01 + 2], src[p11 + 2], fx), fy);
        float g = Lerp(Lerp(src[p00 + 1], src[p10 + 1], fx), Lerp(src[p01 + 1], src[p11 + 1], fx), fy);
        float b = Lerp(Lerp(src[p00], src[p10], fx), Lerp(src[p01], src[p11], fx), fy);
        float a = Lerp(Lerp(src[p00 + 3], src[p10 + 3], fx), Lerp(src[p01 + 3], src[p11 + 3], fx), fy);
        return ((byte)Math.Round(b), (byte)Math.Round(g), (byte)Math.Round(r), (byte)Math.Round(a));
    }

    private static float Lerp(float a, float b, float t) => a + (b - a) * t;
}
