#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout.Scenes;

/// <summary>
/// Schedules each legacy on-air refresh as a one-shot operation after the
/// preceding Play completion has been observed. The effective cadence is
/// deliberately callback completion plus the configured delay, so missed
/// intervals are coalesced instead of being replayed back-to-back.
/// </summary>
public sealed class LegacyRefreshScheduler
{
    private readonly TimeSpan _recurringDelay;
    private readonly Func<TimeSpan, CancellationToken, Task> _delayAsync;
    private readonly int? _maximumRefreshCount;
    private TimeSpan _nextDelay;
    private int _refreshInFlight;
    private int _completedRefreshCount;
    private int _maximumCallbackDrained;

    public LegacyRefreshScheduler(
        TimeSpan initialDelay,
        TimeSpan recurringDelay,
        int? maximumRefreshCount = null)
        : this(
            initialDelay,
            recurringDelay,
            static (delay, cancellationToken) => Task.Delay(delay, cancellationToken),
            maximumRefreshCount)
    {
    }

    internal LegacyRefreshScheduler(
        TimeSpan initialDelay,
        TimeSpan recurringDelay,
        Func<TimeSpan, CancellationToken, Task> delayAsync,
        int? maximumRefreshCount = null)
    {
        ValidateDelay(initialDelay, nameof(initialDelay));
        ValidateDelay(recurringDelay, nameof(recurringDelay));
        ArgumentNullException.ThrowIfNull(delayAsync);
        if (maximumRefreshCount is < 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(maximumRefreshCount),
                "A legacy refresh limit cannot be negative.");
        }

        _nextDelay = initialDelay;
        _recurringDelay = recurringDelay;
        _delayAsync = delayAsync;
        _maximumRefreshCount = maximumRefreshCount;
        _maximumCallbackDrained = maximumRefreshCount == 0 ? 1 : 0;
    }

    public bool IsRefreshInFlight => Volatile.Read(ref _refreshInFlight) != 0;

    public int CompletedRefreshCount => Volatile.Read(ref _completedRefreshCount);

    public int? MaximumRefreshCount => _maximumRefreshCount;

    public bool HasReachedMaximum =>
        _maximumRefreshCount is { } maximum && CompletedRefreshCount >= maximum;

    public bool IsMaximumCallbackDrained =>
        Volatile.Read(ref _maximumCallbackDrained) != 0;

    /// <summary>
    /// Waits for the prior Play callback first, then starts the entire one-shot
    /// delay. A false callback result prevents any delay or refresh dispatch.
    /// </summary>
    public async Task<bool> WaitForNextRefreshAsync(
        Func<CancellationToken, Task<bool>> waitForPlayCompletionAsync,
        Action<TimeSpan>? onDelayScheduled,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(waitForPlayCompletionAsync);
        if (HasReachedMaximum)
        {
            throw new InvalidOperationException(
                "The configured legacy refresh limit has already been reached.");
        }

        if (!await waitForPlayCompletionAsync(cancellationToken))
        {
            return false;
        }

        cancellationToken.ThrowIfCancellationRequested();
        var delay = _nextDelay;
        onDelayScheduled?.Invoke(delay);
        await _delayAsync(delay, cancellationToken);
        return true;
    }

    /// <summary>
    /// Executes at most one refresh at a time, even if the scheduler is used
    /// incorrectly by more than one caller.
    /// </summary>
    internal async Task<TResult> ExecuteRefreshAsync<TResult>(
        Func<CancellationToken, Task<TResult>> refreshAsync,
        CancellationToken cancellationToken)
    {
        if (_maximumRefreshCount is not null)
        {
            throw new InvalidOperationException(
                "A bounded legacy refresh must use the tracked execution path.");
        }

        return await ExecuteRefreshCoreAsync(
            refreshAsync,
            successPredicate: null,
            cancellationToken);
    }

    /// <summary>
    /// Executes one refresh and records a successful dispatch before releasing the
    /// in-flight latch. This closes the dispatch/count gap at a configured ceiling.
    /// </summary>
    public async Task<TResult> ExecuteRefreshAndTrackSuccessAsync<TResult>(
        Func<CancellationToken, Task<TResult>> refreshAsync,
        Func<TResult, bool> successPredicate,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(successPredicate);
        return await ExecuteRefreshCoreAsync(
            refreshAsync,
            successPredicate,
            cancellationToken);
    }

    /// <summary>
    /// Confirms the final Play callback after the dispatch ceiling is reached.
    /// A false callback result leaves the ceiling unconfirmed for fail-closed status.
    /// </summary>
    public async Task<bool> DrainMaximumCallbackAsync(
        Func<CancellationToken, Task<bool>> waitForPlayCompletionAsync,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(waitForPlayCompletionAsync);
        if (!HasReachedMaximum)
        {
            throw new InvalidOperationException(
                "The configured legacy refresh limit has not been reached.");
        }

        if (IsMaximumCallbackDrained)
        {
            return true;
        }

        if (!await waitForPlayCompletionAsync(cancellationToken))
        {
            return false;
        }

        Volatile.Write(ref _maximumCallbackDrained, 1);
        return true;
    }

    private async Task<TResult> ExecuteRefreshCoreAsync<TResult>(
        Func<CancellationToken, Task<TResult>> refreshAsync,
        Func<TResult, bool>? successPredicate,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(refreshAsync);
        if (HasReachedMaximum)
        {
            throw new InvalidOperationException(
                "The configured legacy refresh limit has already been reached.");
        }

        if (Interlocked.CompareExchange(ref _refreshInFlight, 1, 0) != 0)
        {
            throw new InvalidOperationException("A legacy scene refresh is already in flight.");
        }

        try
        {
            var result = await refreshAsync(cancellationToken);
            if (successPredicate?.Invoke(result) == true)
            {
                TrackRefreshSucceeded();
            }

            return result;
        }
        finally
        {
            Volatile.Write(ref _refreshInFlight, 0);
        }
    }

    internal void MarkRefreshSucceeded()
    {
        if (_maximumRefreshCount is not null)
        {
            throw new InvalidOperationException(
                "A bounded legacy refresh is tracked by its execution path.");
        }

        TrackRefreshSucceeded();
    }

    private void TrackRefreshSucceeded()
    {
        if (HasReachedMaximum)
        {
            throw new InvalidOperationException(
                "The configured legacy refresh limit has already been reached.");
        }

        Interlocked.Increment(ref _completedRefreshCount);
        _nextDelay = _recurringDelay;
    }

    private static void ValidateDelay(TimeSpan delay, string parameterName)
    {
        if (delay <= TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(
                parameterName,
                "A legacy refresh delay must be positive.");
        }
    }
}
