@page "/tracks/sessions"
@inherits CmsMediumBrowserBase<CmsSessionBrowser.SessionRow>
@using DeepDrftModels.DTOs
@using DeepDrftModels.Enums
@using Microsoft.AspNetCore.Components.Forms
@attribute [Authorize]
@inject ILogger<CmsSessionBrowser> Logger

@* Embedded as the SESSIONS tab content of the Release Archive (Phase 9 §8.A), and still routable at
   /tracks/sessions for direct-URL access. The grid is the rich CmsAlbumBrowser filtered to Sessions
   (§8.C parity: expand-tracks, delete, Type chip, per-row edit), with the Session hero upload supplied
   as its medium-specific special-action column so that affordance survives the move off the thin table.
   When embedded, the page chrome (title, container, the now-meaningless "Back to Release Archive"
   button) is suppressed; the standalone route keeps it. The hero affordance (9.5.E) is preserved in
   both contexts. *@
@if (Embedded)
{
    @GridContent
}
else
{
    <PageTitle>Sessions — Deep DRFT Management</PageTitle>

    <MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
        <MudButton Variant="Variant.Text"
                   StartIcon="@Icons.Material.Filled.ArrowBack"
                   Href="/releases"
                   Class="mb-4">
            Back to Releases
        </MudButton>

        <MudText Typo="Typo.h4" GutterBottom="true">Sessions</MudText>

        @GridContent
    </MudContainer>
}

@code {
    /// <summary>
    /// True when rendered as tab content inside the Release Archive; suppresses the standalone page
    /// chrome (title, container, back button). False (default) renders the full routable page.
    /// </summary>
    [Parameter] public bool Embedded { get; set; }

    /// <summary>
    /// Forwarded from the inner <see cref="CmsAlbumBrowser"/>: fires after any per-row waveform
    /// generate succeeds so the parent page can refresh its catalogue-wide missing-count badges.
    /// </summary>
    [Parameter] public EventCallback OnWaveformGenerated { get; set; }

    private CmsAlbumBrowser? _albumBrowser;

    protected override ReleaseMedium Medium => ReleaseMedium.Session;
    protected override string MediumNoun => "sessions";

    /// <summary>
    /// Clears the inner grid's cached per-track waveform status so the next row expand re-fetches.
    /// Called by the parent page after a catalogue-wide bulk run.
    /// </summary>
    public Task InvalidateWaveformStatusAsync() =>
        _albumBrowser?.InvalidateWaveformStatusAsync() ?? Task.CompletedTask;

    protected override SessionRow ToRow(ReleaseDto release) => new()
    {
        Release = release,
        HeroImageEntryKey = release.SessionMetadata?.HeroImageEntryKey
    };

    protected override ReleaseDto ReleaseOf(SessionRow row) => row.Release;

    // The grid itself — identical in the embedded and standalone contexts. Defined once as a fragment so
    // both branches above render the same markup without duplication. The Session declares one dedicated
    // "Hero" special-action column; the grid renders it between Tracks and Actions, handing the cell each
    // release, and RowFor recovers the matching SessionRow's upload state.
    private RenderFragment GridContent => @<CmsAlbumBrowser @ref="_albumBrowser"
                                                           Releases="Releases"
                                                           IsLoading="Loading"
                                                           OnReleasesChanged="ReloadAsync"
                                                           OnWaveformGenerated="OnWaveformGenerated"
                                                           SpecialColumns="_specialColumns" />;

    // Allocated once per component instance in OnInitialized (field initializers cannot reference
    // instance members, so initialization is deferred to the first lifecycle hook).
    private IReadOnlyList<SpecialActionColumn> _specialColumns = Array.Empty<SpecialActionColumn>();

    protected override void OnInitialized()
    {
        _specialColumns = new[]
        {
            new SpecialActionColumn("Hero", HeroThumbCell),
            new SpecialActionColumn("", HeroButtonCell),
        };
        base.OnInitialized();
    }

    // Per-row cell for the "Hero" thumbnail column: just the image preview div.
    private RenderFragment<ReleaseDto> HeroThumbCell => release =>@<text>
        @{ var row = RowFor(release); }
        @if (row is not null)
        {
            @if (row.HeroImageEntryKey is { Length: > 0 } heroKey)
            {
                <div class="cms-album-thumb" style="background-image: url('@ThumbUrl(heroKey)');"></div>
            }
            else
            {
                <div class="cms-album-thumb cms-album-thumb--fallback"></div>
            }
        }
    </text>;

    // Per-row cell for the "Hero Image" upload button column: set/replace upload button with progress.
    private RenderFragment<ReleaseDto> HeroButtonCell => release =>@<text>
        @{ var row = RowFor(release); }
        @if (row is not null)
        {
            <MudFileUpload T="IBrowserFile"
                           Accept="image/*"
                           FilesChanged="@(file => UploadHeroAsync(row, file))"
                           Disabled="@row.IsUploading">
                <ActivatorContent>
                    <MudButton Variant="Variant.Outlined"
                               Size="Size.Small"
                               StartIcon="@Icons.Material.Filled.Image"
                               Disabled="@row.IsUploading">
                        @if (row.IsUploading)
                        {
                            <MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
                            <span>Uploading…</span>
                        }
                        else
                        {
                            <span>@(row.HeroImageEntryKey is { Length: > 0 } ? "Replace hero" : "Set hero")</span>
                        }
                    </MudButton>
                </ActivatorContent>
            </MudFileUpload>
        }
    </text>;

    private async Task UploadHeroAsync(SessionRow row, IBrowserFile? file)
    {
        if (file is null) return;
        row.IsUploading = true;
        StateHasChanged();
        try
        {
            await using var stream = file.OpenReadStream(maxAllowedSize: 50_000_000);
            var result = await CmsReleaseService.UploadSessionHeroImageAsync(
                row.Release.Id, stream, file.Name, file.ContentType);

            if (result.Success)
            {
                // The endpoint returns no payload; the entry key is server-generated. Re-fetch the
                // release so the hero thumbnail reflects the new key without guessing it.
                var refreshed = await CmsReleaseService.GetByIdAsync(row.Release.Id);
                if (refreshed.Success && refreshed.Value is { } release)
                {
                    row.Release = release;
                    row.HeroImageEntryKey = release.SessionMetadata?.HeroImageEntryKey;
                }
                Snackbar.Add($"Hero image set for '{row.Release.Title}'.", Severity.Success);
            }
            else
            {
                var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
                Snackbar.Add($"Hero image upload failed: {error}", Severity.Error);
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Hero image upload failed for release {ReleaseId}", row.Release.Id);
            Snackbar.Add("Hero image upload failed — please try again.", Severity.Error);
        }
        finally
        {
            row.IsUploading = false;
            StateHasChanged();
        }
    }

    public sealed class SessionRow
    {
        public required ReleaseDto Release { get; set; }
        public string? HeroImageEntryKey { get; set; }
        public bool IsUploading { get; set; }
    }
}
