@page "/auteurs/{AuteurId:int}/bibliographie"
@inject ServiceLivresApi Api
@inject EtatReseau Reseau

<PageTitle>MaBibli — bibliographie</PageTitle>

@if (_bibliographie is null && _erreur is null)
{
    <h1 class="titre-page">Bibliographie</h1>
    <p class="message-discret">Interrogation de la BnF…</p>
}
else if (_erreur is not null)
{
    <h1 class="titre-page">Bibliographie</h1>
    <p class="message-erreur" role="alert">@_erreur</p>

    @* Une panne de réseau est le plus souvent passagère : sans ce bouton, il fallait
       recharger la page entière pour retenter. *@
    @if (_reessayable)
    {
        <div class="actions-formulaire">
            <button type="button" class="bouton bouton-principal"
                    disabled="@(_chargement || !Reseau.EnLigne)"
                    title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
                    @onclick="ChargerAsync">
                @(_chargement ? "Nouvelle tentative…" : "Réessayer")
            </button>
        </div>
    }
}
else if (_bibliographie is { } biblio)
{
    <h1 class="titre-page">
        @if (biblio.EstNouveautes)
        {
            <text>Nouveautés de @biblio.Auteur.Nom</text>
        }
        else
        {
            @biblio.Auteur.Nom
        }
    </h1>

    @*
        ⚠️ Les deux cas ci-dessous s'excluent, et c'est TOUT l'objet de ce bloc.

        Avant correction, une BnF muette produisait une liste vide, que l'écran commentait
        d'un rassurant « la BnF ne connaît aucun livre de cet auteur » — affiché juste sous
        l'avertissement qui disait le contraire. On affirmait une absence qu'on n'avait pas
        pu constater, ce qui est la pire des deux erreurs possibles ici : elle ferme la
        question au lieu de la laisser ouverte.

        La règle : on n'explique une absence de résultats QUE si la source a répondu.
    *@
    @if (biblio.SourceMuette)
    {
        <p class="message-avertissement" role="alert">
            @biblio.Avertissement
        </p>

        <p class="message-discret">
            La bibliographie n'a donc <strong>pas pu être établie</strong> : cet écran ne dit
            rien de ce que cet auteur a écrit.
        </p>

        <div class="actions-formulaire">
            <button type="button" class="bouton bouton-principal"
                    disabled="@(_chargement || !Reseau.EnLigne)"
                    title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
                    @onclick="ChargerAsync">
                @(_chargement ? "Nouvelle tentative…" : "Réessayer")
            </button>
        </div>
    }
    else if (biblio.Oeuvres.Count == 0)
    {
        @if (biblio.EstNouveautes && biblio.SeuilNouveautesInconnu)
        {
            <p class="message-discret">
                Impossible de dater le livre le plus récent que vous possédez dans les notices
                reçues. La BnF a répondu, mais aucune nouveauté fiable ne peut être déduite.
            </p>
        }
        else if (biblio.EstNouveautes)
        {
            <p class="message-discret">Aucune nouveauté trouvée depuis @biblio.AnneeSeuilNouveautes.</p>
        }
        else
        {
            @* La source a répondu, et elle ne connaît rien : là, et là seulement, on peut
               l'expliquer. *@
            <p class="message-discret">
                La BnF ne connaît aucun livre de cet auteur. C'est le cas attendu pour les auteurs
                étrangers : le dépôt légal français ne couvre que l'édition francophone, et un
                auteur traduit n'y a souvent pas de notice à son nom.
            </p>
        }
    }
    else if (biblio.Avertissement is not null)
    {
        @* Source répondue mais bancale (une page sur deux, par exemple) : la liste vaut
           quelque chose, on la montre en disant qu'elle est peut-être incomplète. *@
        <p class="message-avertissement" role="alert">@biblio.Avertissement</p>
    }
    else
    {
        @* Ces compteurs disent à l'utilisateur CE QU'IL REGARDE. Sur un auteur très réédité,
           la liste n'est qu'un extrait : le taire ferait croire qu'un livre absent n'existe pas. *@
        <p class="compteur">
            @biblio.Oeuvres.Count œuvre@(biblio.Oeuvres.Count > 1 ? "s" : "")
            — @_possedees déjà chez vous
            @if (_masquees > 0)
            {
                <text> — @_masquees masquée@(_masquees > 1 ? "s" : "")</text>
            }
        </p>

        @if (biblio.EstNouveautes)
        {
            <p class="message-discret">
                Œuvres parues après @biblio.AnneeSeuilNouveautes, d'après les éditions BnF reçues.
            </p>
        }

        @* Deux phrases distinctes plutôt qu'une seule à trous : enchaîner des fragments
           conditionnels dans une même phrase produit une ponctuation bancale dès qu'un des
           fragments manque (« exister. .34 notices ont été écartées »). *@
        <p class="message-discret">
            @if (biblio.Tronquee)
            {
                <text>
                    D'après le catalogue de la BnF, à partir de @biblio.NoticesLues notices
                    sur les <strong>@biblio.NoticesAnnoncees</strong> qu'elle annonce :
                    <strong>cette liste est un extrait</strong>, classé par pertinence.
                    Un livre absent d'ici peut très bien exister.
                </text>
            }
            else
            {
                <text>
                    D'après le catalogue de la BnF, sur
                    @biblio.NoticesLues notice@(biblio.NoticesLues > 1 ? "s" : "").
                </text>
            }
        </p>

        @if (biblio.NoticesEcartees > 0)
        {
            <p class="message-discret">
                @biblio.NoticesEcartees notice@(biblio.NoticesEcartees > 1 ? "s ont" : " a")
                été écartée@(biblio.NoticesEcartees > 1 ? "s" : "") : autre support (livre audio,
                jeu) ou ouvrage signé de quelqu'un d'autre.
            </p>
        }

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

        <div class="actions-formulaire">
            <button type="button" class="bouton bouton-discret"
                    @onclick="() => _filtresOuverts = !_filtresOuverts"
                    aria-expanded="@_filtresOuverts">
                Filtrer (@NombreFiltres)
            </button>
        </div>
        @if (_filtresOuverts)
        {
            <div class="filtres">
                <div class="segments" role="group" aria-label="Possession">
                    <button type="button" class="segment @(_modePossession == 0 ? "segment-actif" : null)"
                            @onclick="() => _modePossession = 0">Tout</button>
                    <button type="button" class="segment @(_modePossession == 1 ? "segment-actif" : null)"
                            @onclick="() => _modePossession = 1">Ceux que j'ai déjà</button>
                    <button type="button" class="segment @(_modePossession == 2 ? "segment-actif" : null)"
                            @onclick="() => _modePossession = 2">Ce qui me manque</button>
                </div>
                @if (_masquees > 0)
                {
                    <div class="segments">
                        <button type="button" class="segment @(_afficherMasquees ? "segment-actif" : null)"
                                @onclick="() => _afficherMasquees = true">
                            Afficher les masquées (@_masquees)
                        </button>
                        <button type="button" class="segment @(!_afficherMasquees ? "segment-actif" : null)"
                                @onclick="() => _afficherMasquees = false">Masquer les masquées</button>
                    </div>
                }
            </div>
        }

        <ul class="liste-bibliographie">
            @foreach (var oeuvre in Affichees(biblio))
            {
                @* La classe « oeuvre-possedee » grise la ligne : le livre est déjà sur l'étagère. *@
                <li class="carte-oeuvre @(oeuvre.Possede ? "oeuvre-possedee" : null)" @key="oeuvre.Titre">
                    @if (oeuvre is { Possede: true, LivreId: { } livreId }
                         && _livresCatalogue.FirstOrDefault(l => l.Id == livreId) is { } livre)
                    {
                        <CarteLivre Livre="livre" />
                    }
                    @if (!oeuvre.Possede || !_livresCatalogue.Any(l => l.Id == oeuvre.LivreId))
                    {
                    <div class="carte-corps">
                        <p class="carte-titre">
                            @if (oeuvre is { Possede: true, LivreId: { } livreIdPossede })
                            {
                                <a class="lien-auteur" href="@($"livres/{livreIdPossede}")">@oeuvre.Titre</a>
                            }
                            else
                            {
                                <button type="button" class="carte-titre oeuvre-titre-bouton"
                                        @onclick="() => BasculerOeuvre(oeuvre.Titre)"
                                        aria-expanded="@_oeuvresOuvertes.Contains(oeuvre.Titre)">
                                    @oeuvre.Titre
                                </button>
                            }
                        </p>

                        <p class="carte-details">
                            @if (!string.IsNullOrWhiteSpace(oeuvre.Annee))
                            {
                                <span>@oeuvre.Annee</span>
                            }
                            @if (!string.IsNullOrWhiteSpace(oeuvre.Editeur))
                            {
                                <span>@oeuvre.Editeur</span>
                            }
                            @if (oeuvre.NombreEditions > 1)
                            {
                                <span class="etiquette">@oeuvre.NombreEditions éditions</span>
                            }

                            @if (oeuvre.Possede)
                            {
                                <span class="etiquette etiquette-possede">Déjà chez vous</span>
                            }
                            else if (oeuvre.Souhaite)
                            {
                                <span class="etiquette etiquette-souhaite">Dans vos envies</span>
                            }
                        </p>
                    </div>
                    @if (_oeuvresOuvertes.Contains(oeuvre.Titre))
                    {
                        <p class="message-discret detail-oeuvre">
                            @if (!string.IsNullOrWhiteSpace(oeuvre.Isbn))
                            {
                                <span>ISBN : @FormatageIsbn.Afficher(oeuvre.Isbn)</span>
                            }
                            @if (oeuvre.NombreEditions > 1)
                            {
                                <span>@oeuvre.NombreEditions éditions recensées</span>
                            }
                        </p>
                    }
                    }

                    @if (oeuvre.ADecouvrir)
                    {
                        <div class="actions-formulaire">
                            <label class="selection-oeuvre">
                                <input type="checkbox"
                                       checked="@_selection.Contains(oeuvre.Titre)"
                                       @onchange="e => Selectionner(oeuvre.Titre, e.Value is true)" />
                                Sélectionner
                            </label>
                            <button type="button" class="bouton bouton-compact bouton-principal"
                                    disabled="@_enCours" @onclick="() => SouhaiterAsync(oeuvre)"
                                    aria-label="@($"Ajouter « {oeuvre.Titre} » à ma liste d'envies")">
                                Je le veux
                            </button>
                        </div>
                    }

                    @if (!oeuvre.Masquee)
                    {
                        <button type="button" class="bouton bouton-compact bouton-discret"
                                disabled="@_enCours" @onclick="() => MasquerAsync(oeuvre)">
                            Masquer
                        </button>
                    }
                    else
                    {
                        <button type="button" class="bouton bouton-compact bouton-discret"
                                disabled="@_enCours" @onclick="() => DemasquerAsync(oeuvre)">
                            Réafficher
                        </button>
                    }
                </li>
            }
        </ul>

        @if (_selection.Count > 0)
        {
            <div class="actions-flottantes">
                <button type="button" class="bouton bouton-principal"
                        disabled="@(_enCours || !Reseau.EnLigne)"
                        title="@(Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne)"
                        @onclick="AjouterSelectionAsync">
                    Ajouter la sélection aux envies (@_selection.Count)
                </button>
            </div>
        }

        @* Le rapprochement est fait par titre, et il rate des choses (voir CleOeuvre). Le dire
           franchement vaut mieux que laisser l'utilisateur découvrir seul qu'un livre qu'il
           possède n'est pas grisé. *@
        <p class="message-discret">
            Le rapprochement avec votre bibliothèque se fait <strong>par titre</strong>. Un livre
            que vous possédez sous un titre différent (traduction, intégrale, édition retitrée)
            n'apparaîtra donc pas comme possédé. Rien n'est masqué : à vous de trancher.
        </p>
    }
}

@* « Retour aux auteurs » est parti : l'onglet Auteurs fait exactement cela, en permanence.
   Ne reste que ce qu'aucun onglet ne sait faire — le catalogue restreint à cet auteur. *@
@if (_bibliographie is { } b)
{
    <nav class="actions-flottantes">
        @if (!b.EstNouveautes)
        {
            <a class="bouton bouton-discret"
               href="@($"auteurs/{b.Auteur.Id}/bibliographie?nouveautes=true")">
                Nouveautés
            </a>
        }
        else
        {
            <a class="bouton bouton-discret" href="@($"auteurs/{b.Auteur.Id}/bibliographie")">
                Toute la bibliographie
            </a>
        }
        <a class="bouton bouton-discret" href="@($"?auteur={b.Auteur.Id}")">Ses livres chez vous</a>
    </nav>
}

@code {
    [Parameter]
    public int AuteurId { get; set; }

    [SupplyParameterFromQuery(Name = "nouveautes")]
    public bool Nouveautes { get; set; }

    private BibliographieDto? _bibliographie;
    private int _modePossession;
    private bool _filtresOuverts;
    private bool _afficherMasquees;
    private bool _enCours;
    private bool _chargement;

    /// <summary>Vrai quand l'échec vient du réseau, donc qu'une nouvelle tentative a un sens.</summary>
    private bool _reessayable;

    private string? _erreur;
    private string? _message;

    private int _possedees;
    private int _masquees;
    private IReadOnlyList<LivreDto> _livresCatalogue = [];
    private readonly HashSet<string> _oeuvresOuvertes = [];
    private readonly HashSet<string> _selection = [];

    protected override async Task OnParametersSetAsync() => await ChargerAsync();

    private IEnumerable<OeuvreBibliographie> Affichees(BibliographieDto biblio) =>
        biblio.Oeuvres.Where(o =>
            (_afficherMasquees || !o.Masquee)
            && (_modePossession == 0
                || (_modePossession == 1 && o.Possede)
                || (_modePossession == 2 && o.ADecouvrir)));

    private int NombreFiltres =>
        (_modePossession == 0 ? 0 : 1) + (_masquees > 0 && !_afficherMasquees ? 1 : 0);

    private async Task ChargerAsync()
    {
        _erreur = null;
        _reessayable = false;
        _chargement = true;

        try
        {
            _bibliographie = Nouveautes
                ? await Api.ObtenirNouveautesAsync(AuteurId)
                : await Api.ObtenirBibliographieAsync(AuteurId);
            _livresCatalogue = await Api.ListerAsync(new CritereLivres());

            if (_bibliographie is null)
            {
                // Celui-là ne se retente pas : l'auteur n'existe pas, réessayer ne le fera
                // pas apparaître.
                _erreur = "Cet auteur n'existe pas dans votre bibliothèque.";
                return;
            }

            _possedees = _bibliographie.Oeuvres.Count(o => o.Possede);
            _masquees = _bibliographie.Oeuvres.Count(o => o.Masquee);
        }
        catch (Exception)
        {
            // ⚠️ Jamais le message brut de l'exception : hors-ligne, c'est un « 404 Not Found »
            // du service worker que l'utilisateur n'a aucun moyen d'interpréter. La
            // bibliographie interroge la BnF, elle ne peut PAS venir du cache — il n'y a rien
            // à réparer, seulement à dire.
            //
            // Ce chemin-ci est l'échec de l'appel à NOTRE serveur (délai du navigateur compris) ;
            // une BnF muette, elle, revient en 200 avec un état, et se traite plus haut.
            _erreur = Reseau.EnLigne
                ? "La bibliographie n'a pas pu être obtenue. La BnF est parfois lente à répondre."
                : "La bibliographie vient de la BnF : indisponible hors ligne.";

            _reessayable = true;
        }
        finally
        {
            _chargement = false;
        }
    }

    private void Selectionner(string titre, bool selectionnee)
    {
        if (selectionnee)
        {
            _selection.Add(titre);
        }
        else
        {
            _selection.Remove(titre);
        }
    }

    private async Task AjouterSelectionAsync()
    {
        if (_bibliographie is null)
        {
            return;
        }

        var saisies = _bibliographie.Oeuvres
            .Where(o => _selection.Contains(o.Titre) && o.ADecouvrir)
            .Select(o => new EnregistrementSouhait
            {
                Titre = o.Titre,
                Auteur = _bibliographie.Auteur.Nom,
                Editeur = o.Editeur,
                Annee = o.Annee,
                Isbn = o.Isbn,
            })
            .ToList();

        _enCours = true;
        _message = null;
        var resultats = await Api.AjouterSouhaitsAsync(saisies);
        _enCours = false;
        var erreurs = resultats.Where(r => !r.EstOk).Select(r => r.Erreur).Where(e => e is not null).ToList();
        _message = erreurs.Count == 0 ? null : string.Join(" ", erreurs);
        _selection.Clear();
        await ChargerAsync();
    }

    private async Task MasquerAsync(OeuvreBibliographie oeuvre)
    {
        await ModifierMasquageAsync(oeuvre, false);
    }

    private async Task DemasquerAsync(OeuvreBibliographie oeuvre)
    {
        await ModifierMasquageAsync(oeuvre, true);
    }

    private async Task ModifierMasquageAsync(OeuvreBibliographie oeuvre, bool demasquer)
    {
        _enCours = true;
        _message = demasquer
            ? await Api.DemasquerOeuvreBibliographieAsync(AuteurId, oeuvre.Titre)
            : await Api.MasquerOeuvreBibliographieAsync(AuteurId, oeuvre.Titre);
        _enCours = false;
        if (_message is null)
        {
            await ChargerAsync();
        }
    }

    private void BasculerOeuvre(string titre)
    {
        if (!_oeuvresOuvertes.Add(titre))
        {
            _oeuvresOuvertes.Remove(titre);
        }
    }

    /// <summary>
    /// Ajoute une œuvre à la liste d'envies, en emportant ce que la BnF en sait.
    /// </summary>
    /// <remarks>
    /// L'auteur enregistré est celui de l'écran, pas celui de la notice : c'est lui que
    /// l'utilisateur a en tête, et c'est sous ce nom qu'il retrouvera l'envie dans l'export.
    /// <para>
    /// Après l'ajout, la bibliographie est <b>relue</b> plutôt que corrigée en mémoire : c'est
    /// le serveur qui sait rapprocher l'envie de l'œuvre, et refaire ce calcul ici le
    /// dupliquerait — avec le risque qu'il diverge.
    /// </para>
    /// </remarks>
    private async Task SouhaiterAsync(OeuvreBibliographie oeuvre)
    {
        if (_bibliographie is null)
        {
            return;
        }

        _enCours = true;
        _message = null;

        var resultat = await Api.AjouterSouhaitAsync(new EnregistrementSouhait
        {
            Titre = oeuvre.Titre,
            Auteur = _bibliographie.Auteur.Nom,
            Editeur = oeuvre.Editeur,
            Annee = oeuvre.Annee,

            // L'ISBN de la BnF est celui d'UNE édition relevée, souvent à 10 chiffres et parfois
            // épuisée. Il est repris tel quel : il aide à commander, sans engager le choix.
            Isbn = oeuvre.Isbn,
        });

        _enCours = false;

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

        await ChargerAsync();
    }
}
