using System.Globalization;
using System.Text;

namespace MaBibli.Shared.Textes;

/// <summary>
/// Mise à plat d'un texte pour la recherche et le regroupement : minuscules, sans accents,
/// sans ponctuation, espaces réduits.
/// </summary>
/// <remarks>
/// <b>Pourquoi une forme stockée plutôt qu'une comparaison à la volée ?</b> SQLite ne sait pas
/// comparer sans tenir compte des accents : sa collation <c>NOCASE</c> ne couvre que l'ASCII, et
/// il n'existe pas de <c>unaccent()</c> intégré. Faire le travail en C# à l'écriture, dans une
/// colonne dédiée, est la seule façon d'obtenir « Emile » → « Émile » <b>sans</b> charger tout
/// le catalogue en mémoire à chaque recherche.
/// <para>
/// Conséquence à ne pas oublier : toute écriture doit recalculer la forme normalisée, et le
/// terme cherché doit passer par la <b>même</b> fonction que la valeur stockée.
/// </para>
/// </remarks>
public static class NormalisationTexte
{
    /// <summary>
    /// Forme comparable d'un texte quelconque : « Émile ZOLA » → « emile zola ».
    /// </summary>
    /// <remarks>
    /// La ponctuation devient une espace plutôt que de disparaître : « P.F. Hamilton » doit
    /// donner trois mots (<c>p f hamilton</c>) et non un seul (<c>pf hamilton</c>), sans quoi
    /// le rapprochement par initiales serait impossible.
    /// </remarks>
    public static string Normaliser(string? brut)
    {
        if (string.IsNullOrWhiteSpace(brut))
        {
            return string.Empty;
        }

        // NFD sépare la lettre de son accent ; on ne garde ensuite que la lettre.
        var decompose = brut.Normalize(NormalizationForm.FormD);
        var sortie = new StringBuilder(decompose.Length);
        var espaceEnAttente = false;

        foreach (var c in decompose)
        {
            if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark)
            {
                continue; // Accent détaché par la décomposition.
            }

            if (char.IsLetterOrDigit(c))
            {
                if (espaceEnAttente && sortie.Length > 0)
                {
                    sortie.Append(' ');
                }

                espaceEnAttente = false;
                sortie.Append(char.ToLowerInvariant(c));
                continue;
            }

            // Tout le reste (espaces, points, virgules, tirets…) est un séparateur de mots.
            espaceEnAttente = true;
        }

        return sortie.ToString().Normalize(NormalizationForm.FormC);
    }

    /// <summary>Mots de la forme normalisée, sans mot vide.</summary>
    public static string[] Mots(string? brut)
    {
        var normalise = Normaliser(brut);
        return normalise.Length == 0
            ? []
            : normalise.Split(' ', StringSplitOptions.RemoveEmptyEntries);
    }
}
