import Foundation

/// Cross-platform emoji shortcode parser.
///
/// Desktop and Android use `:emoji_CODE:` format where CODE is the hex-encoded
/// unified Unicode codepoint (e.g., `:emoji_1f631:` → 😱).
/// Multi-codepoint emoji use `-` separator (e.g., `:emoji_1f468-200d-1f4bb:` → 👨‍💻).
///
/// Desktop reference: `TextParser.tsx` pattern `/:emoji_([a-zA-Z0-9_-]+):/`
/// Android reference: `AppleEmojiPicker.kt` → `unifiedToEmoji()`
enum EmojiParser {

    /// Regex matching `:emoji_CODE:` shortcodes.
    /// Matches hex codes with optional `-` separators for multi-codepoint sequences.
    private static let shortcodePattern = try! NSRegularExpression(
        pattern: ":emoji_([a-fA-F0-9]+(?:-[a-fA-F0-9]+)*):",
        options: []
    )

    /// Replaces all `:emoji_CODE:` shortcodes in the text with native Unicode emoji.
    /// Returns the original text unchanged if no shortcodes are found.
    static func replaceShortcodes(in text: String) -> String {
        let range = NSRange(text.startIndex..., in: text)
        let matches = shortcodePattern.matches(in: text, options: [], range: range)

        guard !matches.isEmpty else { return text }

        var result = text
        // Process matches in reverse order so ranges remain valid after replacements.
        for match in matches.reversed() {
            guard let fullRange = Range(match.range, in: result),
                  let codeRange = Range(match.range(at: 1), in: result) else { continue }

            let unified = String(result[codeRange])
            let emoji = unifiedToEmoji(unified)

            if !emoji.isEmpty {
                result.replaceSubrange(fullRange, with: emoji)
            }
        }

        return result
    }

    /// Converts a unified hex code to a native emoji string.
    /// Supports multi-codepoint sequences separated by `-`.
    ///
    /// Android parity: `unifiedToEmoji()` in `AppleEmojiPicker.kt`.
    /// Examples:
    /// - `"1f631"` → "😱"
    /// - `"1f468-200d-1f4bb"` → "👨‍💻"
    /// - `"1f1fa-1f1f8"` → "🇺🇸"
    static func unifiedToEmoji(_ unified: String) -> String {
        unified
            .split(separator: "-")
            .compactMap { part -> String? in
                guard let codePoint = UInt32(part, radix: 16),
                      let scalar = Unicode.Scalar(codePoint) else { return nil }
                return String(scalar)
            }
            .joined()
    }

    /// Converts a native emoji character to its `:emoji_CODE:` shortcode.
    /// Used when inserting emoji from a picker into the message text.
    static func emojiToShortcode(_ emoji: String) -> String {
        let codes = emoji.unicodeScalars.map { String(format: "%x", $0.value) }
        return ":emoji_\(codes.joined(separator: "-")):"
    }

    // MARK: - Emoji-Only Detection

    /// Returns true if text is exactly ONE emoji character (enlarged display).
    /// Multiple emoji use regular text size.
    static func isEmojiOnly(_ text: String) -> Bool {
        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard trimmed.count == 1 else { return false }
        return trimmed.first!.isEmojiCharacter
    }
}

// MARK: - Character Emoji Detection

private extension Character {
    /// True if this character is a visible emoji (excludes digits, #, * which have isEmoji=true).
    var isEmojiCharacter: Bool {
        unicodeScalars.allSatisfy { scalar in
            // Variation selectors and ZWJ are joining characters, not standalone
            scalar.value == 0xFE0F || scalar.value == 0x200D ||
            scalar.properties.isEmojiPresentation ||
            (scalar.properties.isEmoji && scalar.value > 0xFF)
        } && unicodeScalars.contains { $0.properties.isEmoji && $0.value > 0xFF }
    }
}
