@namespace DeepDrftPublic.Client.Controls
@using DeepDrftModels.DTOs

@* Shared presentational queue list. Renders the ordered queue with the current track marked, and
   (when Editable) drag-reorder handles + per-row remove controls. This is the single "view" both
   the docked overlay (17.2) and the embedded panel (17.3) consume — one source, multiple views.

   Purely presentational: owns no data fetch, no player wiring, and no IQueueService mutation of its
   own. Order changes, removals, and row jumps are surfaced to the parent as EventCallbacks; the
   parent calls the queue engine. It runs during prerender without JS interop (MudDropContainer's
   drag work is client-only and inert when no drag occurs). *@

@if (Items is { Count: > 0 })
{
    @if (Editable)
    {
        <MudDropContainer T="QueueRow" @ref="_dropContainer" Items="Rows" ItemsSelector="@((row, zone) => true)"
                          ItemDropped="OnItemDropped" Class="deepdrft-queue-list">
            <ChildContent>
                <MudDropZone T="QueueRow" Identifier="queue" Class="deepdrft-queue-zone" AllowReorder="true"/>
            </ChildContent>
            <ItemRenderer>
                @RenderRow(context)
            </ItemRenderer>
        </MudDropContainer>
    }
    else
    {
        <div class="deepdrft-queue-list">
            @foreach (var row in Rows)
            {
                @RenderRow(row)
            }
        </div>
    }
}

@code {
    /// <summary>The ordered tracks to render. Empty/null renders nothing.</summary>
    [Parameter] public IReadOnlyList<TrackDto>? Items { get; set; }

    /// <summary>
    /// Index of the current track within <see cref="Items"/>, or -1 when none. The matching row is
    /// rendered with a now-playing marker.
    /// </summary>
    [Parameter] public int CurrentIndex { get; set; } = -1;

    /// <summary>
    /// When true, rows show drag handles and a remove control and reorder is enabled. When false the
    /// list is a read-only display (the embed's fixed-order shared queue).
    /// </summary>
    [Parameter] public bool Editable { get; set; }

    /// <summary>
    /// Raised when the user reorders a row: <c>(fromIndex, toIndex)</c>. The parent calls
    /// <c>IQueueService.Move</c>. Only fires when <see cref="Editable"/>.
    /// </summary>
    [Parameter] public EventCallback<(int FromIndex, int ToIndex)> OnReorder { get; set; }

    /// <summary>
    /// Raised when the user removes a row, carrying the row's index. The parent calls
    /// <c>IQueueService.RemoveAt</c>. Only fires when <see cref="Editable"/>.
    /// </summary>
    [Parameter] public EventCallback<int> OnRemove { get; set; }

    /// <summary>
    /// Raised when the user clicks a row body to jump playback to it, carrying the row's index. The
    /// parent decides whether/how to honour it (e.g. play from that index).
    /// </summary>
    [Parameter] public EventCallback<int> OnJump { get; set; }

    private MudDropContainer<QueueRow>? _dropContainer;

    // MudDropContainer snapshots its Items into internal drop zones and does not re-read them on a
    // plain re-render — so a Clear/remove/reorder that changes the parent's Items list must be pushed
    // into the container explicitly, or the panel shows the stale order until reopened (bug #4). The
    // parent passes a fresh Items reference per mutation; refreshing here on every parameter set re-flows
    // the container's snapshot to match. Cheap: Refresh only re-reads the bound list.
    protected override void OnParametersSet() => _dropContainer?.Refresh();

    // Index-tagged view rows. The index is the row's position in Items at render time and is the
    // value surfaced to the parent's callbacks — the component never mutates the underlying list.
    private List<QueueRow> Rows =>
        Items is null
            ? []
            : Items.Select((track, index) => new QueueRow(index, track)).ToList();

    private async Task OnItemDropped(MudItemDropInfo<QueueRow> dropInfo)
    {
        var from = dropInfo.Item!.Index;
        var to = dropInfo.IndexInZone;
        // MudDropContainer recomputes the list from the parent's next render; refresh its snapshot so
        // the dragged row snaps back until the parent's Move re-flows the cascaded Items.
        _dropContainer?.Refresh();
        if (from == to) return;
        await OnReorder.InvokeAsync((from, to));
    }

    private sealed record QueueRow(int Index, TrackDto Track);

    private RenderFragment RenderRow(QueueRow row) => __builder =>
    {
        var isCurrent = row.Index == CurrentIndex;
        <div class="@($"deepdrft-queue-row{(isCurrent ? " deepdrft-queue-row-current" : "")}")">
            @if (Editable)
            {
                <MudIcon Icon="@Icons.Material.Filled.DragIndicator" Size="Size.Small"
                         Class="deepdrft-queue-drag-handle"/>
            }
            <span class="deepdrft-queue-position">@(row.Index + 1)</span>
            <div class="deepdrft-queue-body" @onclick="() => OnJump.InvokeAsync(row.Index)">
                <span class="deepdrft-queue-title">@row.Track.TrackName</span>
                @if (row.Track.Release is { Artist: var artist } && !string.IsNullOrWhiteSpace(artist))
                {
                    <span class="deepdrft-queue-artist">@artist</span>
                }
            </div>
            @if (isCurrent)
            {
                <MudIcon Icon="@Icons.Material.Filled.GraphicEq" Size="Size.Small"
                         Color="Color.Primary" Class="deepdrft-queue-nowplaying"/>
            }
            @* The current track cannot be removed (OQ3/OQ11): the queue empties only organically as the
               current ends with nothing after it. Suppress the × on the current row only — reorder of the
               current track is still allowed. *@
            @if (Editable && !isCurrent)
            {
                <MudIconButton Icon="@Icons.Material.Filled.Close" Size="Size.Small"
                               Class="deepdrft-queue-remove" aria-label="Remove from queue"
                               OnClick="() => OnRemove.InvokeAsync(row.Index)"/>
            }
        </div>
    };
}
