using System.Collections.Concurrent;
using FrameProcessor.Domain;

namespace FrameProcessor.Concurrency;

/// <summary>
/// Hands out a per-frame <see cref="SemaphoreSlim"/> so concurrent uploads to the same
/// frame are serialized while different frames remain independent
/// (see SPEC.md §3.4, CLAUDE.md "Per-frame serialization").
/// </summary>
public sealed class FrameLockProvider
{
    private readonly ConcurrentDictionary<FrameName, SemaphoreSlim> _locks = new();

    public async Task<IDisposable> AcquireAsync(FrameName frame, CancellationToken cancellationToken)
    {
        var semaphore = _locks.GetOrAdd(frame, _ => new SemaphoreSlim(1, 1));
        await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
        return new Releaser(semaphore);
    }

    private sealed class Releaser : IDisposable
    {
        private SemaphoreSlim? _semaphore;

        public Releaser(SemaphoreSlim semaphore) => _semaphore = semaphore;

        public void Dispose()
        {
            Interlocked.Exchange(ref _semaphore, null)?.Release();
        }
    }
}
