import Foundation

// The checking half of PrinciPAL's NoteRefiner, copied verbatim from
// PrinciPAL/ios/PrinciPAL/Capture/NoteRefiner.swift (limits: lines 71–78, checks: lines 186–284)
// and TeacherMatch.tokens from Model/People.swift. Only the model call and UI strings are left out,
// so this compiles without FoundationModels. Do not "fix" anything here: this is the system under test.

extension String {
    var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) }
}

enum TeacherMatch {
    private static let titles: Set<String> = ["mr", "mrs", "ms", "miss", "mx", "dr", "coach", "sr", "sra"]

    static func tokens(_ name: String) -> [String] {
        name.lowercased()
            .components(separatedBy: CharacterSet.letters.inverted)
            .filter { !$0.isEmpty && !titles.contains($0) }
    }
}

enum NoteRefiner {
    enum Style: String, CaseIterable {
        case cleanUp, concise, elaborate, professional

        var limits: (maxGrowth: Double, extra: Int, minRatio: Double, mayDropNumbers: Bool) {
            switch self {
            case .cleanUp: (1.35, 40, 0.33, false)
            case .concise: (1.0, 15, 0.12, true)
            case .elaborate: (2.6, 140, 0.8, false)
            case .professional: (1.9, 80, 0.4, false)
            }
        }
    }

    static func problem(with refined: String, original: String, style: Style, person: String = "") -> String? {
        let limits = style.limits
        guard !refined.isEmpty else { return "empty" }
        if refined.count > Int(Double(original.count) * limits.maxGrowth) + limits.extra { return "too long" }
        if Double(refined.count) < Double(original.count) * limits.minRatio { return "too short" }

        var remaining = numbers(in: original)
        for number in numbers(in: refined) {
            guard let index = remaining.firstIndex(of: number) else { return "added number \(number)" }
            remaining.remove(at: index)
        }
        if !limits.mayDropNumbers && !remaining.isEmpty { return "dropped \(remaining)" }

        let added = addedNames(in: refined, comparedTo: original + " " + person)
        if !added.isEmpty { return "added \(added)" }

        if swappedPeople(refined, original: original, person: person) { return "mixed up who did what" }
        return nil
    }

    static func swappedPeople(_ refined: String, original: String, person: String) -> Bool {
        let firstPerson: Set<String> = ["i", "me", "my", "mine", "myself", "i'm", "i'd", "i'll", "i've", "im", "ive"]
        var others: Set<String> = ["she", "he", "her", "him", "his", "hers", "they", "them", "their", "she's", "he's",
                                   "she'd", "he'd", "she'll", "he'll", "shes", "hes", "teacher"]
        for name in TeacherMatch.tokens(person) where name.count > 1 { others.insert(name) }

        func count(_ words: Set<String>, in text: String) -> Int {
            matches(#"[A-Za-z]+(?:['’][A-Za-z]+)?"#, in: text)
                .map { $0.lowercased().replacingOccurrences(of: "’", with: "'") }
                .map { $0.hasSuffix("'s") && !words.contains($0) ? String($0.dropLast(2)) : $0 }
                .filter(words.contains).count
        }
        return count(firstPerson, in: refined) > count(firstPerson, in: original)
            && count(others, in: refined) < count(others, in: original)
    }

    static func addedNames(in refined: String, comparedTo original: String) -> [String] {
        let known = Set(matches(#"[A-Za-z'’]+"#, in: original).map {
            $0.lowercased().replacingOccurrences(of: #"['’]s$"#, with: "", options: .regularExpression)
        })
        guard let regex = try? NSRegularExpression(pattern: #"\b[A-Z][A-Za-z'’]*"#) else { return [] }
        let range = NSRange(refined.startIndex..., in: refined)
        return regex.matches(in: refined, range: range).compactMap { match -> String? in
            guard let r = Range(match.range, in: refined) else { return nil }
            let before = refined[..<r.lowerBound].trimmingCharacters(in: .whitespaces)
            let afterTitle = before.range(of: #"\b(Mr|Mrs|Ms|Mx|Dr)\.$"#, options: .regularExpression) != nil
            if before.isEmpty || (!afterTitle && ".!?:;\n\"“(".contains(before.last!)) { return nil }
            let word = String(refined[r])
            let bare = word.replacingOccurrences(of: #"['’]s$"#, with: "", options: .regularExpression).lowercased()
            if word == "I" || allowed.contains(bare) { return nil }
            if known.contains(bare) || known.contains(bare + "s") || (bare.hasSuffix("s") && known.contains(String(bare.dropLast()))) {
                return nil
            }
            return word
        }
    }

    private static let allowed: Set<String> = ["mr", "mrs", "ms", "miss", "mx", "dr", "am", "pm"]

    private static let small = ["zero": 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7,
                                "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, "thirteen": 13,
                                "fourteen": 14, "fifteen": 15, "sixteen": 16, "seventeen": 17, "eighteen": 18,
                                "nineteen": 19]
    private static let tens = ["twenty": 20, "thirty": 30, "forty": 40, "fifty": 50, "sixty": 60, "seventy": 70,
                               "eighty": 80, "ninety": 90]

    static func numbers(in text: String) -> [String] {
        var found = matches(#"\d+"#, in: text)
        var current: Int?
        func flush() { if let value = current { found.append(String(value)) }; current = nil }
        for word in text.lowercased().components(separatedBy: CharacterSet.letters.inverted) {
            if let unit = small[word] {
                if let value = current, value >= 20, value < 100, value % 10 == 0, unit < 10 { current = value + unit }
                else { flush(); current = unit }
            } else if let ten = tens[word] {
                flush(); current = ten
            } else if word == "hundred", let value = current {
                current = value * 100
            } else if !word.isEmpty {
                flush()
            }
        }
        flush()
        return found.sorted()
    }

    private static func matches(_ pattern: String, in text: String) -> [String] {
        guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }
        return regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
            .compactMap { Range($0.range, in: text).map { String(text[$0]) } }
    }
}
