namespace Minint.Core.Models;

/// <summary>
/// A single document (frame) within a container.
/// Has its own palette shared by all layers, plus a list of layers.
/// </summary>
public sealed class MinintDocument
{
    public string Name { get; set; }

    /// <summary>
    /// Delay before showing the next frame during animation playback (ms).
    /// </summary>
    public uint FrameDelayMs { get; set; }

    /// <summary>
    /// Document palette. Index 0 is always <see cref="RgbaColor.Transparent"/>.
    /// All layers reference colors by index into this list.
    /// </summary>
    public List<RgbaColor> Palette { get; }

    public List<MinintLayer> Layers { get; }

    /// <summary>
    /// Reverse lookup cache: RgbaColor → palette index. Built lazily, invalidated
    /// on structural palette changes (compact, clear). Call <see cref="InvalidatePaletteCache"/>
    /// after bulk palette modifications.
    /// </summary>
    private Dictionary<RgbaColor, int>? _paletteCache;

    public MinintDocument(string name)
    {
        Name = name;
        FrameDelayMs = 100;
        Palette = [RgbaColor.Transparent];
        Layers = [];
    }

    /// <summary>
    /// Constructor for deserialization — accepts pre-built palette and layers.
    /// </summary>
    public MinintDocument(string name, uint frameDelayMs, List<RgbaColor> palette, List<MinintLayer> layers)
    {
        Name = name;
        FrameDelayMs = frameDelayMs;
        Palette = palette;
        Layers = layers;
    }

    /// <summary>
    /// Returns the number of bytes needed to store a single palette index on disk.
    /// </summary>
    public int IndexByteWidth => Palette.Count switch
    {
        <= 255 => 1,
        <= 65_535 => 2,
        <= 16_777_215 => 3,
        _ => 4
    };

    /// <summary>
    /// O(1) lookup of a color in the palette. Returns the index, or -1 if not found.
    /// Lazily builds an internal dictionary on first call.
    /// </summary>
    public int FindColorCached(RgbaColor color)
    {
        var cache = EnsurePaletteCache();
        return cache.GetValueOrDefault(color, -1);
    }

    /// <summary>
    /// Returns the index of <paramref name="color"/>. If absent, appends it to the palette
    /// and updates the cache. O(1) amortized.
    /// </summary>
    public int EnsureColorCached(RgbaColor color)
    {
        var cache = EnsurePaletteCache();
        if (cache.TryGetValue(color, out int idx))
            return idx;

        idx = Palette.Count;
        Palette.Add(color);
        cache[color] = idx;
        return idx;
    }

    /// <summary>
    /// Drops the reverse lookup cache. Must be called after any operation that
    /// reorders, removes, or bulk-replaces palette entries (e.g. compact, grayscale).
    /// </summary>
    public void InvalidatePaletteCache() => _paletteCache = null;

    private Dictionary<RgbaColor, int> EnsurePaletteCache()
    {
        if (_paletteCache is not null)
            return _paletteCache;

        var cache = new Dictionary<RgbaColor, int>(Palette.Count);
        for (int i = 0; i < Palette.Count; i++)
            cache.TryAdd(Palette[i], i); // first occurrence wins (for dupes)
        _paletteCache = cache;
        return cache;
    }
}
