using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Controls;
using DeepDrftPublic.Client.Services;
using DeepDrftPublic.Client.ViewModels;
using Microsoft.AspNetCore.Components;
using Models.Common;

namespace DeepDrftPublic.Client.Pages;

public partial class TracksView : ComponentBase, IDisposable
{
    private const string PersistKey = "tracks-page";

    [Inject] public required TracksViewModel ViewModel { get; set; }
    [Inject] public required PersistentComponentState PersistentState { get; set; }
    [Inject] public required NavigationManager Navigation { get; set; }
    [CascadingParameter] public required IStreamingPlayerService PlayerService { get; set; }

    // Filter params arrive on the URL: /tracks?album=X, /tracks?genre=Y, /tracks?q=Z. Copied into
    // the ViewModel on init before the first fetch so the gallery renders filtered on direct nav.
    [SupplyParameterFromQuery(Name = "album")] public string? AlbumQuery { get; set; }
    [SupplyParameterFromQuery(Name = "genre")] public string? GenreQuery { get; set; }
    [SupplyParameterFromQuery(Name = "q")] public string? SearchQuery { get; set; }

    private IStreamingPlayerService? _subscribedService;
    private PersistingComponentStateSubscription _persistingSubscription;

    // Ephemeral view-mode selection — presentation-only, not persisted across navigation.
    private GalleryViewMode _viewMode = GalleryViewMode.Grid;

    protected override async Task OnInitializedAsync()
    {
        // Seed filter state from the URL before any fetch or restore decision.
        ViewModel.FilterAlbum = string.IsNullOrWhiteSpace(AlbumQuery) ? null : AlbumQuery;
        ViewModel.FilterGenre = string.IsNullOrWhiteSpace(GenreQuery) ? null : GenreQuery;
        ViewModel.SearchText = string.IsNullOrWhiteSpace(SearchQuery) ? null : SearchQuery;

        // Carry the prerendered page across the prerender -> interactive (WASM) seam.
        // Without this, the WASM pass gets a fresh scoped ViewModel (Page == null),
        // re-renders the skeleton, re-fetches, and replaces the gallery DOM a few
        // seconds in — replaying TrackCard entrance animations. Mirror the dark-mode
        // PersistentComponentState bridge: persist on the way out of prerender,
        // restore on the interactive pass, and only fetch on a miss.
        _persistingSubscription = PersistentState.RegisterOnPersisting(PersistTracks);

        // The prerendered page is always unfiltered. When the URL carries filter params, that
        // restored page is wrong for this view — skip the restore and fetch with the filter.
        if (!ViewModel.HasActiveFilter
            && PersistentState.TryTakeFromJson<PagedResult<TrackDto>>(PersistKey, out var restored)
            && restored is not null)
        {
            ViewModel.Page = restored;
            ViewModel.PageNumber = restored.Page;
        }
        else
        {
            await SetPage(ViewModel.PageNumber);
        }
    }

    protected override void OnParametersSet()
    {
        // The gallery's per-card icons read off the player's live state (CurrentTrack /
        // IsPlaying / IsPaused), which mutates outside this component's render path:
        // the player bar's play/pause/stop/close all change it directly. The cascade is
        // IsFixed, so the provider's re-render never reaches us — subscribe to the
        // multicast side-channel and re-render on every state change.
        if (PlayerService != null && !ReferenceEquals(PlayerService, _subscribedService))
        {
            if (_subscribedService != null)
                _subscribedService.StateChanged -= OnPlayerStateChanged;

            PlayerService.StateChanged += OnPlayerStateChanged;
            _subscribedService = PlayerService;
        }
    }

    private void OnPlayerStateChanged() => InvokeAsync(StateHasChanged);

    private Task PersistTracks()
    {
        // Only persist the unfiltered page. A filtered page restored onto a later plain /tracks
        // visit would show the wrong results, so a filtered render leaves the cache untouched.
        if (ViewModel.Page is not null && !ViewModel.HasActiveFilter)
        {
            PersistentState.PersistAsJson(PersistKey, ViewModel.Page);
        }
        return Task.CompletedTask;
    }

    private async Task SetPage(int newPage)
    {
        var result = await ViewModel.TrackData.GetPage(
            newPage, ViewModel.PageSize, ViewModel.SortBy, ViewModel.IsDescending,
            ViewModel.SearchText, ViewModel.FilterAlbum, ViewModel.FilterGenre);

        if (result is { Success: true, Value: PagedResult<TrackDto> pageResult })
        {
            ViewModel.Page = pageResult;
            ViewModel.PageSize = pageResult.PageSize;
        }
    }

    // Fired by MudTextField after its 400ms DebounceInterval, so only the trailing keystroke in a
    // burst reaches here. Resets to page 1 since the result set changes, then re-fetches with the
    // active filter (search + any album/genre pill compose).
    private async Task OnSearchInput(string? value)
    {
        ViewModel.SearchText = string.IsNullOrWhiteSpace(value) ? null : value;
        ViewModel.PageNumber = 1;
        await SetPage(1);
        StateHasChanged();
    }

    // Clears the album/genre pill and returns to the unfiltered gallery. Updates the URL (drops the
    // query param) and re-fetches in place. SearchText is intentionally left intact — the pill only
    // represents FilterAlbum/FilterGenre, not free-text search, so clearing it must not discard an
    // active search term. Blazor reuses the component on a same-route query change and does not
    // re-run OnInitializedAsync, so the state reset + refetch happen here explicitly rather than
    // relying on re-init.
    private async Task ClearFilter()
    {
        ViewModel.FilterAlbum = null;
        ViewModel.FilterGenre = null;
        ViewModel.PageNumber = 1;

        Navigation.NavigateTo("/tracks");
        await SetPage(1);
        StateHasChanged();
    }

    private async Task PlayTrack(TrackDto track)
    {
        // Resume the current track if it's merely paused; otherwise stream the new selection.
        if (PlayerService.CurrentTrack?.Id == track.Id && PlayerService.IsPaused)
        {
            await PlayerService.TogglePlayPause();
        }
        else
        {
            await PlayerService.SelectTrack(track);
        }
    }

    private async Task PauseTrack(TrackDto track)
    {
        if (PlayerService.CurrentTrack?.Id == track.Id && PlayerService.IsPlaying)
        {
            await PlayerService.TogglePlayPause();
        }
    }

    public void Dispose()
    {
        _persistingSubscription.Dispose();

        if (_subscribedService != null)
        {
            _subscribedService.StateChanged -= OnPlayerStateChanged;
            _subscribedService = null;
        }
    }
}
