using MaBibli.Shared.Entites;
using MaBibli.Shared.Textes;

namespace MaBibli.Shared.Catalogue;

/// <summary>
/// Filtrage, recherche et tri du catalogue.
/// </summary>
/// <remarks>
/// Écrit sur <see cref="IQueryable{T}"/> pour être exécuté par EF Core côté base <b>et</b>
/// testable en mémoire sur une simple liste, sans base ni réseau.
/// <para>
/// La recherche porte sur les colonnes <b>normalisées</b> (<see cref="Livre.TitreNormalise"/>,
/// <see cref="Auteur.NomNormalise"/>), pas sur les colonnes d'affichage : SQLite ne sait pas
/// ignorer les accents, et sa collation <c>NOCASE</c> s'arrête à l'ASCII. Le terme cherché passe
/// par la même normalisation que la valeur stockée — c'est ce qui fait que « emile » trouve
/// « Émile ».
/// </para>
/// </remarks>
public static class FiltreLivres
{
    /// <param name="utilisateur">
    /// Personne dont on regarde le statut. Sert <b>uniquement</b> à
    /// <see cref="CritereLivres.Statut"/> : il ne restreint jamais la liste elle-même.
    /// </param>
    public static IQueryable<Livre> Appliquer(
        IQueryable<Livre> source, CritereLivres criteres, string? utilisateur)
    {
        // AUCUN filtre sur AjoutePar : la bibliothèque est commune (CLAUDE.md).
        // Ce commentaire est là pour qu'une régression future saute aux yeux en relecture.

        if (criteres.Format is { } format)
        {
            source = source.Where(l => l.Format == format);
        }

        if (criteres.Statut is { } statut)
        {
            // Le statut vit dans une table par personne : on interroge la ligne de l'appelant,
            // et lui seulement. Sans utilisateur identifié, personne n'a de statut : la sélection
            // est vide, ce qui vaut mieux que de montrer celui d'un autre.
            source = utilisateur is null
                ? source.Where(_ => false)
                : source.Where(l => l.Statuts.Any(s => s.Utilisateur == utilisateur && s.Statut == statut));
        }

        if (criteres.AuteurId is { } auteurId)
        {
            source = source.Where(l => l.Auteurs.Any(la => la.AuteurId == auteurId));
        }

        var terme = NormalisationTexte.Normaliser(criteres.Recherche);
        if (terme.Length > 0)
        {
            // Le terme est confronté aux deux formes stockées de chaque auteur : le nom dans
            // l'ordre de saisie, et la clé aux mots triés. La seconde est ce qui fait que
            // « zola émile » trouve « Émile Zola » — le terme est trié de la même façon.
            var cle = RapprochementAuteurs.Cle(criteres.Recherche);

            source = source.Where(l =>
                l.TitreNormalise.Contains(terme)
                || l.Auteurs.Any(la =>
                    la.Auteur!.NomNormalise.Contains(terme)
                    || la.Auteur.CleRegroupement.Contains(cle)));
        }

        return Trier(source);
    }

    /// <summary>
    /// Tri par titre, puis par identifiant pour rendre l'ordre déterministe.
    /// </summary>
    /// <remarks>
    /// CLAUDE.md ne tranche pas le tri. L'ordre alphabétique a été retenu parce qu'une
    /// bibliothèque se parcourt par titre ; l'ordre d'ajout n'a d'intérêt que le jour de la saisie.
    /// <para>
    /// Le tri porte sur le titre normalisé : sinon « Œuvres » et « Éducation » atterrissent
    /// après « Zola » dans l'ordre des points de code.
    /// </para>
    /// </remarks>
    public static IQueryable<Livre> Trier(IQueryable<Livre> source) =>
        source.OrderBy(l => l.TitreNormalise).ThenBy(l => l.Id);
}
