@page "/series/{Id:int}"
@page "/series/{Id:int}/edition"
@inject ServiceLivresApi Api
@inject NavigationManager Navigation
@inject EtatReseau Reseau
@implements IDisposable

<PageTitle>MaBibli — @(_serie?.Titre ?? "série")</PageTitle>

@if (_serie is null)
{
    <p class="message-discret" role="status">
        @(_chargement ? "Chargement…" : "Cette série n'est pas dans les données enregistrées sur cet appareil.")
    </p>
}
else
{
    @if (!ModeEdition && _parente is { } parente)
    {
        @if (!ModeEdition)
        {
        <p class="message-discret">
            Fait partie de <a href="@($"series/{parente.Id}")">@parente.Titre</a>.
        </p>
        }
    }

    <h1 class="titre-page">@_serie.Titre</h1>

    <p class="message-discret">
        @if (_serie.Elements.Count == 0)
        {
            <text>Aucun tome recensé. Ajoutez ceux que vous possédez, et ceux qui vous manquent.</text>
        }
        else
        {
            var total = Total(_serie);
            <text>@total.Possedes sur @total.Total tomes dans la bibliothèque.</text>
        }
    </p>

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

    @* La bascule vers les envies ne change rien à l'écran : sans un mot, on ne saurait pas
       qu'elle a eu lieu, et on cliquerait deux fois. *@
    @if (_message is not null)
    {
        <p class="message-avertissement" role="status">@_message</p>
    }

    @* L'arbre est rendu récursivement ; le chemin protège le rendu si une base ancienne contient
       malgré tout une boucle. *@
    @if (!ModeEdition && _filles.Count > 0)
    {
        <h2 class="titre-section">Séries de ce cycle</h2>
        <ul class="liste-sous-series">
            @foreach (var fille in _filles)
            {
                <ArbreSerie Serie="fille" Toutes="_toutes" Livres="_livresCatalogue"
                            Totaux="_totaux" Deplies="_deplies"
                            Chemin="@CheminRacine"
                            ActionsActives="Reseau.EnLigne" MotifBlocage="@MotifBlocage"
                            OnDeplacer="DeplacerSousSerieAsync" />
            }
        </ul>
    }

    @if (!ModeEdition && _serie.Elements.Count > 0)
    {
        <h2 class="titre-section">Ordre de lecture</h2>

        @*
            ⚠️ L'ordre est STOCKÉ, jamais déduit d'une année de publication : une préquelle se
            lit avant le livre paru dix ans plus tôt. Les flèches sont le seul moyen de le
            changer — le glisser-déposer HTML5 ne fonctionne pas au doigt, et c'est sur
            téléphone qu'on consulte une saga en librairie.
        *@
        <ul class="liste-tomes">
            @foreach (var (element, index) in _serie.Elements.Select((e, i) => (e, i)))
            {
                <li class="carte-tome @(element.Possede ? null : "tome-manquant")" @key="element.Id">
                    <div class="rang-actions">
                        <button type="button" class="bouton bouton-compact"
                                disabled="@(_enCours || !Reseau.EnLigne || index == 0)"
                                title="@MotifBlocage"
                                aria-label="@($"Monter « {element.Titre} »")"
                                @onclick="() => DeplacerAsync(index, index - 1)">▲</button>

                        <span class="rang-numero" aria-hidden="true">@(index + 1)</span>

                        <button type="button" class="bouton bouton-compact"
                                disabled="@(_enCours || !Reseau.EnLigne || index == _serie.Elements.Count - 1)"
                                title="@MotifBlocage"
                                aria-label="@($"Descendre « {element.Titre} »")"
                                @onclick="() => DeplacerAsync(index, index + 1)">▼</button>
                    </div>

                    <div class="carte-corps">
                        @if (element.LivreId is { } livreId && _livresCatalogue.FirstOrDefault(l => l.Id == livreId) is { } livre)
                        {
                            <CarteLivre Livre="livre" />
                        }
                        else if (element.LivreId is { } idRattache)
                        {
                            <a class="carte-titre" href="@($"livres/{idRattache}")">@element.Titre</a>
                        }
                        else
                        {
                            <p class="carte-titre">@element.Titre</p>
                        }

                        @if ((element.LivreId is null || !_livresCatalogue.Any(l => l.Id == element.LivreId))
                            && !string.IsNullOrWhiteSpace(element.Auteurs))
                        {
                            <p class="carte-auteur">@element.Auteurs</p>
                        }

                        @if (element.LivreId is null || !_livresCatalogue.Any(l => l.Id == element.LivreId))
                        {
                        <p class="carte-details">
                            @if (!element.Possede)
                            {
                                @* Dire ce qui manque, et non laisser deviner par l'absence de lien. *@
                                <span class="etiquette etiquette-manquant">Pas dans la bibliothèque</span>
                            }

                            @if (element.PreteA is { } emprunteur)
                            {
                                <span class="etiquette etiquette-prete">Prêté à @emprunteur</span>
                            }
                        </p>
                        }

                        <div class="actions-tome">
                            @if (!element.Possede)
                            {
                                @* Le tome manquant est COMMUN au foyer, l'envie qu'il devient est
                                   PERSONNELLE : deux membres peuvent vouloir le même tome. *@
                                <button type="button" class="bouton bouton-compact"
                                        disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                                        @onclick="() => SouhaiterAsync(element)">
                                    Ajouter à mes envies
                                </button>

                                <button type="button" class="bouton bouton-compact"
                                        disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                                        @onclick="() => OuvrirRattachement(element.Id)">
                                    Je l'ai
                                </button>

                                <button type="button" class="bouton bouton-compact"
                                        disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                                        @onclick="() => OuvrirLookup(element)">
                                    Ajouter par ISBN
                                </button>
                            }
                            else
                            {
                                <button type="button" class="bouton bouton-compact"
                                        disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                                        @onclick="() => RattacherAsync(element.Id, null)">
                                    Détacher
                                </button>
                            }

                            <button type="button" class="bouton bouton-compact bouton-danger"
                                    disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                                    @onclick="() => RetirerAsync(element)">
                                Retirer
                            </button>
                        </div>

                        @if (_rattachement == element.Id)
                        {
                            <div class="recherche-livre">
                                <input class="champ-saisie" type="search" placeholder="Chercher dans le catalogue…"
                                       value="@_recherche" @oninput="ChercherAsync"
                                       aria-label="Chercher un livre à rattacher" />

                                @foreach (var resultat in _resultats)
                                {
                                    <button type="button" class="bouton bouton-discret"
                                            @key="resultat.Id"
                                            @onclick="() => RattacherAsync(element.Id, resultat.Id)">
                                        @resultat.Titre@(resultat.AuteursAffiches is { } a ? $" — {a}" : "")
                                    </button>
                                }
                            </div>
                        }

                        @if (_lookupElement?.Id == element.Id)
                        {
                            <div class="recherche-livre">
                                <input class="champ-saisie" type="text" inputmode="numeric"
                                       placeholder="ISBN du tome…"
                                       @bind="_lookupIsbn" @bind:event="oninput"
                                       @onkeydown="SurToucheLookup" />
                                <button type="button" class="bouton bouton-principal"
                                        disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_lookupIsbn))"
                                        title="@MotifBlocage" @onclick="ChercherLookupAsync">
                                    @(_lookupEnCours ? "Recherche…" : "Préremplir")
                                </button>
                                @if (_lookupCandidats.Count > 0)
                                {
                                    @foreach (var candidat in _lookupCandidats)
                                    {
                                        <div class="carte-candidat">
                                            <div class="carte-corps">
                                                <p class="carte-titre">@candidat.Titre</p>
                                                <p class="carte-auteur">@candidat.Auteur</p>
                                                <p class="carte-details">
                                                    @(candidat.Editeur ?? "éditeur inconnu")
                                                    · @(candidat.Annee ?? "année inconnue")
                                                </p>
                                                <button type="button" class="bouton bouton-principal"
                                                        disabled="@_enCours"
                                                        @onclick="() => AjouterCommeLivreAsync(candidat)">
                                                    Ajouter comme livre
                                                </button>
                                                <button type="button" class="bouton bouton-discret"
                                                        disabled="@_enCours"
                                                        @onclick="() => AjouterCommeEnvieAsync(candidat)">
                                                    Ajouter comme envie
                                                </button>
                                            </div>
                                        </div>
                                    }
                                }
                            </div>
                        }
                    </div>
                </li>
            }
        </ul>
    }

    @if (!ModeEdition)
    {
    <h2 class="titre-section">Ajouter un tome</h2>

    @*
        Deux voies, parce qu'une saga se recense d'un coup — souvent avant d'en posséder la
        moitié — puis se remplit au fil des achats. Un tome saisi sans livre N'EST PAS un
        brouillon : c'est ce qui rend le trou visible.
    *@
    <div class="barre-recherche">
        <input class="champ-saisie" type="text" placeholder="Titre d'un tome…"
               @bind="_nouveauTome" @bind:event="oninput" @onkeydown="SurToucheTome"
               aria-label="Titre du tome à ajouter" autocomplete="off" />
    </div>

    <div class="actions-formulaire">
        <button type="button" class="bouton bouton-principal"
                disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_nouveauTome))"
                title="@MotifBlocage"
                @onclick="() => AjouterTomeAsync(null)">
            Ajouter ce tome
        </button>

        <button type="button" class="bouton bouton-discret"
                disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                @onclick="() => OuvrirRattachement(AjoutDepuisCatalogue)">
            Depuis le catalogue
        </button>
    </div>

    @if (_rattachement == AjoutDepuisCatalogue)
    {
        <div class="recherche-livre">
            <input class="champ-saisie" type="search" placeholder="Chercher dans le catalogue…"
                   value="@_recherche" @oninput="ChercherAsync"
                   aria-label="Chercher un livre à ajouter" />

            @foreach (var livre in _resultats)
            {
                <button type="button" class="bouton bouton-discret" @key="livre.Id"
                        @onclick="() => AjouterTomeAsync(livre.Id)">
                    @livre.Titre@(livre.AuteursAffiches is { } a ? $" — {a}" : "")
                </button>
            }
        </div>
    }
    }

    @if (ModeEdition)
    {
    <h2 class="titre-section">La série</h2>

    <label class="champ">
        <span class="champ-libelle">Nom</span>
        <input class="champ-saisie" type="text" @bind="_titre" @bind:event="oninput" />
    </label>

    <label class="champ">
        <span class="champ-libelle">
            Rangée dans<span class="champ-aide"> (un cycle contient des séries)</span>
        </span>
        <select class="champ-saisie" @bind="_parenteId">
            <option value="0">Aucune — série de premier niveau</option>
            @foreach (var candidate in Candidates)
            {
                <option value="@candidate.Id">@candidate.Titre</option>
            }
        </select>
    </label>

    <div class="actions-formulaire">
        <button type="button" class="bouton bouton-principal"
                disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_titre))"
                title="@MotifBlocage"
                @onclick="EnregistrerAsync">
            Enregistrer
        </button>

        <button type="button" class="bouton bouton-discret bouton-danger"
                disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                @onclick="SupprimerAsync">
            @(_confirmeSuppression ? "Confirmer la suppression" : "Supprimer la série")
        </button>
    </div>

    @if (_confirmeSuppression)
    {
        <p class="message-avertissement" role="status">
            Les livres restent au catalogue ; seules les places disparaissent.
            @if (_filles.Count > 0)
            {
                <text> Les @_filles.Count série(s) de ce cycle deviendront des séries de premier niveau.</text>
            }
        </p>
    }
    }
    @if (!ModeEdition)
    {
    <nav class="actions-flottantes">
        @if (Reseau.EnLigne)
        {
            <a class="bouton bouton-principal" href="@($"series/{Id}/edition")">Modifier</a>
        }
        else
        {
            <button type="button" class="bouton bouton-principal" disabled
                    title="@EtatReseau.MotifHorsLigne">Modifier</button>
        }
    </nav>
    }
}

@code {
    /// <summary>Marqueur de la recherche « ajouter depuis le catalogue », qui ne vise aucun élément.</summary>
    private const int AjoutDepuisCatalogue = -1;

    [Parameter] public int Id { get; set; }

    private IReadOnlyList<SerieDto> _toutes = [];
    private IReadOnlyList<LivreDto> _livresCatalogue = [];
    private SerieDto? _serie;
    private SerieDto? _parente;
    private List<SerieDto> _filles = [];
    private IReadOnlyDictionary<int, (int Possedes, int Total)> _totaux =
        new Dictionary<int, (int, int)>();
    private readonly HashSet<int> _deplies = [];

    private bool _chargement = true;
    private bool _enCours;
    private string? _erreur;

    private string _titre = string.Empty;
    private int _parenteId;
    private bool _confirmeSuppression;

    private string _nouveauTome = string.Empty;

    private int? _rattachement;
    private string _recherche = string.Empty;
    private IReadOnlyList<LivreDto> _resultats = [];
    private ElementSerieDto? _lookupElement;
    private string _lookupIsbn = string.Empty;
    private IReadOnlyList<CandidatLivre> _lookupCandidats = [];
    private bool _lookupEnCours;

    private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
    private IReadOnlySet<int> CheminRacine => new HashSet<int> { Id };

    private bool ModeEdition =>
        Navigation.ToAbsoluteUri(Navigation.Uri).AbsolutePath.TrimEnd('/')
            .EndsWith("/edition", StringComparison.OrdinalIgnoreCase);

    /// <summary>
    /// Séries dans lesquelles celle-ci peut être rangée.
    /// </summary>
    /// <remarks>
    /// Ni elle-même, ni ses descendantes : l'arbre se refermerait sur lui-même. Le serveur le
    /// refuse aussi — cette liste évite seulement de proposer un choix qui sera rejeté.
    /// </remarks>
    private IEnumerable<SerieDto> Candidates =>
        _toutes.Where(s => s.Id != Id && !EstDescendante(s.Id));

    private bool EstDescendante(int candidate)
    {
        var courante = _toutes.FirstOrDefault(s => s.Id == candidate);
        var garde = _toutes.Count + 1;

        while (courante is not null && garde-- > 0)
        {
            if (courante.Id == Id)
            {
                return true;
            }

            courante = courante.SerieParenteId is { } parenteId
                ? _toutes.FirstOrDefault(s => s.Id == parenteId)
                : null;
        }

        return false;
    }

    protected override void OnInitialized()
    {
        Reseau.Change += SurChangementReseau;
        Navigation.LocationChanged += SurNavigation;
    }

    private void SurNavigation(object? _, LocationChangedEventArgs __) => InvokeAsync(StateHasChanged);

    private void SurChangementReseau() => _ = InvokeAsync(async () =>
    {
        await ChargerAsync();
        StateHasChanged();
    });

    public void Dispose()
    {
        Reseau.Change -= SurChangementReseau;
        Navigation.LocationChanged -= SurNavigation;
    }

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

    private async Task ChargerAsync()
    {
        _chargement = true;
        _toutes = await Api.ListerSeriesAsync();
        _livresCatalogue = await Api.ListerAsync(new CritereLivres());
        _chargement = false;

        Rattacher();
    }

    /// <summary>Recale les vues dérivées après un chargement ou une écriture.</summary>
    private void Rattacher()
    {
        _serie = _toutes.FirstOrDefault(s => s.Id == Id);

        if (_serie is null)
        {
            return;
        }

        _parente = _serie.SerieParenteId is { } parenteId
            ? _toutes.FirstOrDefault(s => s.Id == parenteId)
            : null;

        _filles = _toutes.Where(s => s.SerieParenteId == Id).OrderBy(s => s.Position).ToList();
        _totaux = TotauxSeries.Calculer(_toutes);

        _titre = _serie.Titre;
        _parenteId = _serie.SerieParenteId ?? 0;
    }

    /// <summary>Remplace la série courante par celle que vient de rendre le serveur.</summary>
    private void Appliquer(SerieDto serie)
    {
        _toutes = _toutes.Select(s => s.Id == serie.Id ? serie : s).ToList();
        Rattacher();
    }

    private async Task SurToucheTome(KeyboardEventArgs e)
    {
        if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_nouveauTome) && Reseau.EnLigne)
        {
            await AjouterTomeAsync(null);
        }
    }

    private async Task AjouterTomeAsync(int? livreId)
    {
        await EcrireAsync(() => Api.AjouterElementSerieAsync(
            Id, new AjoutElementSerie { LivreId = livreId, Titre = livreId is null ? _nouveauTome : null }));

        if (_erreur is null)
        {
            _nouveauTome = string.Empty;
            FermerRecherche();
        }
    }

    private Task RattacherAsync(int elementId, int? livreId) =>
        EcrireAsync(async () =>
        {
            var resultat = await Api.RattacherLivreSerieAsync(elementId, livreId);
            FermerRecherche();
            return resultat;
        });

    private async Task RetirerAsync(ElementSerieDto element)
    {
        _enCours = true;
        _erreur = await Api.RetirerElementSerieAsync(element.Id);
        _enCours = false;

        if (_erreur is null)
        {
            await ChargerAsync();
        }
    }

    private async Task SouhaiterAsync(ElementSerieDto element)
    {
        _enCours = true;
        _erreur = null;

        var resultat = await Api.SouhaiterTomeAsync(element.Id);

        _enCours = false;
        _erreur = resultat.Erreur;

        if (resultat.EstOk)
        {
            _message = $"« {element.Titre} » est dans votre liste d'envies.";
        }
    }

    private (int Possedes, int Total) Total(SerieDto serie) =>
            _totaux.TryGetValue(serie.Id, out var total)
                ? total
                : (serie.NombrePossedes, serie.Elements.Count);

        private async Task DeplacerSousSerieAsync(DeplacementSerie mouvement)
        {
            var soeurs = _toutes
                .Where(s => s.SerieParenteId == mouvement.ParenteId)
                .OrderBy(s => s.Position)
                .ThenBy(s => s.Id)
                .ToList();
            var index = soeurs.FindIndex(s => s.Id == mouvement.SerieId);
            var cible = index + mouvement.Delta;
            if (index < 0 || cible < 0 || cible >= soeurs.Count)
            {
                return;
            }

            var ids = soeurs.Select(s => s.Id).ToList();
            (ids[index], ids[cible]) = (ids[cible], ids[index]);
            await EcrireAsync(() => Api.ReordonnerSousSeriesAsync(mouvement.ParenteId, ids));
            if (_erreur is null)
            {
                await ChargerAsync();
            }
        }

    private void OuvrirLookup(ElementSerieDto element)
        {
            _lookupElement = element;
            _lookupIsbn = string.Empty;
            _lookupCandidats = [];
            _erreur = null;
        }

    private async Task SurToucheLookup(KeyboardEventArgs e)
        {
            if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_lookupIsbn))
            {
                await ChercherLookupAsync();
            }
        }

    private async Task ChercherLookupAsync()
        {
            _lookupEnCours = true;
            _erreur = null;
            _lookupCandidats = [];
            var resultat = await Api.ChercherIsbnAsync(_lookupIsbn);
            _lookupEnCours = false;

            if (resultat is null)
            {
                _erreur = Reseau.EnLigne
                    ? "La recherche n'a rien pu ramener."
                    : EtatReseau.MotifHorsLigne;
                return;
            }

            if (resultat.Periodique is not null)
            {
                _erreur = "Les revues ne peuvent pas être rattachées à une série.";
                return;
            }

            _lookupCandidats = resultat.Candidats;
            if (_lookupCandidats.Count == 0)
            {
                _erreur = "Aucune notice ne correspond à cet ISBN.";
            }
        }

    private async Task AjouterCommeEnvieAsync(CandidatLivre candidat)
        {
            _enCours = true;
            _erreur = null;
            var resultat = await Api.AjouterSouhaitAsync(new EnregistrementSouhait
            {
                Titre = candidat.Titre,
                Auteur = candidat.Auteur,
                Editeur = candidat.Editeur,
                Annee = candidat.Annee,
                Isbn = candidat.IsbnInterroge,
                CoverUrl = candidat.CoverUrl,
            });
            _enCours = false;
            _erreur = resultat.Erreur;
            if (resultat.EstOk)
            {
                _message = $"« {candidat.Titre} » est dans votre liste d'envies.";
                _lookupElement = null;
            }
        }

    private async Task AjouterCommeLivreAsync(CandidatLivre candidat)
        {
            if (_lookupElement is null)
            {
                return;
            }

            _enCours = true;
            _erreur = null;
            var saisie = new EnregistrementLivre
            {
                Isbn = candidat.IsbnInterroge,
                Titre = candidat.Titre,
                Auteurs = candidat.Auteurs.Select(a => new AuteurSaisi(a)).ToList(),
                Editeur = candidat.Editeur,
                CoverUrl = candidat.CoverUrl,
            };
            var resultat = await Api.CreerAsync(saisie);
            _enCours = false;

            if (resultat.Doublons is not null)
            {
                _erreur = resultat.Doublons.Message;
                return;
            }

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

            await EcrireAsync(() => Api.RattacherLivreSerieAsync(_lookupElement.Id, resultat.Livre!.Id));
            if (_erreur is null)
            {
                _lookupElement = null;
        }
    }

    private string? _message;

    private async Task DeplacerAsync(int depuis, int vers)
    {
        if (_serie is null || vers < 0 || vers >= _serie.Elements.Count)
        {
            return;
        }

        // Réordonné localement d'abord : la flèche doit répondre tout de suite, l'aller-retour
        // réseau ne doit pas se voir. Un échec recharge l'ordre du serveur.
        var ids = _serie.Elements.Select(e => e.Id).ToList();
        (ids[depuis], ids[vers]) = (ids[vers], ids[depuis]);

        await EcrireAsync(() => Api.ReordonnerSerieAsync(Id, ids));
    }

    private async Task EnregistrerAsync()
    {
        await EcrireAsync(() => Api.ModifierSerieAsync(
            Id, new EnregistrementSerie { Titre = _titre, SerieParenteId = _parenteId == 0 ? null : _parenteId }));

        // Le rangement dans un cycle change la parenté des AUTRES séries affichées : on relit.
        if (_erreur is null)
        {
            await ChargerAsync();
        }
    }

    private async Task SupprimerAsync()
    {
        if (!_confirmeSuppression)
        {
            _confirmeSuppression = true;
            return;
        }

        _enCours = true;
        _erreur = await Api.SupprimerSerieAsync(Id);
        _enCours = false;

        if (_erreur is null)
        {
            Navigation.NavigateTo("series");
        }
    }

    private void OuvrirRattachement(int cible)
    {
        _rattachement = cible;
        _recherche = string.Empty;
        _resultats = [];
    }

    private void FermerRecherche()
    {
        _rattachement = null;
        _recherche = string.Empty;
        _resultats = [];
    }

    private async Task ChercherAsync(ChangeEventArgs e)
    {
        _recherche = e.Value?.ToString() ?? string.Empty;

        if (string.IsNullOrWhiteSpace(_recherche))
        {
            _resultats = [];
            return;
        }

        // La recherche passe par le service habituel : hors-ligne elle interroge l'instantané,
        // en ligne le serveur. Rien de particulier à prévoir ici.
        _resultats = (await Api.ListerAsync(new CritereLivres { Recherche = _recherche }))
            .Take(8)
            .ToList();
    }

    /// <summary>Exécute une écriture qui rend la série, en tenant l'état d'attente et l'erreur.</summary>
    private async Task EcrireAsync(Func<Task<ResultatApi<SerieDto>>> appel)
    {
        _enCours = true;
        _erreur = null;
        _message = null;

        var resultat = await appel();

        _enCours = false;

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

            // L'ordre affiché a pu être modifié localement avant l'appel : on reprend celui du
            // serveur plutôt que de laisser croire à un enregistrement.
            await ChargerAsync();
            return;
        }

        Appliquer(resultat.Valeur!);
    }
}
