using System.Globalization;
using System.Text.RegularExpressions;

namespace MaBibli.Api.Services.Isbn;

/// <summary>
/// Nettoyage de la ponctuation ISBD des champs Dublin Core de la BnF.
/// </summary>
/// <remarks>
/// Les champs BnF ne sont pas exploitables bruts. Règles validées le 2026-08-17 (CLAUDE.md) :
/// <list type="bullet">
/// <item><c>dc:title</c> : <c>Germinal / Émile Zola ; préface…</c> → <c>Germinal</c></item>
/// <item><c>dc:creator</c> : <c>Zola, Émile (1840-1902). Auteur du texte</c> → <c>Émile Zola</c></item>
/// <item><c>dc:publisher</c> : <c>le Livre de poche (Paris)</c> → <c>le Livre de poche</c></item>
/// </list>
/// Les valeurs déjà propres doivent traverser ces règles <b>inchangées</b>.
/// </remarks>
public static partial class NettoyageIsbd
{
    [GeneratedRegex(@"\s*\([^)]*\)")]
    private static partial Regex Parentheses();

    [GeneratedRegex(@"\s*\([^)]*\)\s*$")]
    private static partial Regex ParenthesesFinales();

    /// <summary>
    /// Titre : couper à la première mention de responsabilité (« / »).
    /// </summary>
    public static string? Titre(string? brut)
    {
        var s = brut?.Trim();
        if (string.IsNullOrEmpty(s))
        {
            return null;
        }

        var i = s.IndexOf(" / ", StringComparison.Ordinal);
        if (i >= 0)
        {
            s = s[..i];
        }

        s = s.Trim();
        return s.Length == 0 ? null : s;
    }

    /// <summary>
    /// Retire du titre le nom d'auteur qui y est collé en fin de chaîne.
    /// </summary>
    /// <remarks>
    /// Cas réel mesuré, ISBN <c>9782749942636</c> :
    /// <c>dc:title</c> vaut « Le plus grand défi … (Nouvelle éd. augmentée) <b>Aurélien Barrau</b> »
    /// alors que <c>dc:creator</c> vaut « Barrau, Aurélien (1973-....) ». Le nom est recollé
    /// <b>sans</b> le séparateur ISBD « / », donc <see cref="Titre"/> ne peut rien y faire : la
    /// notice est sale à la source.
    /// <para>
    /// L'heuristique est volontairement stricte — comparaison en <b>fin de chaîne</b> uniquement,
    /// et seulement contre un auteur déjà nettoyé. Un titre qui contient légitimement un nom de
    /// personne ailleurs (« Lettre à Émile Zola ») n'est pas touché, et un titre qui <i>est</i>
    /// le nom de l'auteur (biographie, recueil) est laissé intact plutôt que réduit à rien.
    /// </para>
    /// </remarks>
    public static string? RetirerAuteurEnFin(string? titre, IEnumerable<string?> auteurs)
    {
        var s = titre?.Trim();
        if (string.IsNullOrEmpty(s))
        {
            return null;
        }

        foreach (var auteur in auteurs)
        {
            var nom = auteur?.Trim();
            if (string.IsNullOrEmpty(nom) || nom.Length >= s.Length)
            {
                // Un nom aussi long que le titre ne laisserait rien : c'est une biographie
                // ou un recueil, pas une notice sale.
                continue;
            }

            var debut = s.Length - nom.Length;

            // IgnoreNonSpace ignore les accents sans changer les longueurs, contrairement à une
            // normalisation NFD : les indices calculés restent valables sur la chaîne d'origine.
            var identique = string.Compare(
                s, debut, nom, 0, nom.Length,
                CultureInfo.InvariantCulture,
                CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) == 0;

            if (!identique)
            {
                continue;
            }

            // Le nom doit commencer sur une frontière de mot, sinon « …Barrau » couperait aussi
            // dans un mot qui se termine par les mêmes lettres.
            if (char.IsLetterOrDigit(s[debut - 1]))
            {
                continue;
            }

            var reste = s[..debut].TrimEnd(' ', ',', ';', '.', '-', '/', ':');
            if (reste.Length > 0)
            {
                return reste;
            }
        }

        return s;
    }

    /// <summary>
    /// Auteur : retirer les dates entre parenthèses, le rôle qui suit le point,
    /// puis inverser « Nom, Prénom » en « Prénom Nom ».
    /// </summary>
    public static string? Auteur(string? brut)
    {
        var s = brut?.Trim();
        if (string.IsNullOrEmpty(s))
        {
            return null;
        }

        // 1. Dates de vie et autres qualificatifs entre parenthèses : (1840-1902), (1932-....)
        s = Parentheses().Replace(s, string.Empty).Trim();

        // 2. Rôle après le point : « . Auteur du texte », « . Préfacier ».
        var pointRole = IndexDuPointDeRole(s);
        if (pointRole >= 0)
        {
            // Quand le rôle suit une initiale (« Cormen, Thomas H. Auteur du texte »), le point
            // qui sert de séparateur appartient aussi au nom : on le conserve.
            s = EstPointDInitiale(s, pointRole) ? s[..(pointRole + 1)] : s[..pointRole];
        }

        s = RetirerPonctuationFinale(s);

        // 3. Inversion « Nom, Prénom » → « Prénom Nom » (sur la première virgule seulement).
        var virgule = s.IndexOf(", ", StringComparison.Ordinal);
        if (virgule > 0)
        {
            var nom = s[..virgule].Trim();
            var prenom = s[(virgule + 2)..].Trim();
            if (nom.Length > 0 && prenom.Length > 0)
            {
                s = $"{prenom} {nom}";
            }
        }

        s = s.Trim();
        return s.Length == 0 ? null : s;
    }

    /// <summary>
    /// Éditeur : retirer la ville entre parenthèses en fin de chaîne.
    /// </summary>
    public static string? Editeur(string? brut)
    {
        var s = brut?.Trim();
        if (string.IsNullOrEmpty(s))
        {
            return null;
        }

        s = ParenthesesFinales().Replace(s, string.Empty).Trim();
        return s.Length == 0 ? null : s;
    }

    /// <summary>
    /// Retire les virgules et espaces de fin, et le point final <b>sauf</b> s'il appartient
    /// à une initiale (« Cormen, Thomas H. » doit garder son point).
    /// </summary>
    private static string RetirerPonctuationFinale(string s)
    {
        s = s.TrimEnd(' ', ',');

        while (s.EndsWith('.'))
        {
            if (EstPointDInitiale(s, s.Length - 1))
            {
                break;
            }

            s = s[..^1].TrimEnd(' ', ',');
        }

        return s;
    }

    /// <summary>
    /// Position du point qui introduit le rôle, ou -1.
    /// </summary>
    /// <remarks>
    /// Un point suivi d'une espace sépare le rôle du nom. Le point d'une initiale
    /// (« Cormen, Thomas <b>H.</b> ») n'en est pas un — le couper amputerait le prénom.
    /// <para>
    /// Mais protéger l'initiale ne doit pas protéger le rôle qui la suit : « Cormen, Thomas H.
    /// Auteur du texte » renvoyait auparavant « Thomas H. Auteur du texte Cormen ». Ce qui
    /// tranche : dans « Nom, Prénom », la seule chose qui puisse suivre une initiale et faire
    /// encore partie du nom est <b>une autre initiale</b> (« Tolkien, J. R. R. »). Tout le
    /// reste est le rôle.
    /// </para>
    /// </remarks>
    private static int IndexDuPointDeRole(string s)
    {
        for (var i = 0; i < s.Length - 1; i++)
        {
            if (s[i] != '.' || s[i + 1] != ' ')
            {
                continue;
            }

            if (EstPointDInitiale(s, i) && CommenceParUneInitiale(s, i + 2))
            {
                continue;
            }

            return i;
        }

        return -1;
    }

    /// <summary>
    /// Le point en <paramref name="i"/> clôt-il une initiale (majuscule isolée) ?
    /// </summary>
    private static bool EstPointDInitiale(string s, int i)
        => i >= 1
           && char.IsUpper(s[i - 1])
           && (i == 1 || s[i - 2] == ' ' || s[i - 2] == '-' || s[i - 2] == '\'');

    /// <summary>
    /// Le texte à partir de <paramref name="debut"/> commence-t-il par une initiale
    /// (une majuscule suivie d'un point ou de la fin de chaîne) ?
    /// </summary>
    private static bool CommenceParUneInitiale(string s, int debut)
        => debut < s.Length
           && char.IsUpper(s[debut])
           && (debut + 1 == s.Length || s[debut + 1] == '.');
}
