@using DeepDrftPublic.Client.Services

<button type="button"
        class="@ButtonClass"
        disabled="@(_streamLoading || !RendererInfo.IsInteractive)"
        aria-busy="@_streamLoading.ToString().ToLowerInvariant()"
        @onclick="StreamNow">
    @if (_findingTrack)
    {
        <span>@LoadingLabel</span>
    }
    else
    {
        <span>@ButtonLabel</span>
    }
</button>
@if (_streamMessage is not null)
{
    <p class="stream-now-message" role="status">@_streamMessage</p>
}

@implements IDisposable

@code {
    [Parameter, EditorRequired] public required string ButtonClass { get; set; }
    [Parameter, EditorRequired] public required string ButtonLabel { get; set; }
    [Parameter] public string LoadingLabel { get; set; } = "Finding a track…";
    [Parameter] public EventCallback OnStreamStarted { get; set; }
    [CascadingParameter] public IStreamingPlayerService? PlayerService { get; set; }
    [CascadingParameter] public IQueueService? Queue { get; set; }
    [Inject] public required ITrackDataService TrackData { get; set; }

    private bool _streamLoading;
    private bool _findingTrack;
    private string? _streamMessage;
    private CancellationTokenSource? _messageCts;

    private const string EmptyLibraryMessage = "No tracks yet — check back soon.";
    private const string FetchFailedMessage = "Couldn't reach the library — try again.";

    private async Task StreamNow()
    {
        // Re-entrancy guard: the button is disabled while loading, but guard in code too so a
        // double-dispatch can never start two concurrent streams.
        if (_streamLoading) return;

        _streamLoading = true;
        _findingTrack = true;
        _streamMessage = null;

        // Warm the AudioContext FIRST, inside the gesture's call stack and before the network
        // await below. Safari only lets a suspended AudioContext resume while the originating
        // user gesture is still active; awaiting GetRandomTrack() first would consume the gesture
        // and leave playback silently refused. PlayerService is null only outside the
        // AudioPlayerProvider cascade (it should always be present in the public layout).
        var warmTask = PlayerService?.WarmAudioContext() ?? Task.CompletedTask;

        try
        {
            await warmTask;
            var result = await TrackData.GetRandomTrack();

            if (!result.Success)
            {
                ShowTransientMessage(FetchFailedMessage);
                return;
            }

            if (result.Value is not { } track)
            {
                ShowTransientMessage(EmptyLibraryMessage);
                return;
            }

            await OnStreamStarted.InvokeAsync();

            // Track is found — flip only the label flag so the button reverts to
            // its resting label before the stream begins, while _streamLoading stays true
            // to keep the button disabled and the re-entrancy guard intact.
            _findingTrack = false;
            StateHasChanged();

            // PLAY semantics: prepend to the queue's front so a "stream now" track becomes current and
            // any existing queue stays intact behind it. Falls back to a direct stream when the queue
            // cascade is absent.
            if (Queue is not null)
                await Queue.PlayTrack(track);
            else if (PlayerService is not null)
                await PlayerService.SelectTrackStreaming(track);
        }
        catch (Exception)
        {
            ShowTransientMessage(FetchFailedMessage);
        }
        finally
        {
            _streamLoading = false;
            _findingTrack = false;
        }
    }

    private void ShowTransientMessage(string message)
    {
        _streamMessage = message;

        // Cancel any in-flight clear timer so the newest message gets its full display window.
        _messageCts?.Cancel();
        _messageCts?.Dispose();
        _messageCts = new CancellationTokenSource();
        var token = _messageCts.Token;

        _ = ClearMessageAfterDelayAsync(token);
    }

    private async Task ClearMessageAfterDelayAsync(CancellationToken token)
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(4), token);
        }
        catch (TaskCanceledException)
        {
            return;
        }

        _streamMessage = null;
        await InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        _messageCts?.Cancel();
        _messageCts?.Dispose();
    }
}
