namespace Lumi.Companion.Core;

public sealed class SingleFlightOperation
{
    private readonly object _gate = new();
    private Task? _active;
    private long _generation;

    public bool IsRunning
    {
        get { lock (_gate) return _active is { IsCompleted: false }; }
    }

    public Task RunAsync(Func<CancellationToken, Task> operation, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(operation);
        lock (_gate)
        {
            if (_active is { IsCompleted: false }) return _active;
            var generation = ++_generation;
            var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var task = completion.Task;
            _active = task;
            _ = RunCoreAsync(generation, operation, cancellationToken, completion);
            return task;
        }
    }

    private async Task RunCoreAsync(long generation, Func<CancellationToken, Task> operation, CancellationToken cancellationToken, TaskCompletionSource completion)
    {
        try
        {
            await operation(cancellationToken);
            completion.TrySetResult();
        }
        catch (OperationCanceledException error) { completion.TrySetCanceled(error.CancellationToken); }
        catch (Exception error) { completion.TrySetException(error); }
        finally
        {
            lock (_gate)
            {
                if (_generation == generation) _active = null;
            }
        }
    }
}
