@inherits LayoutComponentBase
@implements IDisposable
@inject ServiceLivresApi Api
@inject EtatReseau Reseau
@inject NavigationManager Navigation
@inject IJSRuntime JS

@*
    Mise en page pensée mobile d'abord : un bandeau, une colonne, rien à gauche.
    Le PC hérite de la même colonne, simplement centrée et limitée en largeur.

    ⚠️ La navigation est passée EN HAUT le 2026-08-20, à la demande de l'utilisateur, après
    que la barre d'onglets du bas se soit affichée « toujours très mal » sur son téléphone.
    Cela renverse la décision du 2026-08-18 (« en bas, le pouce atteint le bas de l'écran ») :
    le raisonnement sur le pouce reste juste, mais il ne vaut rien face à une barre qui ne
    s'affiche pas correctement. Le repli retenu est celui du gabarit Blazor par défaut — un
    bouton bascule et une liste qui se déploie — parce qu'il ne dépend d'AUCUNE mise en page
    exotique : sans la moindre feuille de style, il reste une suite de liens lisibles les uns
    sous les autres, au lieu d'une rangée écrasée.
*@
<header class="bandeau">
    @* Le déplacement entre écrans était jugé « foireux » : d'un écran profond (bibliographie,
       fiche de tome, ajout d'envie) il fallait deviner quel onglet ramenait en arrière. Un
       retour explicite, au même endroit sur tous les écrans, répond à la question sans
       obliger à connaître l'arborescence. *@
    <button type="button" class="bandeau-icone" @onclick="RetourAsync"
            title="Revenir à l'écran précédent" aria-label="Revenir à l'écran précédent">
        <span aria-hidden="true">&#8592;</span>
    </button>

    <a class="marque" href="">
        @* Pictogramme dessiné en ligne plutôt que chargé depuis /logo-bandeau.svg : un fichier
           séparé se sert (ou non) indépendamment de l'application, et c'est précisément ce qui
           produisait l'icône cassée en haut à gauche — un appareil dont le cache datait d'avant
           l'ajout du fichier recevait un 404. Un SVG en ligne ne peut pas manquer. *@
        <svg class="marque-logo" viewBox="0 0 64 64" aria-hidden="true" focusable="false">
            <polygon points="32,15 10,20 10,47 32,44" fill="currentColor" />
            <polygon points="32,15 54,20 54,47 32,44" fill="currentColor" />
            <line x1="32" y1="15" x2="32" y2="44" stroke="#1b3a5c" stroke-width="1.5" />
        </svg>
        <span class="marque-nom">MaBibli</span>
    </a>

    @if (!Reseau.EnLigne)
    {
        @* Une pastille dans le bandeau, visible sur tous les écrans : l'état du réseau change
           l'usage de l'application, il ne doit pas se découvrir au premier clic qui échoue. *@
        <span class="pastille-hors-ligne" title="@EtatReseau.MotifHorsLigne">Hors ligne</span>
    }

    @if (_utilisateur?.Identifiant is not null)
    {
        <span class="utilisateur" title="@(_utilisateur.Simule ? "Utilisateur simulé (développement)" : "Connecté via le portail YunoHost")">
            @_utilisateur.Affichage@(_utilisateur.Simule ? " (dev)" : "")
        </span>
    }

    <button type="button" class="bandeau-icone bandeau-bascule" @onclick="() => _menuOuvert = !_menuOuvert"
            aria-expanded="@_menuOuvert" aria-controls="menu-principal"
            aria-label="@(_menuOuvert ? "Fermer le menu" : "Ouvrir le menu")">
        <span aria-hidden="true">@(_menuOuvert ? "\u2715" : "\u2630")</span>
    </button>
</header>

@*
    Les six destinations de l'application. Sur PC elles tiennent en une rangée sous le
    bandeau ; sur téléphone elles se déploient en liste au clic sur la bascule.

    Les entrées restent actives hors-ligne : les six écrans se consultent depuis leurs
    instantanés. Ce sont les écritures qui se désactivent, pas la navigation.
*@
<nav id="menu-principal" class="menu @(_menuOuvert ? "menu-ouvert" : null)" aria-label="Navigation principale">
    <NavLink class="menu-lien" href="" Match="NavLinkMatch.All">Catalogue</NavLink>
    <NavLink class="menu-lien" href="auteurs">Auteurs</NavLink>
    <NavLink class="menu-lien" href="series">Séries</NavLink>
    <NavLink class="menu-lien" href="revues">Revues</NavLink>
    <NavLink class="menu-lien" href="prets">Prêts</NavLink>
    <NavLink class="menu-lien" href="souhaits">Envies</NavLink>
</nav>

@if (!Reseau.EnLigne)
{
    @*
        Dire d'où viennent les données et de quand elles datent. Sans cette phrase, une
        bibliothèque affichée hors-ligne est indiscernable d'une bibliothèque à jour — et un
        livre ajouté depuis un autre appareil manquerait sans explication.
    *@
    <p class="bandeau-reseau" role="status">
        <strong>Hors ligne.</strong>
        @(Reseau.DerniereSynchro is { } synchro
            ? $" Données enregistrées {Quand(synchro)}. "
            : " Aucune donnée n'a encore pu être enregistrée sur cet appareil. ")
        Consultation et recherche fonctionnent ; les modifications sont impossibles.
    </p>
}

<main class="contenu">
    @Body
</main>

@code {
    private UtilisateurCourant? _utilisateur;
    private bool _etaitEnLigne = true;
    private bool _synchroEnCours;

    /// <summary>Menu déployé (téléphone). Sur PC la rangée est visible en permanence.</summary>
    private bool _menuOuvert;

    protected override async Task OnInitializedAsync()
    {
        Reseau.Change += SurChangementReseau;
        Reseau.SynchroChange += SurSynchro;

        // ⚠️ Sans cela, le menu resterait déployé par-dessus l'écran qu'on vient d'atteindre :
        // NavLink ne referme rien de lui-même, et un clic sur « Auteurs » laisserait les six
        // entrées empilées au-dessus de la liste des auteurs.
        Navigation.LocationChanged += SurNavigation;

        // Écoute des bascules online/offline avant tout appel : un démarrage hors-ligne doit
        // aller directement au cache, sans attendre l'échec d'une requête.
        await Reseau.DemarrerAsync();
        _etaitEnLigne = Reseau.EnLigne;

        _utilisateur = await Api.ObtenirUtilisateurAsync();

        // Rafraîchit tout le fonds, pas seulement l'écran ouvert : c'est ce qui rend la
        // bibliothèque entière consultable et cherchable après la coupure.
        await SynchroniserAsync();
    }

    /// <summary>
    /// Au retour du réseau, on recharge : la bibliothèque a pu changer depuis un autre appareil,
    /// et les actions d'écriture redeviennent disponibles dans la foulée.
    /// </summary>
    private void SurChangementReseau()
    {
        var revenu = Reseau.EnLigne && !_etaitEnLigne;
        _etaitEnLigne = Reseau.EnLigne;

        _ = InvokeAsync(async () =>
        {
            StateHasChanged();

            if (revenu)
            {
                _utilisateur = await Api.ObtenirUtilisateurAsync();
                await SynchroniserAsync();
                StateHasChanged();
            }
        });
    }

    private async Task SynchroniserAsync()
    {
        if (_synchroEnCours)
        {
            return;
        }

        _synchroEnCours = true;

        try
        {
            await Api.SynchroniserAsync();
        }
        finally
        {
            _synchroEnCours = false;
        }
    }

    /// <summary>Date de synchronisation en clair : l'heure suffit le jour même.</summary>
    private static string Quand(DateTimeOffset instant)
    {
        var local = instant.ToLocalTime();

        return local.Date == DateTimeOffset.Now.Date
            ? $"aujourd'hui à {local:HH:mm}"
            : $"le {local:dd/MM/yyyy} à {local:HH:mm}";
    }

    /// <summary>La date affichée vient de changer : rien à recharger, juste à redessiner.</summary>
    private void SurSynchro() => _ = InvokeAsync(StateHasChanged);

    private void SurNavigation(object? _, LocationChangedEventArgs __)
    {
        if (!_menuOuvert)
        {
            return;
        }

        _menuOuvert = false;
        _ = InvokeAsync(StateHasChanged);
    }

    /// <summary>
    /// Revient à l'écran précédent, en repliant le menu au passage.
    /// </summary>
    /// <remarks>
    /// Le retour passe par l'<b>historique du navigateur</b>, pas par une destination calculée :
    /// c'est le seul moyen de ramener là d'où l'on vient réellement, un même écran étant
    /// atteignable par plusieurs chemins (une fiche livre s'ouvre depuis le catalogue, une
    /// bibliographie, une série ou une recherche). Le repli sur le catalogue quand la pile est
    /// vide est traité en JavaScript, <c>history.length</c> n'ayant pas d'équivalent côté C#.
    /// </remarks>
    private async Task RetourAsync()
    {
        _menuOuvert = false;
        await JS.InvokeVoidAsync("mabibliRetour");
    }

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