namespace NpncbDiagnostic;

/// <summary>
/// Reconstructs DEFLATE output through a 32 KiB sliding window. The full file is
/// never retained: bytes are used only for distance-copy validation and CRC-32.
/// </summary>
internal sealed class DeflateOutputTracker
{
    private const int WindowSize = 32 * 1024;
    private const int WindowMask = WindowSize - 1;

    private readonly byte[] _window = new byte[WindowSize];
    private readonly long _expectedLength;
    private readonly Crc32Accumulator _crc32 = new();
    private int _writeIndex;
    private long _length;

    public DeflateOutputTracker(long expectedLength)
    {
        if (expectedLength < 0) throw new ArgumentOutOfRangeException(nameof(expectedLength));
        _expectedLength = expectedLength;
    }

    public long Length => _length;
    public uint Crc32 => _crc32.Value;

    public void WriteLiteral(byte value)
    {
        EnsureCanWrite(1);
        WriteUnchecked(value);
    }

    public void WriteStoredByte(byte value)
    {
        EnsureCanWrite(1);
        WriteUnchecked(value);
    }

    public void CopyMatch(int distance, int length)
    {
        if (distance < 1 || distance > WindowSize)
            throw new InvalidDataException($"Invalid DEFLATE distance {distance}.");
        if (distance > _length)
            throw new InvalidDataException(
                $"DEFLATE distance {distance} exceeds the { _length } bytes already decoded.");
        if (length < 3 || length > 258)
            throw new InvalidDataException($"Invalid DEFLATE match length {length}.");

        EnsureCanWrite(length);

        for (int i = 0; i < length; i++)
        {
            int sourceIndex = (_writeIndex - distance) & WindowMask;
            byte value = _window[sourceIndex];
            WriteUnchecked(value);
        }
    }

    public void VerifyCompleted(uint expectedCrc32)
    {
        if (_length != _expectedLength)
        {
            throw new InvalidDataException(
                $"Decoded size mismatch: expected {_expectedLength:N0}, got {_length:N0} bytes.");
        }

        if (Crc32 != expectedCrc32)
        {
            throw new InvalidDataException(
                $"CRC-32 mismatch: expected 0x{expectedCrc32:X8}, got 0x{Crc32:X8}.");
        }
    }

    private void EnsureCanWrite(int count)
    {
        if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
        if (_length > _expectedLength - count)
        {
            throw new InvalidDataException(
                "Decoded data exceeds the uncompressed size recorded in the ZIP central directory.");
        }
    }

    private void WriteUnchecked(byte value)
    {
        _window[_writeIndex] = value;
        _writeIndex = (_writeIndex + 1) & WindowMask;
        _length++;
        _crc32.Update(value);
    }
}
