import Foundation

/// Pure-function logic extracted from `ContactsWriter` so it can be unit-tested
/// without requiring the Contacts framework or a real CNContactStore.
///
/// All functions here are free of `CN*` framework types and have no side effects.
public enum ContactsWriterCore {

    // MARK: - Family name composition

    /// Build the Contacts lastName field: emoji sequence + optional preserved human surname.
    ///
    /// Rules (confirmed policy 2026-04-24):
    ///  - If emojis is empty, return any existing human-authored suffix unchanged.
    ///  - If the existing familyName is emoji-only (a prior write of ours), drop it.
    ///  - Otherwise, splice: `<emojis> <extra>` where extra is the non-emoji tail.
    public static func composeFamilyName(emojis: String, existingFamilyName: String) -> String {
        let trimmedExisting = existingFamilyName.trimmingCharacters(in: .whitespacesAndNewlines)
        let extra = extractExtra(from: trimmedExisting)

        if emojis.isEmpty { return extra }
        if extra.isEmpty { return emojis }
        return "\(emojis) \(extra)"
    }

    /// Pull the human-authored non-emoji suffix from a prior familyName value.
    ///
    /// Prior writes look like `🔥👸🌹 Smith`; the leading emoji run is ours and the
    /// tail `Smith` belongs to Quinn. Emoji-only values return `""` so they don't
    /// accumulate on repeated writes.
    public static func extractExtra(from familyName: String) -> String {
        guard !familyName.isEmpty else { return "" }

        var remainder = familyName[...]
        // Skip the leading emoji-and-whitespace run.
        while let first = remainder.first, isEmojiOrSeparator(first) {
            remainder = remainder.dropFirst()
        }
        let tail = String(remainder).trimmingCharacters(in: .whitespacesAndNewlines)
        // If nothing non-emoji survived, there's no human suffix to keep.
        return tail
    }

    /// Returns true for emoji scalars and whitespace — characters we write ourselves.
    public static func isEmojiOrSeparator(_ ch: Character) -> Bool {
        if ch.isWhitespace { return true }
        for scalar in ch.unicodeScalars {
            if scalar.properties.isEmojiPresentation { continue }
            if scalar.properties.isEmoji { continue }
            if scalar.properties.isEmojiModifier { continue }
            if scalar.properties.isEmojiModifierBase { continue }
            if scalar.properties.isJoinControl { continue }
            if scalar.value == 0x200D { continue } // ZWJ
            if scalar.value == 0xFE0F { continue } // VS-16 variation selector
            return false
        }
        return true
    }

    // MARK: - Note merging

    /// Merge a managed block into a CNContact note, preserving text outside the
    /// `<lilith-sync v=1>…</lilith-sync>` sentinels verbatim.
    public static func mergeNote(existing: String, managedBlock: String) -> String {
        let open = "<lilith-sync v=1>"
        let close = "</lilith-sync>"

        guard let openRange = existing.range(of: open),
              let closeRange = existing.range(of: close, range: openRange.upperBound..<existing.endIndex) else {
            // No prior managed block — append to end (or return block alone if note empty).
            if existing.isEmpty { return managedBlock }
            return existing + "\n\n" + managedBlock
        }

        let before = existing[..<openRange.lowerBound]
        let after = existing[closeRange.upperBound...]
        return String(before) + managedBlock + String(after)
    }

}
