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

<PageTitle>MaBibli — @(_revue?.Titre ?? "revue")</PageTitle>

@if (_revue is null)
{
    <p class="message-discret" role="status">
        @(_chargement ? "Chargement…" : "Cette revue n'est pas dans les données enregistrées sur cet appareil.")
    </p>
}
else
{
    <h1 class="titre-page">@_revue.Titre</h1>

    <p class="carte-details">
        @if (!string.IsNullOrWhiteSpace(_revue.Issn))
        {
            <span class="code-isbn">ISSN @_revue.Issn</span>
        }
        @if (!string.IsNullOrWhiteSpace(_revue.Editeur))
        {
            <span>@_revue.Editeur</span>
        }
    </p>

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

    <h2 class="titre-section">Numéros possédés</h2>

    @if (_revue.Numeros.Count == 0)
    {
        <p class="message-discret">
            Aucun numéro recensé pour l'instant.
        </p>
    }
    else
    {
        @* Du plus récent au plus ancien : devant un kiosque, la question est « ai-je le
           dernier ? », pas « par quoi ai-je commencé ». C'est l'inverse d'une série, qui suit
           son ordre de lecture. *@
        <ul class="liste-numeros">
            @foreach (var numero in _revue.Numeros)
            {
                <li class="carte-numero" @key="numero.Id">
                    <div class="carte-corps">
                        <p class="carte-titre">N° @numero.Numero</p>

                        <p class="carte-details">
                            @if (numero.DateParution is { } parution)
                            {
                                <span>@parution.ToLocalTime().ToString("MMMM yyyy")</span>
                            }
                            else
                            {
                                <span>Date de parution inconnue</span>
                            }
                        </p>

                        @if (!string.IsNullOrWhiteSpace(numero.Note))
                        {
                            <p class="souhait-note">@numero.Note</p>
                        }
                    </div>

                    <button type="button" class="bouton bouton-compact bouton-danger"
                            disabled="@(_enCours || !Reseau.EnLigne)" title="@MotifBlocage"
                            aria-label="@($"Retirer le numéro {numero.Numero}")"
                            @onclick="() => RetirerAsync(numero)">
                        Retirer
                    </button>
                </li>
            }
        </ul>
    }

    <h2 class="titre-section">Ajouter un numéro</h2>

    @*
        ⚠️ Le numéro se saisit à la main, et ce n'est pas un oubli : les deux chiffres de
        parution du code-barres ne sont PAS un numéro fiable (CLAUDE.md), et l'add-on EAN-2 qui
        le porterait vraiment n'a pas pu être vérifié sur un magazine réel.
    *@
    @if (_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne))
    {
        <p class="message-avertissement" role="status">
            Le code-barres portait « @NumeroScanne » à côté du code principal, ce qui est
            normalement le numéro de parution. <strong>Vérifiez-le sur la couverture</strong>
            avant d'enregistrer.
        </p>
    }

    <div class="champs-numero">
        <label class="champ">
            <span class="champ-libelle">Numéro</span>
            <input class="champ-saisie" type="text" placeholder="43" @bind="_numero"
                   @bind:event="oninput" @onkeydown="SurTouche" autocomplete="off" />
        </label>

        <label class="champ">
            <span class="champ-libelle">Parution <span class="champ-aide">(facultatif)</span></span>
            <input class="champ-saisie" type="date" @bind="_parution" />
        </label>

        <label class="champ">
            <span class="champ-libelle">Note <span class="champ-aide">(facultatif)</span></span>
            <input class="champ-saisie" type="text" @bind="_note" @bind:event="oninput"
                   placeholder="dossier sur l'eau" autocomplete="off" />
        </label>
    </div>

    <div class="actions-formulaire">
        <button type="button" class="bouton bouton-principal"
                disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_numero))"
                title="@MotifBlocage"
                @onclick="AjouterAsync">
            Ajouter ce numéro
        </button>
    </div>

    <h2 class="titre-section">La revue</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">ISSN <span class="champ-aide">(identifiant de la revue)</span></span>
        <input class="champ-saisie" type="text" @bind="_issn" @bind:event="oninput"
               placeholder="2466-6718" autocomplete="off" />
    </label>

    <label class="champ">
        <span class="champ-libelle">Éditeur</span>
        <input class="champ-saisie" type="text" @bind="_editeur" @bind:event="oninput" />
    </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 revue")
        </button>
    </div>

    @if (_confirmeSuppression)
    {
        <p class="message-avertissement" role="status">
            Les @_revue.Numeros.Count numéro(s) recensés disparaîtront avec elle : ils n'existent
            que par leur revue.
        </p>
    }
}

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

    /// <summary>
    /// Numéro lu sur l'add-on <b>EAN-2</b> du code-barres, transmis par l'écran de scan.
    /// </summary>
    /// <remarks>
    /// ⚠️ <b>Proposé, pas acquis.</b> L'EAN-2 est normativement le numéro de parution, mais
    /// rien ne l'a confirmé sur des magazines réels : il atterrit donc dans le champ, où
    /// l'utilisateur le compare à la couverture et le corrige. C'est aussi pourquoi l'écran le
    /// dit explicitement au lieu de le glisser en douce.
    /// </remarks>
    [SupplyParameterFromQuery(Name = "numero")]
    public string? NumeroScanne { get; set; }

    /// <summary>Le numéro scanné n'est proposé qu'une fois : ensuite, le champ appartient à l'utilisateur.</summary>
    private bool _numeroPropose;

    private RevueDto? _revue;
    private bool _chargement = true;
    private bool _enCours;
    private string? _erreur;
    private bool _confirmeSuppression;

    private string _titre = string.Empty;
    private string? _issn;
    private string? _editeur;

    private string _numero = string.Empty;
    private DateTime? _parution;
    private string? _note;

    private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;

    protected override void OnInitialized() => Reseau.Change += SurChangementReseau;

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

    public void Dispose() => Reseau.Change -= SurChangementReseau;

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

    private async Task ChargerAsync()
    {
        _chargement = true;
        _revue = (await Api.ListerRevuesAsync()).FirstOrDefault(r => r.Id == Id);
        _chargement = false;

        if (_revue is not null)
        {
            _titre = _revue.Titre;
            _issn = _revue.Issn;
            _editeur = _revue.Editeur;
        }

        if (!_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne))
        {
            _numeroPropose = true;
            _numero = NumeroScanne.Trim();
        }
    }

    private void Appliquer(RevueDto revue)
    {
        _revue = revue;
        _titre = revue.Titre;
        _issn = revue.Issn;
        _editeur = revue.Editeur;
    }

    private async Task SurTouche(KeyboardEventArgs e)
    {
        if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_numero) && Reseau.EnLigne)
        {
            await AjouterAsync();
        }
    }

    private async Task AjouterAsync()
    {
        _enCours = true;
        _erreur = null;

        // Comme pour les prêts : le <input type="date"> produit une date SANS fuseau. Elle est
        // déclarée locale avant conversion, sans quoi la parution se décalerait d'un jour pour
        // la moitié du globe.
        var parution = _parution is { } date
            ? DateTime.SpecifyKind(date, DateTimeKind.Local).ToUniversalTime()
            : (DateTime?)null;

        var resultat = await Api.AjouterNumeroAsync(Id, new AjoutNumeroRevue
        {
            Numero = _numero,
            DateParution = parution,
            Note = _note,
        });

        _enCours = false;

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

        Appliquer(resultat.Valeur!);
        _numero = string.Empty;
        _parution = null;
        _note = null;
    }

    private async Task RetirerAsync(NumeroRevueDto numero)
    {
        _enCours = true;
        _erreur = await Api.RetirerNumeroAsync(numero.Id);
        _enCours = false;

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

    private async Task EnregistrerAsync()
    {
        _enCours = true;
        _erreur = null;

        var resultat = await Api.ModifierRevueAsync(
            Id, new EnregistrementRevue { Titre = _titre, Issn = _issn, Editeur = _editeur });

        _enCours = false;

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

        Appliquer(resultat.Valeur!);
    }

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

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

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