#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

/// <summary>
/// Couples the current refresh cancellation generation with its published
/// state. Generation checks and state transitions happen under one lock so an
/// obsolete loop can never overwrite a replacement or a stopped state.
/// </summary>
public sealed class LegacyRefreshEpoch<TState>
{
    private readonly object _gate = new();
    private CancellationTokenSource? _current;
    private TState _state;

    public LegacyRefreshEpoch(TState initialState)
    {
        _state = initialState;
    }

    public TState ReadState()
    {
        lock (_gate)
        {
            return _state;
        }
    }

    public bool IsCurrent(CancellationTokenSource epoch)
    {
        ArgumentNullException.ThrowIfNull(epoch);
        lock (_gate)
        {
            return ReferenceEquals(_current, epoch);
        }
    }

    public void Replace(
        CancellationTokenSource epoch,
        Func<TState, TState> updateState)
    {
        ArgumentNullException.ThrowIfNull(epoch);
        ArgumentNullException.ThrowIfNull(updateState);

        CancellationTokenSource? previous;
        lock (_gate)
        {
            var nextState = updateState(_state);
            previous = _current;
            _current = epoch;
            _state = nextState;
        }

        if (!ReferenceEquals(previous, epoch))
        {
            CancelOutsideLock(previous);
        }
    }

    public void Stop(Func<TState, TState> updateState)
    {
        ArgumentNullException.ThrowIfNull(updateState);

        CancellationTokenSource? previous;
        lock (_gate)
        {
            var nextState = updateState(_state);
            previous = _current;
            _current = null;
            _state = nextState;
        }

        CancelOutsideLock(previous);
    }

    public bool TryUpdate(
        CancellationTokenSource epoch,
        Func<TState, TState> updateState)
    {
        ArgumentNullException.ThrowIfNull(epoch);
        ArgumentNullException.ThrowIfNull(updateState);

        lock (_gate)
        {
            if (!ReferenceEquals(_current, epoch))
            {
                return false;
            }

            _state = updateState(_state);
            return true;
        }
    }

    public bool TryComplete(
        CancellationTokenSource epoch,
        Func<TState, TState> updateState)
    {
        ArgumentNullException.ThrowIfNull(epoch);
        ArgumentNullException.ThrowIfNull(updateState);

        lock (_gate)
        {
            if (!ReferenceEquals(_current, epoch))
            {
                return false;
            }

            var nextState = updateState(_state);
            _current = null;
            _state = nextState;
            return true;
        }
    }

    private static void CancelOutsideLock(CancellationTokenSource? cancellation)
    {
        if (cancellation is null)
        {
            return;
        }

        try
        {
            cancellation.Cancel();
        }
        catch (ObjectDisposedException)
        {
            // The obsolete loop may have completed between the atomic swap and
            // cancellation. Its generation is already detached from state.
        }
    }
}
