using DeepDrftPublic.Client.Services;
using DeepDrftPublic.Client.Clients;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace DeepDrftPublic.Client.Controls;

public partial class AudioPlayerProvider : ComponentBase, IAsyncDisposable
{
    [Inject] public required AudioInteropService AudioInterop { get; set; }
    [Inject] public required TrackMediaClient TrackMediaClient { get; set; }
    [Inject] public required ILogger<StreamingAudioPlayerService> Logger { get; set; }

    private IStreamingPlayerService? _audioPlayerService;
    private QueueService? _queueService;

    [Parameter] public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        // Create the service immediately (but don't initialize yet).
        // The base class lazily initializes on first track selection via
        // EnsureInitializedAsync — that path is correct because audio contexts
        // require a user gesture anyway. Initializing eagerly here causes 4+
        // SignalR round-trips before any content is stable.
        _audioPlayerService = new StreamingAudioPlayerService(AudioInterop, TrackMediaClient, Logger);

        // Provider is the SOLE owner of OnStateChanged. When the service fires,
        // the provider re-renders, which cascades to its children automatically.
        // Children must not wrap or replace this callback.
        _audioPlayerService.OnStateChanged = new EventCallback(this, () => InvokeAsync(StateHasChanged));
        // OnTrackSelected will be set by individual child components that need it

        // The queue orchestrates above the single-slot player. The player is not DI-registered
        // (constructed here), so the queue binds to it via Attach rather than constructor injection —
        // no construction cycle, no IServiceProvider. Cascaded alongside the player so the bar and a
        // future up-next panel both read it.
        _queueService = new QueueService();
        _queueService.Attach(_audioPlayerService);
    }

    /// <summary>
    /// Dispose the player on unmount so the JS setInterval driving progress
    /// callbacks no longer holds a DotNetObjectReference into a destroyed
    /// component (otherwise it throws every 100ms after navigation away).
    /// </summary>
    public async ValueTask DisposeAsync()
    {
        // Dispose the queue first so it unsubscribes from the player's TrackEnded before the
        // player tears down.
        _queueService?.Dispose();
        _queueService = null;

        if (_audioPlayerService is IAsyncDisposable disposable)
        {
            await disposable.DisposeAsync();
        }
        _audioPlayerService = null;
    }
}
