using Minint.Core.Models;

namespace Minint.Core.Services.Impl;

public sealed class Compositor : ICompositor
{
    /// <inheritdoc />
    public uint[] Composite(MinintDocument document, int width, int height)
    {
        int pixelCount = width * height;
        var result = new uint[pixelCount]; // starts as 0x00000000 (transparent black)

        var palette = document.Palette;

        foreach (var layer in document.Layers)
        {
            if (!layer.IsVisible)
                continue;

            byte layerOpacity = layer.Opacity;
            if (layerOpacity == 0)
                continue;

            var pixels = layer.Pixels;
            for (int i = 0; i < pixelCount; i++)
            {
                int idx = pixels[i];
                if (idx == 0)
                    continue; // transparent — skip

                var src = palette[idx];

                // Effective source alpha = palette alpha * layer opacity / 255
                int srcA = src.A * layerOpacity / 255;
                if (srcA == 0)
                    continue;

                if (srcA == 255)
                {
                    // Fully opaque — fast path, no blending needed
                    result[i] = PackArgb(src.R, src.G, src.B, 255);
                    continue;
                }

                // Standard "over" alpha compositing
                uint dst = result[i];
                int dstA = (int)(dst >> 24);
                int dstR = (int)((dst >> 16) & 0xFF);
                int dstG = (int)((dst >> 8) & 0xFF);
                int dstB = (int)(dst & 0xFF);

                int outA = srcA + dstA * (255 - srcA) / 255;
                if (outA == 0)
                    continue;

                int outR = (src.R * srcA + dstR * dstA * (255 - srcA) / 255) / outA;
                int outG = (src.G * srcA + dstG * dstA * (255 - srcA) / 255) / outA;
                int outB = (src.B * srcA + dstB * dstA * (255 - srcA) / 255) / outA;

                result[i] = PackArgb(
                    (byte)Math.Min(outR, 255),
                    (byte)Math.Min(outG, 255),
                    (byte)Math.Min(outB, 255),
                    (byte)Math.Min(outA, 255));
            }
        }

        return result;
    }

    private static uint PackArgb(byte r, byte g, byte b, byte a) =>
        (uint)(b | (g << 8) | (r << 16) | (a << 24));
}
