using MaBibli.Shared.Entites;
using MaBibli.Shared.Textes;
namespace MaBibli.Shared.Catalogue;
///
/// Filtrage, recherche et tri du catalogue.
///
///
/// Écrit sur pour être exécuté par EF Core côté base et
/// testable en mémoire sur une simple liste, sans base ni réseau.
///
/// La recherche porte sur les colonnes normalisées (,
/// ), pas sur les colonnes d'affichage : SQLite ne sait pas
/// ignorer les accents, et sa collation NOCASE 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 ».
///
///
public static class FiltreLivres
{
///
/// Personne dont on regarde le statut. Sert uniquement à
/// : il ne restreint jamais la liste elle-même.
///
public static IQueryable Appliquer(
IQueryable 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.TypeDocument is { } type)
{
source = source.Where(l => l.TypeDocument == type);
}
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));
}
if (criteres.Prete is { } prete)
{
// Un prêt EN COURS est une ligne sans date de retour : l'historique clos ne compte
// pas. Aucune identité n'intervient — le prêt est commun au foyer, contrairement au
// statut de lecture juste au-dessus.
source = prete
? source.Where(l => l.Prets.Any(p => p.DateRetour == null))
: source.Where(l => !l.Prets.Any(p => p.DateRetour == null));
}
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);
}
///
/// Tri par titre, puis par identifiant pour rendre l'ordre déterministe.
///
///
/// 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.
///
/// Le tri porte sur le titre normalisé : sinon « Œuvres » et « Éducation » atterrissent
/// après « Zola » dans l'ordre des points de code.
///
///
public static IQueryable Trier(IQueryable source) =>
source.OrderBy(l => l.TitreNormalise).ThenBy(l => l.Id);
}