using DeepDrftModels.DTOs;
using Microsoft.AspNetCore.Components;
using NetBlocks.Models;

namespace DeepDrftPublic.Client.Services;

public interface IPlayerService
{
    // State properties
    bool IsInitialized { get; }
    bool IsLoaded { get; }
    bool IsLoading { get; }
    bool IsPlaying { get; }
    bool IsPaused { get; }
    double CurrentTime { get; }
    double? Duration { get; }
    double Volume { get; }
    double LoadProgress { get; }
    string? ErrorMessage { get; }
    TrackDto? CurrentTrack { get; }

    /// <summary>
    /// Normalized loudness profile for the current track, each value in [0, 1], or null when no
    /// profile is available (no track loaded, or the track has no stored profile). The seek zone
    /// renders this as a waveform; a null profile drives the flat-but-seekable fallback. Fetched on
    /// track select and cleared on unload/stop; <see cref="StateChanged"/> fires once it arrives.
    /// </summary>
    double[]? WaveformProfile { get; }

    // Events for UI updates
    EventCallback? OnStateChanged { get; set; }
    EventCallback? OnTrackSelected { get; set; }

    /// <summary>
    /// Multicast side-channel for state changes. The provider owns the single
    /// <see cref="OnStateChanged"/> EventCallback (it drives the provider re-render);
    /// cascade consumers that read state directly off this service — and so are not
    /// re-rendered by the provider's render when the cascade is <c>IsFixed</c> —
    /// subscribe here to re-render themselves. Fires on the same cadence as
    /// <see cref="OnStateChanged"/> (throttled to ~10/s during streaming).
    /// </summary>
    event Action? StateChanged;

    /// <summary>
    /// Raised once when the current track reaches its natural end of playback (the JS
    /// end-of-stream callback), distinct from a stop/unload/track-switch. This is the single
    /// hook the play-queue subscribes to in order to auto-advance to the next track. It does
    /// NOT fire when playback is stopped, the track is switched, or the player is unloaded —
    /// only on organic completion — so an orchestrator can treat it as "advance the queue."
    /// </summary>
    event Action? TrackEnded;
    
    // Control methods
    Task InitializeAsync();
    Task SelectTrack(TrackDto track);
    Task Stop();
    Task Unload();
    Task TogglePlayPause();
    Task Seek(double position);
    Task SetVolume(double volume);
    Task ClearError();
}

public interface IStreamingPlayerService : IPlayerService
{
    // Streaming state properties
    bool IsStreamingMode { get; }
    bool CanStartStreaming { get; }
    bool HeaderParsed { get; }
    int BufferedChunks { get; }
    
    // Streaming control methods
    Task SelectTrackStreaming(TrackDto track);

    /// <summary>
    /// Initializes the player (if needed) and resumes the AudioContext. Call this synchronously at
    /// the very start of a user-gesture handler — before any <c>await</c> on network I/O — so the
    /// gesture is still "active" when the context resumes. Safari refuses to start a suspended
    /// AudioContext once the originating gesture has been consumed by an intervening await
    /// (e.g. fetching which track to play), so warming here and streaming after is load-bearing.
    /// <see cref="SelectTrackStreaming"/> also resumes the context, but only after its own internal
    /// awaits — too late for a handler that must first fetch the track to play.
    /// </summary>
    Task WarmAudioContext();

    /// <summary>
    /// Stages a track as the current track without touching the audio context or starting the
    /// stream. Used by the embed player, where there is no user gesture on initial load: the track
    /// is shown as ready, and the first play click (a genuine gesture) calls
    /// <see cref="SelectTrackStreaming"/> so the browser allows the AudioContext to start. Sets
    /// <see cref="IPlayerService.CurrentTrack"/> and notifies; performs no JS interop.
    /// </summary>
    Task StageTrack(TrackDto track);
}