using System.Text;

namespace MaBibli.Shared.Isbn;

/// <summary>
/// Normalisation, validation et conversion des ISBN.
/// </summary>
/// <remarks>
/// <b>Piège bloquant documenté dans CLAUDE.md :</b> la BnF indexe l'ISBN <i>tel qu'imprimé</i>
/// sur le livre. Les ouvrages parus avant 2007 ne portent qu'un ISBN-10 et sont donc
/// introuvables par leur ISBN-13, alors qu'un scanner de code-barres lit toujours un EAN-13.
/// La conversion 13 → 10 n'est pas une optimisation : sans elle, tout le fonds ancien est perdu.
/// </remarks>
public static class IsbnUtils
{
    /// <summary>
    /// Retire tirets, espaces et points ; met le <c>x</c> final en majuscule.
    /// Renvoie <c>null</c> si l'entrée est vide.
    /// </summary>
    public static string? Normaliser(string? brut)
    {
        if (string.IsNullOrWhiteSpace(brut))
        {
            return null;
        }

        var sb = new StringBuilder(brut.Length);
        foreach (var c in brut)
        {
            if (char.IsDigit(c))
            {
                sb.Append(c);
            }
            else if (c is 'x' or 'X')
            {
                sb.Append('X');
            }
            // Tout le reste (tirets, espaces, points, insécables…) est ignoré.
        }

        return sb.Length == 0 ? null : sb.ToString();
    }

    /// <summary>Vrai si <paramref name="isbn"/> (déjà normalisé) est un ISBN-10 valide, clé comprise.</summary>
    public static bool EstIsbn10Valide(string? isbn)
    {
        if (isbn is not { Length: 10 })
        {
            return false;
        }

        var somme = 0;
        for (var i = 0; i < 10; i++)
        {
            var c = isbn[i];
            int valeur;
            if (char.IsDigit(c))
            {
                valeur = c - '0';
            }
            else if (c == 'X' && i == 9)
            {
                // Le X n'est autorisé qu'en position de clé.
                valeur = 10;
            }
            else
            {
                return false;
            }

            somme += valeur * (10 - i);
        }

        return somme % 11 == 0;
    }

    /// <summary>Vrai si <paramref name="isbn"/> (déjà normalisé) est un ISBN-13 / EAN-13 valide, clé comprise.</summary>
    public static bool EstIsbn13Valide(string? isbn)
    {
        if (isbn is not { Length: 13 })
        {
            return false;
        }

        var somme = 0;
        for (var i = 0; i < 13; i++)
        {
            if (!char.IsDigit(isbn[i]))
            {
                return false;
            }

            somme += (isbn[i] - '0') * (i % 2 == 0 ? 1 : 3);
        }

        return somme % 10 == 0;
    }

    /// <summary>Vrai si l'ISBN normalisé est valide sous l'une ou l'autre forme.</summary>
    public static bool EstValide(string? isbn) => EstIsbn10Valide(isbn) || EstIsbn13Valide(isbn);

    /// <summary>
    /// Convertit un ISBN-13 en ISBN-10.
    /// </summary>
    /// <remarks>
    /// Uniquement pour le préfixe <c>978</c> : les ISBN-13 en <c>979</c> n'ont aucun équivalent
    /// ISBN-10 et ne doivent pas être « convertis ». Le calcul suit CLAUDE.md : retirer <c>978</c>,
    /// garder les 9 chiffres, recalculer la clé (somme pondérée 10→2, modulo 11, <c>X</c> si 10).
    /// </remarks>
    /// <returns><c>true</c> et l'ISBN-10 en sortie, ou <c>false</c> si la conversion est impossible.</returns>
    public static bool TryConvertirEnIsbn10(string? isbn13, out string? isbn10)
    {
        isbn10 = null;

        if (!EstIsbn13Valide(isbn13) || !isbn13!.StartsWith("978", StringComparison.Ordinal))
        {
            return false;
        }

        // Les 9 chiffres significatifs : on retire le préfixe 978 et la clé EAN-13.
        var corps = isbn13.Substring(3, 9);

        var somme = 0;
        for (var i = 0; i < 9; i++)
        {
            somme += (corps[i] - '0') * (10 - i);
        }

        var cle = (11 - (somme % 11)) % 11;
        isbn10 = corps + (cle == 10 ? "X" : cle.ToString());
        return true;
    }

    /// <summary>
    /// URL de la couverture chez OpenLibrary, à utiliser <b>quelle que soit la source</b>
    /// des métadonnées : le Dublin Core de la BnF n'en fournit aucune.
    /// </summary>
    /// <remarks>
    /// Le <c>?default=false</c> est indispensable : sans lui OpenLibrary renvoie une image
    /// placeholder au lieu d'un 404, et on afficherait des couvertures vides sans le savoir.
    /// </remarks>
    public static string UrlCouverture(string isbn) =>
        $"https://covers.openlibrary.org/b/isbn/{isbn}-L.jpg?default=false";
}
