using DeepDrftModels.DTOs;

namespace DeepDrftPublic.Client.Services;

/// <summary>
/// Orchestrates ordered playback ("what plays next") <em>above</em> the single-slot
/// <see cref="IStreamingPlayerService"/>. The player stays a single-track device; the queue owns the
/// track list, the current position, skip-forward/back, and auto-advance on natural track end. It
/// drives playback solely through the player's existing <see cref="IStreamingPlayerService.SelectTrackStreaming"/>
/// — it adds no new playback semantics.
///
/// <para>
/// <b>Two-level deque model (the load-bearing invariant).</b> The queue is a deque whose
/// <see cref="Current"/> track (the item at <see cref="CurrentIndex"/>) is the live "front of play".
/// Two families of mutation enter the deque from opposite ends:
/// <list type="bullet">
///   <item><b>PLAY (manual)</b> — <see cref="PlayTrack"/> / <see cref="PlayRelease"/> prepend to the
///   <em>front</em>. The previously-current track is removed, the prepended track(s) become the head
///   in order, the new head becomes current and starts streaming, and whatever sat after the old
///   current stays intact behind the prepend. A whole release prepends in order in one operation.</item>
///   <item><b>Add-to-queue</b> — <see cref="Enqueue"/> / <see cref="EnqueueRange"/> append to the
///   <em>end</em>. They never interrupt the current track and never start playback.</item>
/// </list>
/// </para>
///
/// <para>
/// <b>Advance and end-of-track.</b> <see cref="Next"/> and auto-advance (the player's
/// <see cref="IPlayerService.TrackEnded"/>) walk <see cref="CurrentIndex"/> forward, leaving the just-
/// played track in the list behind the pointer so <see cref="Previous"/> can step back to it. The one
/// exception is the <em>last</em> track: when the current track ends naturally and there is nothing
/// after it, the queue <b>empties</b> and goes dormant (<see cref="CurrentIndex"/> == -1) rather than
/// stranding the finished track as current.
/// </para>
///
/// <para>
/// With an empty queue (<see cref="CurrentIndex"/> == -1) the queue is dormant: it drives nothing and
/// auto-advances nothing, so direct single-track play through the player behaves exactly as it did
/// before the queue existed. The <b>first</b> <see cref="Enqueue"/>/<see cref="EnqueueRange"/> into a
/// dormant queue while a track is already playing externally seeds the head from the player's current
/// track (learned through the attached player, no extra dependency) and then appends the added item, so
/// the resulting deque is <c>[now-playing, added…]</c> rather than a phantom single entry.
/// </para>
/// </summary>
public interface IQueueService
{
    /// <summary>The ordered tracks currently queued. Empty when nothing is enqueued.</summary>
    IReadOnlyList<TrackDto> Items { get; }

    /// <summary>
    /// Index into <see cref="Items"/> of the track the queue considers current, or -1 when the
    /// queue is empty. Always a valid index into <see cref="Items"/> when non-negative.
    /// </summary>
    int CurrentIndex { get; }

    /// <summary>The current track, or null when the queue is empty.</summary>
    TrackDto? Current { get; }

    /// <summary>
    /// True when the queue has been loaded via <see cref="Arm"/> but no track has streamed yet —
    /// the embed's pre-gesture state. Set by <see cref="Arm"/>; cleared the moment playback actually
    /// starts (<see cref="Start"/>/<see cref="PlayRelease"/>/<see cref="PlayTrack"/>/<see cref="Next"/>/
    /// <see cref="Previous"/>) or on <see cref="Clear"/>. The player bar reads this to route the first
    /// play gesture through <see cref="Start"/> (which begins the armed release) rather than streaming
    /// the staged track alone.
    /// </summary>
    bool IsArmed { get; }

    /// <summary>True when there is a track after <see cref="CurrentIndex"/> to advance to.</summary>
    bool HasNext { get; }

    /// <summary>True when there is a track before <see cref="CurrentIndex"/> to step back to.</summary>
    bool HasPrevious { get; }

    /// <summary>
    /// Raised whenever the queue's contents or current position change. The player bar subscribes
    /// to re-render its skip-forward/back affordances. Fires on enqueue, prepend, advance, step-back,
    /// and clear.
    /// </summary>
    event Action? QueueChanged;

    /// <summary>
    /// Manual PLAY of a single track: prepends <paramref name="track"/> to the <em>front</em> of the
    /// deque, removes the previously-current track, makes <paramref name="track"/> the new head/current,
    /// and starts streaming it. The rest of the queue (everything that sat after the old current) stays
    /// intact behind the new head. Into a dormant queue this simply becomes the sole head and plays.
    /// This is the deque-front counterpart to the append-only <see cref="Enqueue"/>.
    /// </summary>
    Task PlayTrack(TrackDto track);

    /// <summary>
    /// Manual PLAY of a release: prepends <paramref name="tracks"/> (in the order given) to the
    /// <em>front</em> of the deque, removes the previously-current track, and starts streaming the
    /// prepended track at <paramref name="startIndex"/> — which becomes current. Tracks prepended
    /// before <paramref name="startIndex"/> sit behind the pointer (reachable via <see cref="Previous"/>);
    /// tracks after it are up-next; whatever sat after the old current stays intact behind the whole
    /// prepend. This is the "play album" entry point the detail pages consume: a header Play uses
    /// <c>startIndex: 0</c>; a mid-album row play passes that row's index. No-op when
    /// <paramref name="tracks"/> is empty.
    /// </summary>
    Task PlayRelease(IEnumerable<TrackDto> tracks, int startIndex = 0);

    /// <summary>
    /// Loads <paramref name="tracks"/> as the queue and sets the current position to index 0 WITHOUT
    /// streaming anything — the queue is "armed". This is the embed's prerender-safe entry point: it
    /// performs no JS interop, so it runs identically during prerender and after WASM boot. It replaces
    /// the queue (an armed embed is a fresh staged release, not a prepend). The first play gesture (see
    /// <see cref="IsArmed"/>) then starts playback via <see cref="Start"/>, which keeps the loaded
    /// release queued so it advances through its tracks. No-op when <paramref name="tracks"/> is empty
    /// (the queue stays empty and disarmed).
    /// </summary>
    void Arm(IEnumerable<TrackDto> tracks);

    /// <summary>
    /// Begins playback of an armed queue (see <see cref="Arm"/>): streams the current track and clears
    /// <see cref="IsArmed"/>, leaving the loaded list and position intact so auto-advance carries on
    /// through the release. This is the first-gesture entry point the embed bar calls. No-op (and stays
    /// disarmed) when the queue is not armed or is empty — so it never double-streams or disturbs a
    /// queue already playing.
    /// </summary>
    Task Start();

    /// <summary>
    /// Appends a track to the <em>end</em> of the queue without changing what is currently playing.
    /// Into a dormant queue (<see cref="CurrentIndex"/> == -1) while a track is already playing
    /// externally (through the attached player but not via the queue), the append first seeds the head
    /// with that now-playing track, then appends <paramref name="track"/> — yielding
    /// <c>[now-playing, track]</c> so the queue reflects what the listener actually hears. Into a fully
    /// dormant queue with nothing playing, the single appended track becomes the head at
    /// <see cref="CurrentIndex"/> == 0. Either way it does NOT begin playback (add is not play).
    /// Interop-free; safe during prerender.
    /// </summary>
    void Enqueue(TrackDto track);

    /// <summary>
    /// Appends tracks to the <em>end</em> of the queue without changing what is currently playing.
    /// Into a dormant queue while a track is already playing externally, the append first seeds the head
    /// with that now-playing track (see <see cref="Enqueue"/>), then appends the range. Into a fully
    /// dormant queue with nothing playing, the first appended track becomes the head at
    /// <see cref="CurrentIndex"/> == 0. It does NOT begin playback (add is not play). Interop-free; safe
    /// during prerender.
    /// </summary>
    void EnqueueRange(IEnumerable<TrackDto> tracks);

    /// <summary>
    /// Reorders the queue, moving the track at <paramref name="fromIndex"/> to
    /// <paramref name="toIndex"/>, and re-emits <see cref="QueueChanged"/>. Adjusts
    /// <see cref="CurrentIndex"/> so the <em>same track</em> stays current across the move — it does
    /// not restart, re-stream, or interrupt the currently-playing track. Interop-free; safe during
    /// prerender. No-op (no throw, no <see cref="QueueChanged"/>) when either index is out of range
    /// or the indices are equal.
    /// </summary>
    void Move(int fromIndex, int toIndex);

    /// <summary>
    /// Removes the track at <paramref name="index"/> and re-emits <see cref="QueueChanged"/>. Does
    /// not touch playback (the player stays a single-track device): removing the current track does
    /// not stop it — the playing track runs to its natural end while <see cref="CurrentIndex"/>
    /// resolves to the new occupant of that slot (the next track) so the next auto-advance/skip is
    /// coherent. Removing a track before the current decrements <see cref="CurrentIndex"/> (the same
    /// track stays current); removing after the current leaves it unchanged. Removing the last
    /// remaining track empties the queue (<see cref="CurrentIndex"/> == -1, dormant). Interop-free;
    /// safe during prerender. No-op (no throw, no <see cref="QueueChanged"/>) when
    /// <paramref name="index"/> is out of range.
    /// </summary>
    void RemoveAt(int index);

    /// <summary>
    /// Advances to the next track and streams it. No-op when <see cref="HasNext"/> is false.
    /// </summary>
    Task Next();

    /// <summary>
    /// Steps back to the previous track and streams it. No-op when <see cref="HasPrevious"/> is false.
    /// </summary>
    Task Previous();

    /// <summary>
    /// Moves the current pointer to <paramref name="index"/> and streams that track once. This is the
    /// row-jump primitive the open playlist panel uses: unlike <see cref="PlayRelease"/> it does not
    /// prepend (the track is already in the deque), and unlike repeated <see cref="Next"/> it does not
    /// stream the intervening rows. No-op when <paramref name="index"/> is out of range or already
    /// current.
    /// </summary>
    Task JumpTo(int index);

    /// <summary>Empties the queue and resets the position. Does not stop the player.</summary>
    void Clear();

    /// <summary>
    /// Empties the up-next while keeping the currently-playing track: removes every item except
    /// <see cref="Current"/>, leaving it as the sole remaining item at <see cref="CurrentIndex"/> == 0,
    /// and re-emits <see cref="QueueChanged"/>. Unlike <see cref="Clear"/> (which empties everything and
    /// goes dormant), this preserves what is playing — the player is never stopped and the current track
    /// stays queued, so playback continues uninterrupted while the rest of the queue is discarded.
    /// Interop-free; safe during prerender. No-op (no throw, no <see cref="QueueChanged"/>) when the queue
    /// is empty/dormant or already holds only the current track.
    /// </summary>
    void ClearUpcoming();
}
