@page "/"
@inject ServiceLivresApi Api
@implements IDisposable

<PageTitle>MaBibli — catalogue</PageTitle>

<h1 class="titre-page">Catalogue</h1>

@if (_auteur is not null)
{
    @* Le « cliquer sur un auteur » d'IDEES.md : le catalogue est ici restreint à ses livres. *@
    <p class="message-discret">
        Livres de <strong>@_auteur.Nom</strong>.
        <a href="">Revenir à toute la bibliothèque</a>
    </p>
}

<div class="barre-recherche">
    <input class="champ-saisie" type="search" placeholder="Titre ou auteur…"
           value="@_recherche" @oninput="SurRecherche" aria-label="Rechercher un livre" />
</div>

<div class="filtres">
    <div class="segments" role="group" aria-label="Filtrer par format">
        <button type="button" class="segment @(_format is null ? "segment-actif" : null)"
                @onclick="() => FiltrerFormat(null)">Tous</button>
        @foreach (var format in Enum.GetValues<Format>())
        {
            <button type="button" class="segment @(_format == format ? "segment-actif" : null)"
                    @onclick="() => FiltrerFormat(format)">@Libelles.Format(format)</button>
        }
    </div>

    @* Ce filtre porte sur MON statut, pas sur celui du foyer : un livre sans statut pour moi
       ne remonte sous aucune valeur, il reste visible sous « Tous ». *@
    <div class="segments" role="group" aria-label="Filtrer par mon statut de lecture">
        <button type="button" class="segment @(_statut is null ? "segment-actif" : null)"
                @onclick="() => FiltrerStatut(null)">Tous</button>
        @foreach (var statut in Enum.GetValues<Statut>())
        {
            <button type="button" class="segment @(_statut == statut ? "segment-actif" : null)"
                    @onclick="() => FiltrerStatut(statut)">@Libelles.Statut(statut)</button>
        }
    </div>
</div>

@if (_erreur is not null)
{
    <p class="message-erreur" role="alert">@_erreur</p>
}

@if (_chargement && _livres is null)
{
    <p class="message-discret">Chargement…</p>
}
else if (_livres is { Count: 0 })
{
    <p class="message-discret">
        @(EstFiltre ? "Aucun livre ne correspond à cette recherche." : "La bibliothèque est vide. Ajoutez un premier livre.")
    </p>
}
else if (_livres is not null)
{
    <p class="compteur">@_livres.Count livre@(_livres.Count > 1 ? "s" : "")</p>

    <ul class="liste-livres">
        @foreach (var livre in _livres)
        {
            <li class="carte-livre" @key="livre.Id">
                <Couverture Url="@livre.CoverUrl" Titre="@livre.Titre" Classe="couverture-petite" />

                <div class="carte-corps">
                    <a class="carte-titre" href="@($"livres/{livre.Id}")">@livre.Titre</a>

                    @if (livre.Auteurs.Count > 0)
                    {
                        <p class="carte-auteur">
                            @foreach (var (auteur, index) in livre.Auteurs.Select((a, i) => (a, i)))
                            {
                                @(index > 0 ? ", " : "")
                                <a class="lien-auteur" href="@($"?auteur={auteur.Id}")">@auteur.Nom</a>
                            }
                        </p>
                    }

                    <p class="carte-details">
                        <span class="etiquette">@Libelles.Format(livre.Format)</span>
                        @if (!string.IsNullOrWhiteSpace(livre.Editeur))
                        {
                            <span>@livre.Editeur</span>
                        }
                    </p>

                    @* Changement de statut : action la plus fréquente, donc directement dans la liste. *@
                    <ChoixStatut Valeur="livre.Statut"
                                 ValeurChanged="s => ChangerStatut(livre, s)"
                                 Desactive="_statutEnCours == livre.Id"
                                 Compact="true" />
                </div>
            </li>
        }
    </ul>
}

<nav class="actions-flottantes">
    <a class="bouton bouton-principal" href="ajout/isbn">Ajouter par ISBN</a>
    <a class="bouton bouton-discret" href="ajout/manuel">Saisie manuelle</a>
    <a class="bouton bouton-discret" href="auteurs">Auteurs</a>
</nav>

@code {
    /// <summary>Restreint le catalogue à un auteur. Vient de « ?auteur=… », posé par les liens de la liste.</summary>
    [SupplyParameterFromQuery(Name = "auteur")]
    public int? AuteurId { get; set; }

    private IReadOnlyList<LivreDto>? _livres;
    private AuteurDto? _auteur;
    private int? _auteurCharge;
    private string _recherche = string.Empty;
    private Format? _format;
    private Statut? _statut;
    private bool _chargement;
    private string? _erreur;
    private int? _statutEnCours;

    // Une frappe au clavier annule la recherche précédente encore en vol.
    private CancellationTokenSource _cts = new();

    private bool EstFiltre =>
        !string.IsNullOrWhiteSpace(_recherche) || _format is not null || _statut is not null
        || AuteurId is not null;

    protected override async Task OnParametersSetAsync()
    {
        if (_auteurCharge != AuteurId)
        {
            _auteurCharge = AuteurId;
            _auteur = AuteurId is { } id ? await Api.ObtenirAuteurAsync(id) : null;
        }

        await ChargerAsync();
    }

    private async Task SurRecherche(ChangeEventArgs e)
    {
        _recherche = e.Value?.ToString() ?? string.Empty;
        await ChargerAsync();
    }

    private Task FiltrerFormat(Format? format)
    {
        _format = format;
        return ChargerAsync();
    }

    private Task FiltrerStatut(Statut? statut)
    {
        _statut = statut;
        return ChargerAsync();
    }

    private async Task ChargerAsync()
    {
        _cts.Cancel();
        _cts.Dispose();
        _cts = new CancellationTokenSource();
        var jeton = _cts.Token;

        _chargement = true;
        _erreur = null;

        try
        {
            var criteres = new CritereLivres
            {
                Recherche = _recherche,
                Format = _format,
                Statut = _statut,
                AuteurId = AuteurId,
            };

            _livres = await Api.ListerAsync(criteres, jeton);
        }
        catch (OperationCanceledException)
        {
            return; // Une recherche plus récente a pris le relais.
        }
        catch (Exception ex)
        {
            _erreur = $"Impossible de charger le catalogue : {ex.Message}";
        }
        finally
        {
            if (!jeton.IsCancellationRequested)
            {
                _chargement = false;
            }
        }
    }

    private async Task ChangerStatut(LivreDto livre, Statut? statut)
    {
        if (livre.Statut == statut)
        {
            return;
        }

        _statutEnCours = livre.Id;
        var resultat = await Api.ChangerStatutAsync(livre.Id, statut);
        _statutEnCours = null;

        if (!resultat.EstOk)
        {
            _erreur = resultat.Erreur;
            return;
        }

        if (_statut is not null)
        {
            // Un filtre par statut est actif : le livre vient peut-être de sortir de la sélection,
            // il faut donc recharger plutôt que corriger la liste en place.
            await ChargerAsync();
            return;
        }

        // Remplacement local : évite de recharger toute la liste pour un seul champ.
        _livres = _livres!.Select(l => l.Id == livre.Id ? resultat.Valeur! : l).ToList();
    }

    public void Dispose()
    {
        _cts.Cancel();
        _cts.Dispose();
    }
}
