@using System.Net
@using DeepDrftManager.Services
@using DeepDrftModels.DTOs
@using DeepDrftModels.Enums
@inject ICmsTrackService CmsTrackService
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject ILogger<CmsAlbumBrowser> Logger

@if (IsLoading)
{
    <MudProgressCircular Indeterminate="true" Class="mt-4" />
}
else if (_rows.Count == 0)
{
    <MudText Typo="Typo.body1" Class="mt-4">No releases found.</MudText>
}
else
{
    <MudTable T="AlbumRow"
              Items="_rows"
              Hover="true"
              Striped="true"
              Dense="true"
              Bordered="false"
              FixedHeader="true">
        <HeaderContent>
            <MudTh Style="width: 1%;"></MudTh>
            <MudTh Style="width: 1%;">Art</MudTh>
            <MudTh>Album</MudTh>
            <MudTh>Artist</MudTh>
            <MudTh>Genre</MudTh>
            <MudTh>Release Date</MudTh>
            <MudTh>Type</MudTh>
            <MudTh Style="width: 1%; white-space: nowrap;">Tracks</MudTh>
            @foreach (var column in SpecialColumns)
            {
                <MudTh Style="width: 1%; white-space: nowrap;">@column.Header</MudTh>
            }
            <MudTh Style="width: 1%; white-space: nowrap;">Actions</MudTh>
        </HeaderContent>
        <RowTemplate>
            <MudTd>
                <MudIconButton Icon="@(context.IsExpanded ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
                               Size="Size.Small"
                               OnClick="@(() => ToggleExpand(context))" />
            </MudTd>
            <MudTd DataLabel="Art">
                @if (!string.IsNullOrEmpty(context.Release.ImagePath))
                {
                    <div class="cms-album-thumb"
                         style="background-image: url('@ThumbUrl(context.Release.ImagePath)');"></div>
                }
                else
                {
                    <div class="cms-album-thumb cms-album-thumb--fallback"></div>
                }
            </MudTd>
            <MudTd DataLabel="Album">@context.Release.Title</MudTd>
            <MudTd DataLabel="Artist">@context.Release.Artist</MudTd>
            <MudTd DataLabel="Genre">@(context.Release.Genre ?? "—")</MudTd>
            <MudTd DataLabel="Release Date">@(context.Release.ReleaseDate?.ToString("d MMMM, yyyy") ?? "—")</MudTd>
            <MudTd DataLabel="Type">
                <MudChip T="string" Size="Size.Small" Variant="Variant.Outlined">
                    @(context.Release.Medium == ReleaseMedium.Cut
                        ? context.Release.ReleaseType?.ToString() ?? "—"
                        : MediumTypeLabels[context.Release.Medium])
                </MudChip>
            </MudTd>
            <MudTd DataLabel="Tracks">@context.TrackCount</MudTd>
            @foreach (var column in SpecialColumns)
            {
                @* One dedicated cell per host-declared special-action column (Mix waveform, Session hero).
                   The Cell fragment recovers its typed row state via the host's RowFor lookup. Sits between
                   Tracks and Actions so the universal Edit/Delete stay rightmost. *@
                <MudTd DataLabel="@column.Header">@column.Cell(context.Release)</MudTd>
            }
            <MudTd DataLabel="Actions">
                <MudTooltip Text="Batch Edit">
                    <MudIconButton Icon="@Icons.Material.Filled.Edit"
                                   Size="Size.Small"
                                   Color="Color.Primary"
                                   Href="@($"/tracks/album/{Uri.EscapeDataString(context.Release.Title)}/edit")" />
                </MudTooltip>
                <MudTooltip Text="Delete release">
                    <MudIconButton Icon="@Icons.Material.Filled.Delete"
                                   Size="Size.Small"
                                   Color="Color.Error"
                                   Disabled="@context.IsDeleting"
                                   OnClick="@(() => ConfirmAndDeleteAlbum(context))" />
                </MudTooltip>
            </MudTd>
        </RowTemplate>
        <ChildRowContent>
            @if (context.IsExpanded)
            {
                <MudTr>
                    <MudTd colspan="@ColumnCount" Style="padding: 0;">
                        @if (context.IsLoading)
                        {
                            <MudStack Row="true" AlignItems="AlignItems.Center" Class="pa-2">
                                <MudProgressCircular Size="Size.Small" Indeterminate="true" />
                                <MudText Typo="Typo.body2">Loading tracks…</MudText>
                            </MudStack>
                        }
                        else if (context.Tracks is { Count: 0 })
                        {
                            <MudText Typo="Typo.body2" Class="pa-4">No tracks found.</MudText>
                        }
                        else if (context.Tracks is not null)
                        {
                            <MudTable T="TrackDto" Items="context.Tracks" Context="track" Dense="true" Hover="false"
                                      Elevation="0" Style="background: transparent;">
                                <HeaderContent>
                                    <MudTh Style="width: 1%; white-space: nowrap;">#</MudTh>
                                    <MudTh>Track Name</MudTh>
                                </HeaderContent>
                                <RowTemplate>
                                    <MudTd DataLabel="#">@track.TrackNumber</MudTd>
                                    <MudTd DataLabel="Track Name">@track.TrackName</MudTd>
                                </RowTemplate>
                            </MudTable>
                        }
                    </MudTd>
                </MudTr>
            }
        </ChildRowContent>
    </MudTable>
}

@code {
    [Parameter] public IReadOnlyList<ReleaseDto> Releases { get; set; } = Array.Empty<ReleaseDto>();
    [Parameter] public bool IsLoading { get; set; }
    [Parameter] public EventCallback OnReleasesChanged { get; set; }

    // Zero or more dedicated, header-labelled special-action columns (Session hero upload, Mix waveform
    // generate), each rendered as its own header cell + per-row cell between the Tracks and Actions
    // columns. The ALL and Cut tabs leave this empty and render exactly as before — only the standard
    // columns plus Edit/Delete. A per-medium host supplies its bespoke affordances here so the rich
    // expand/delete/Type-chip/edit logic stays single-sourced in this grid rather than forked.
    [Parameter] public IReadOnlyList<SpecialActionColumn> SpecialColumns { get; set; } = Array.Empty<SpecialActionColumn>();

    // Base columns: expand, Art, Album, Artist, Genre, Release Date, Type, Tracks, Actions = 9.
    private const int BaseColumnCount = 9;

    // Total rendered columns, driving the expanded child-row colspan so it always spans the full table
    // regardless of how many special-action columns the host declared.
    private int ColumnCount => BaseColumnCount + SpecialColumns.Count;

    private List<AlbumRow> _rows = new();

    // Tracks the Releases reference last projected into _rows. Guards against OnParametersSet
    // resurrecting a row we removed locally on delete: while the parent holds the same Releases
    // instance (e.g. a mid-operation re-render under IsDeleting, before any refresh hands us a new
    // list), a blind rebuild every render would bring the deleted row back. We only re-project when
    // the parent hands us a genuinely new list.
    private IReadOnlyList<ReleaseDto>? _projectedReleases;

    // Re-project rows only when the parent supplies a genuinely new release list (reference change).
    // Local edits to _rows (a removed row after delete) must survive re-renders triggered by the
    // same Releases instance.
    protected override void OnParametersSet()
    {
        if (!ReferenceEquals(_projectedReleases, Releases))
        {
            _projectedReleases = Releases;
            _rows = Releases.Select(r => new AlbumRow { Release = r }).ToList();
        }
    }

    // Relative path — resolves against the Manager's own origin, proxied by ImageProxyController.
    private static string ThumbUrl(string imagePath) =>
        $"/api/image/{Uri.EscapeDataString(imagePath)}";

    // Medium → Type-chip display label for non-Cut media. Cut rows show ReleaseType instead.
    // One entry per non-Cut medium; a future medium adds one line here, no markup change needed.
    private static readonly IReadOnlyDictionary<ReleaseMedium, string> MediumTypeLabels =
        new Dictionary<ReleaseMedium, string>
        {
            [ReleaseMedium.Session] = "Session",
            [ReleaseMedium.Mix]     = "DJ Mix",
        };

    private async Task ToggleExpand(AlbumRow row)
    {
        row.IsExpanded = !row.IsExpanded;
        if (row.IsExpanded && row.Tracks is null && !row.IsLoading)
        {
            row.IsLoading = true;
            StateHasChanged();
            row.Tracks = await LoadTracksAsync(row.Release.Title);
            row.IsLoading = false;
        }
    }

    // Albums are small releases; a single page of 100 always covers the full track list (see brief).
    private async Task<List<TrackDto>> LoadTracksAsync(string albumTitle)
    {
        var result = await CmsTrackService.GetPagedAsync(
            page: 1, pageSize: 100,
            sortColumn: "TrackNumber", sortDescending: false,
            album: albumTitle);

        if (result.Success && result.Value is not null)
        {
            return result.Value.Items.ToList();
        }

        var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
        Snackbar.Add($"Failed to load tracks for '{albumTitle}': {error}", Severity.Error);
        return new List<TrackDto>();
    }

    private async Task ConfirmAndDeleteAlbum(AlbumRow row)
    {
        // Need track IDs to delete; load them if the row was never expanded.
        row.Tracks ??= await LoadTracksAsync(row.Release.Title);
        var tracks = row.Tracks;
        var count = tracks.Count;

        if (count == 0)
        {
            // Orphaned release: every track was soft-deleted earlier, leaving a 0-track row that
            // cannot be cleared by deleting tracks. Delete the release record directly instead.
            await ConfirmAndDeleteEmptyReleaseAsync(row);
            return;
        }

        var confirmed = await DialogService.ShowMessageBox(
            title: "Delete release",
            markupMessage: new MarkupString(
                $"Delete all <strong>{count}</strong> track(s) in <strong>{WebUtility.HtmlEncode(row.Release.Title)}</strong>? This removes metadata and audio for every track."),
            yesText: "Delete all",
            cancelText: "Cancel");

        if (confirmed != true) return;

        row.IsDeleting = true;
        StateHasChanged();

        var failures = 0;
        foreach (var track in tracks)
        {
            try
            {
                var del = await CmsTrackService.DeleteTrackAsync(track.Id);
                if (!del.Success) failures++;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Delete failed for track {TrackId}", track.Id);
                failures++;
            }
        }

        row.IsDeleting = false;

        if (failures == 0)
        {
            Snackbar.Add($"Deleted '{row.Release.Title}'.", Severity.Success);
            _rows.Remove(row);
            await OnReleasesChanged.InvokeAsync();
        }
        else
        {
            Snackbar.Add($"{count - failures} of {count} track(s) deleted; {failures} failed.", Severity.Warning);
            await OnReleasesChanged.InvokeAsync();
        }
        StateHasChanged();
    }

    // Delete an orphaned release (0 live tracks) via the release endpoint. Mirrors the track-cascade
    // delete path's row lifecycle: confirm, guard with IsDeleting, then remove the row and notify the
    // parent so its release list stays in sync with what is shown.
    private async Task ConfirmAndDeleteEmptyReleaseAsync(AlbumRow row)
    {
        var confirmed = await DialogService.ShowMessageBox(
            title: "Delete release",
            markupMessage: new MarkupString(
                $"<strong>{WebUtility.HtmlEncode(row.Release.Title)}</strong> has no tracks. Delete this empty release record?"),
            yesText: "Delete",
            cancelText: "Cancel");

        if (confirmed != true) return;

        row.IsDeleting = true;
        StateHasChanged();

        var result = await CmsTrackService.DeleteReleaseAsync(row.Release.Id);

        row.IsDeleting = false;

        if (result.Success)
        {
            Snackbar.Add($"Deleted '{row.Release.Title}'.", Severity.Success);
            _rows.Remove(row);
            await OnReleasesChanged.InvokeAsync();
        }
        else
        {
            var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
            Snackbar.Add($"Delete failed: {error}", Severity.Error);
        }
        StateHasChanged();
    }

    private sealed class AlbumRow
    {
        public required ReleaseDto Release { get; init; }
        public List<TrackDto>? Tracks { get; set; }   // null = not yet loaded
        public bool IsExpanded { get; set; }
        public bool IsLoading { get; set; }
        public bool IsDeleting { get; set; }

        // Server-projected count from GetReleasesAsync. Drives the Tracks column without a lazy load.
        public int TrackCount => Release.TrackCount;
    }
}
