using System.Text;
using System.Text.RegularExpressions;
using SIGCM2.Domain.Pricing.Exceptions;

namespace SIGCM2.Domain.Pricing.WordCounter;

/// <summary>
/// PRC-001 — Pure domain service for counting words in classified ad text.
///
/// Algorithm (in order):
/// 1. Null/empty → WordCountResult(0, empty)
/// 2. Length check: rawText.Length > 2000 → WordCountValidationException
/// 3. Emoji detection via Rune.EnumerateRunes + IsEmojiRune → EmojiDetectedException
/// 4. Count special chars by category (BEFORE replacement — anti-fraud ordering)
/// 5. Replace specials with space; normalize line breaks; collapse whitespace; trim
/// 6. Split(' ', RemoveEmptyEntries) → token count
/// 7. Return WordCountResult
///
/// Tildes (á é í ó ú ñ etc.) are regular word letters — NOT specials.
/// Hyphens are NOT specials — they split words naturally via whitespace split only when
/// they appear as separators between non-whitespace chars (e.g. "buen-estado" → the hyphen
/// itself becomes a word boundary because Split splits on spaces only, so hyphenated words
/// are NOT split by default). Wait — spec GC-18: "buen-estado casi-nuevo" → TotalWords=4.
/// This means hyphen DOES split. The tokenizer must split on hyphen too.
///
/// Design resolution: after normalization, split on whitespace OR hyphen.
/// Hyphens are treated as word boundaries (split token), not as specials counted in SpecialCharCounts.
/// </summary>
public sealed class WordCounterService
{
    public const int MaxInputLength = 2000;

    // Category patterns — order matters for counting (BEFORE replacement)
    private static readonly (string Category, Regex Pattern)[] CategoryPatterns =
    [
        ("Currency",    new Regex(@"[\$€¥£]", RegexOptions.Compiled)),
        ("Percentage",  new Regex(@"%",        RegexOptions.Compiled)),
        ("Exclamation", new Regex(@"[!¡]",     RegexOptions.Compiled)),
        ("Question",    new Regex(@"[?¿]",     RegexOptions.Compiled)),
    ];

    // Collapses any run of spaces/tabs into a single space
    private static readonly Regex WhitespaceCollapseRegex = new(@"[ \t]+", RegexOptions.Compiled);

    public WordCountResult Count(string? rawText)
    {
        // Step 1: null/empty fast path
        if (string.IsNullOrEmpty(rawText))
            return new WordCountResult(0, new Dictionary<string, int>());

        // Step 2: length check (on raw input, pre-normalization)
        if (rawText.Length > MaxInputLength)
            throw new WordCountValidationException(
                nameof(rawText),
                $"El texto supera el máximo de {MaxInputLength} caracteres.");

        // Step 3: emoji detection — fail-fast on first emoji rune found
        foreach (var rune in rawText.EnumerateRunes())
        {
            if (IsEmojiRune(rune))
                throw new EmojiDetectedException(rune.Value);
        }

        // Step 4: count specials by category on ORIGINAL text (anti-fraud ordering)
        var counts = new Dictionary<string, int>();
        foreach (var (category, pattern) in CategoryPatterns)
        {
            var matchCount = pattern.Matches(rawText).Count;
            if (matchCount > 0)
                counts[category] = matchCount;
        }

        // Step 5: normalize
        // 5a. Replace specials with space (each occurrence → space, enabling anti-fraud split)
        var normalized = rawText;
        foreach (var (_, pattern) in CategoryPatterns)
            normalized = pattern.Replace(normalized, " ");

        // 5b. Normalize line endings to space
        normalized = normalized.Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' ');

        // 5c. Replace hyphens with space (GC-18: "buen-estado" → 2 tokens)
        //     Hyphens are word-boundary separators, not special counted chars.
        normalized = normalized.Replace('-', ' ');

        // 5d. Collapse consecutive spaces/tabs to single space, then trim
        normalized = WhitespaceCollapseRegex.Replace(normalized, " ").Trim();

        // Step 6: tokenize on space
        if (string.IsNullOrEmpty(normalized))
            return new WordCountResult(0, counts);

        var tokens = normalized.Split(' ', StringSplitOptions.RemoveEmptyEntries);

        // Step 7: return result
        return new WordCountResult(tokens.Length, counts);
    }

    /// <summary>
    /// Returns true if the given string contains any emoji codepoint.
    /// Used by validators that must reject emojis in user-facing identifiers
    /// (e.g. ChargeableCharConfig.Symbol) where the frontend blocker can be bypassed
    /// by direct API calls. Shares the same IsEmojiRune Unicode ranges used by Count().
    /// </summary>
    public static bool ContainsEmoji(string? text)
    {
        if (string.IsNullOrEmpty(text)) return false;
        foreach (var rune in text.EnumerateRunes())
        {
            if (IsEmojiRune(rune)) return true;
        }
        return false;
    }

    /// <summary>
    /// Returns true if the given rune is an emoji codepoint.
    /// Covers: Extended Pictographics, Misc Symbols, Dingbats, Variation Selector-16, ZWJ.
    /// </summary>
    internal static bool IsEmojiRune(Rune r)
    {
        int v = r.Value;

        // Main emoji blocks
        if (v >= 0x1F300 && v <= 0x1F5FF) return true; // Misc Symbols & Pictographs
        if (v >= 0x1F600 && v <= 0x1F64F) return true; // Emoticons
        if (v >= 0x1F680 && v <= 0x1F6FF) return true; // Transport & Map
        if (v >= 0x1F700 && v <= 0x1F77F) return true; // Alchemical Symbols
        if (v >= 0x1F900 && v <= 0x1F9FF) return true; // Supplemental Symbols & Pictographs
        if (v >= 0x1FA00 && v <= 0x1FAFF) return true; // Symbols and Pictographs Extended-A

        // Misc Symbols and Dingbats (conditional — many non-emoji chars here too,
        // but the design includes these ranges)
        if (v >= 0x2600 && v <= 0x26FF) return true;   // Misc Symbols (⚡☀️etc.)
        if (v >= 0x2700 && v <= 0x27BF) return true;   // Dingbats (✂️✈️etc.)

        // Variation Selector-16 (U+FE0F) — used to force emoji presentation
        if (v == 0xFE0F) return true;

        // Zero Width Joiner (U+200D) — used in compound emoji (👨‍👩‍👧, 🧑‍🤝‍🧑)
        if (v == 0x200D) return true;

        return false;
    }
}
