namespace ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;

/// <summary>
/// Process-wide count of live <see cref="DashboardSnapshotHub"/> connections.
/// Registered as a singleton and read by <see cref="DashboardSnapshotPublisher"/>
/// to idle-gate the snapshot tick: with no dashboard connected there is nothing
/// to broadcast to, so no snapshot is built.
/// </summary>
public sealed class DashboardSnapshotHubConnectionCounter
{
    private int _count;

    /// <summary>Gets the number of live snapshot hub connections.</summary>
    public int Count => Volatile.Read(ref _count);

    /// <summary>Records a new snapshot hub connection.</summary>
    /// <returns>The connection count after the increment.</returns>
    public int Increment()
    {
        return Interlocked.Increment(ref _count);
    }

    /// <summary>
    /// Records a snapshot hub disconnection, clamped at zero: SignalR can invoke
    /// <c>OnDisconnectedAsync</c> for a connection whose <c>OnConnectedAsync</c>
    /// faulted, and a negative count would idle-gate the publisher while viewers
    /// are still attached.
    /// </summary>
    /// <remarks>
    /// The clamp is applied inside the compare-and-swap rather than as a repair
    /// afterwards. Decrementing first and then correcting a negative result races:
    /// two unmatched decrements from zero would both plan a repair, a real
    /// connection could increment in between, and the stale repair would then
    /// overwrite that live connection's increment — freezing a real viewer's
    /// dashboard behind the idle gate. Reading, clamping, and publishing as one
    /// atomic step means a lost race simply retries against the fresh value.
    /// </remarks>
    /// <returns>The connection count after the decrement.</returns>
    public int Decrement()
    {
        int current;
        int next;
        do
        {
            current = Volatile.Read(ref _count);
            next = current > 0 ? current - 1 : 0;
        }
        while (Interlocked.CompareExchange(ref _count, next, current) != current);

        return next;
    }
}
