namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;

/// <summary>
/// Serializes async critical sections — one caller inside at a time, FIFO. Used by
/// <see cref="RealOpcUaClient"/> so every structural mutation of an OPC UA Subscription
/// (AddItem / RemoveItem / ApplyChanges / Create / Delete) and every shard-list mutation
/// runs alone: the OPC Foundation Subscription object is not safe under concurrent
/// structural mutation, and nothing else orders the subscribe background task, the
/// tag-resolution probe and an alarm subscribe against each other.
///
/// <para>
/// A named type rather than a bare <see cref="SemaphoreSlim"/> field so the "exactly one
/// gate, released on every path" discipline is visible at each call site and testable on
/// its own.
/// </para>
/// </summary>
internal sealed class AsyncSerialGate
{
    private readonly SemaphoreSlim _gate = new(1, 1);

    /// <summary>Runs <paramref name="action"/> with no other gated section in flight.</summary>
    /// <param name="action">The critical section.</param>
    /// <param name="cancellationToken">Cancellation token observed while waiting to enter.</param>
    /// <returns>A task that completes when the section has run and the gate is released.</returns>
    public async Task RunAsync(Func<Task> action, CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            await action().ConfigureAwait(false);
        }
        finally
        {
            _gate.Release();
        }
    }

    /// <summary>Value-returning counterpart of <see cref="RunAsync(Func{Task}, CancellationToken)"/>.</summary>
    /// <typeparam name="T">Result type of the critical section.</typeparam>
    /// <param name="action">The critical section.</param>
    /// <param name="cancellationToken">Cancellation token observed while waiting to enter.</param>
    /// <returns>The section's result.</returns>
    public async Task<T> RunAsync<T>(Func<Task<T>> action, CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            return await action().ConfigureAwait(false);
        }
        finally
        {
            _gate.Release();
        }
    }
}
