using DeepDrftModels.DTOs;
using DeepDrftModels.Enums;
using DeepDrftPublic.Client.Services;
using Microsoft.AspNetCore.Components;

namespace DeepDrftPublic.Client.Pages;

/// <summary>
/// The public archive: a release-cardinal searchable browser over every release across all media
/// (Phase 9 §8.H, decision H2). Replaces the former three-card medium overview. Search (Title /
/// Artist), an enum-driven medium filter, and a genre filter narrow the release list; each card
/// routes to its per-medium detail. Mirrors the <see cref="TracksView"/> seam: the unfiltered first
/// page is bridged across the prerender -> WASM boundary so hydration neither re-fetches nor replays
/// the card entrance animations.
/// </summary>
public partial class ArchiveView : ComponentBase, IDisposable
{
    private const string PersistKey = "archive-releases";

    // A large page covers the full library in one fetch — the archive has no pager, matching the
    // medium galleries (AlbumsView / MediumBrowseBase) which also pull pageSize: 100.
    private const int PageSize = 100;

    [Inject] public required IReleaseDataService ReleaseData { get; set; }
    [Inject] public required ITrackDataService TrackData { get; set; }
    [Inject] public required PersistentComponentState PersistentState { get; set; }

    // Medium filter chips are enum-driven so a fourth medium surfaces a chip from one lookup entry,
    // with no markup fork (Phase 9 extension discipline).
    private static readonly ReleaseMedium[] _media = Enum.GetValues<ReleaseMedium>();

    private bool _loading = true;
    private List<ReleaseDto> _releases = [];
    private List<GenreSummaryDto> _genres = [];

    // null medium == All; null genre == no genre filter. SearchText null/empty == no search.
    private ReleaseMedium? _selectedMedium;
    private string? _selectedGenre;

    private string? SearchText { get; set; }

    private PersistingComponentStateSubscription _persistingSubscription;

    private bool HasActiveFilter =>
        _selectedMedium is not null
        || !string.IsNullOrWhiteSpace(_selectedGenre)
        || !string.IsNullOrWhiteSpace(SearchText);

    protected override async Task OnInitializedAsync()
    {
        _persistingSubscription = PersistentState.RegisterOnPersisting(Persist);

        // The genre chip source is the release-cardinal distinct-genre list (already sourced from the
        // release join — see GetDistinctGenresAsync). It only renders interactively, so it is fetched
        // lazily on the interactive pass rather than persisted.
        if (RendererInfo.IsInteractive)
            await LoadGenres();

        // The prerendered page is always the unfiltered first page. Restore it only when no filter is
        // active; a filtered interactive pass must fetch its own narrowed result instead.
        if (!HasActiveFilter
            && PersistentState.TryTakeFromJson<List<ReleaseDto>>(PersistKey, out var restored)
            && restored is not null)
        {
            _releases = restored;
            _loading = false;
            return;
        }

        await LoadReleases();
    }

    private async Task LoadGenres()
    {
        var result = await TrackData.GetGenres();
        if (result is { Success: true, Value: { } genres })
            _genres = genres;
    }

    private async Task LoadReleases()
    {
        _loading = true;

        var result = await ReleaseData.GetPaged(
            medium: _selectedMedium?.ToString().ToLowerInvariant(),
            page: 1,
            pageSize: PageSize,
            search: SearchText,
            genre: _selectedGenre);

        _releases = result is { Success: true, Value.Items: { } items }
            ? items.ToList()
            : [];

        _loading = false;
    }

    // Fired by MudTextField after its 400ms DebounceInterval, so only the trailing keystroke in a
    // burst reaches here. Re-fetches with the composed filter (search + medium + genre).
    private async Task OnSearchInput(string? value)
    {
        SearchText = string.IsNullOrWhiteSpace(value) ? null : value;
        await LoadReleases();
    }

    private async Task OnMediumSelected(ReleaseMedium? medium)
    {
        _selectedMedium = medium;
        await LoadReleases();
    }

    private async Task OnGenreSelected(string? genre)
    {
        _selectedGenre = string.IsNullOrWhiteSpace(genre) ? null : genre;
        await LoadReleases();
    }

    // Per-medium detail target. Session/Mix open their own detail page; a Cut has no single-release
    // detail page, so it opens the track gallery filtered to its release title — the same destination
    // AlbumsView's Cut cards use, preserving the established navigation.
    private static string DetailHref(ReleaseDto release) => release.Medium switch
    {
        ReleaseMedium.Session => $"/sessions/{release.Id}",
        ReleaseMedium.Mix => $"/mixes/{release.Id}",
        _ => $"/tracks?album={Uri.EscapeDataString(release.Title)}",
    };

    // Display label for a medium filter chip. Centralised so a new medium's label is one entry, not a
    // markup change. "DJ Mix" matches the CMS Type-chip wording (§8.D).
    private static string MediumLabel(ReleaseMedium medium) => medium switch
    {
        ReleaseMedium.Cut => "Cuts",
        ReleaseMedium.Session => "Sessions",
        ReleaseMedium.Mix => "Mixes",
        _ => medium.ToString(),
    };

    private Task Persist()
    {
        // Only the unfiltered first page is safe to restore onto a later plain /archive visit. A
        // filtered render leaves the cache untouched so the bridge never serves narrowed results to an
        // unfiltered load.
        if (_releases.Count > 0 && !HasActiveFilter)
            PersistentState.PersistAsJson(PersistKey, _releases);
        return Task.CompletedTask;
    }

    public void Dispose() => _persistingSubscription.Dispose();
}
