namespace ytLive.Services.Audio;

/// <summary>
/// A thread-safe float ring buffer for the live audio path (TASK 9): NAudio
/// raises capture chunks on its own threads, the mixer's live loop drains on
/// its own. Writes overwrite the OLDEST samples when full so the buffer never
/// grows unbounded — the stream can't stall on capture jitter, it just skips
/// the stale tail.
/// </summary>
public sealed class AudioRingBuffer
{
    private readonly object _sync = new();
    private readonly float[] _buffer;
    private int _head;
    private int _count;

    public AudioRingBuffer(int capacity)
    {
        if (capacity <= 0)
            throw new ArgumentOutOfRangeException(nameof(capacity));
        _buffer = new float[capacity];
    }

    public int Count
    {
        get
        {
            lock (_sync)
            {
                return _count;
            }
        }
    }

    /// <summary>Appends a chunk, dropping the oldest samples if the buffer is full.</summary>
    public void Write(float[] samples)
    {
        if (samples.Length == 0)
            return;

        lock (_sync)
        {
            if (samples.Length >= _buffer.Length)
            {
                Array.Copy(samples, samples.Length - _buffer.Length, _buffer, 0, _buffer.Length);
                _head = 0;
                _count = _buffer.Length;
                return;
            }

            if (_count + samples.Length > _buffer.Length)
            {
                // Evict the oldest samples to make room; the incoming write then
                // overwrites them as it wraps. Head stays put — only writing
                // advances it.
                var drop = _count + samples.Length - _buffer.Length;
                _count -= drop;
            }

            var writeAt = _head;
            var first = Math.Min(samples.Length, _buffer.Length - writeAt);
            Array.Copy(samples, 0, _buffer, writeAt, first);
            if (first < samples.Length)
                Array.Copy(samples, first, _buffer, 0, samples.Length - first);
            _head = (writeAt + samples.Length) % _buffer.Length;
            _count += samples.Length;
        }
    }

    /// <summary>Reads up to <paramref name="count"/> samples, removing them;
    /// returns the number actually read (fewer on underrun).</summary>
    public int Read(float[] destination, int count)
    {
        lock (_sync)
        {
            var n = Math.Min(count, _count);
            var tail = (_head - _count + _buffer.Length) % _buffer.Length;
            var first = Math.Min(n, _buffer.Length - tail);
            Array.Copy(_buffer, tail, destination, 0, first);
            if (first < n)
                Array.Copy(_buffer, 0, destination, first, n - first);
            _count -= n;
            return n;
        }
    }

    public void Clear()
    {
        lock (_sync)
        {
            _head = 0;
            _count = 0;
        }
    }
}
