import Foundation

// Prototype v2 of the faithfulness check, designed from the v1 failures on cases.json.
// It wraps v1 unchanged and adds two things:
//   1. Canonicalization: shorthand is expanded the same way in both texts before v1 runs,
//      so "thurs" → "Thursday", "third grade" → "3rd grade", "p.t.o." → "PTO" no longer look like additions.
//   2. Meaning checks v1 doesn't have: dropped or added negation, plans becoming done,
//      dropped hedges, added reasons and added praise or criticism.
// It deliberately does NOT try to catch role swaps, reversed feelings or invented events; those need a
// semantic check (see the case study's recommendations).

enum NoteRefinerV2 {
    static func problem(with refined: String, original: String, style: NoteRefiner.Style, person: String = "") -> String? {
        let o = canonical(original), r = canonical(refined)
        if let surface = NoteRefiner.problem(with: r, original: o, style: style, person: person) { return surface }

        let ow = words(original), rw = words(refined)
        let negO = ow.filter(isNegation).count, negR = rw.filter(isNegation).count
        if negR < negO { return "dropped a negation" }
        if negR > negO { return "added a negation" }

        if futureMarkers(ow) > 0 && futureMarkers(rw) == 0 { return "a plan became done" }
        if ow.contains(where: hedges.contains) && !rw.contains(where: hedges.contains) { return "dropped a hedge" }

        if let reason = rw.first(where: { causal.contains($0) && !ow.contains($0) }) { return "added a reason (\(reason))" }
        if let judgment = rw.first(where: { evaluative.contains($0) && !ow.contains($0) }) { return "added a judgment (\(judgment))" }
        return nil
    }

    // MARK: Canonicalization

    private static let abbreviations: [String: String] = [
        "mon": "Monday", "tue": "Tuesday", "tues": "Tuesday", "wed": "Wednesday", "thu": "Thursday",
        "thur": "Thursday", "thurs": "Thursday", "fri": "Friday",
        "jan": "January", "feb": "February", "apr": "April", "jun": "June", "jul": "July", "aug": "August",
        "sep": "September", "sept": "September", "oct": "October", "nov": "November", "dec": "December",
    ]
    private static let ordinals = ["first": 1, "second": 2, "third": 3, "fourth": 4, "fifth": 5, "sixth": 6,
                                   "seventh": 7, "eighth": 8, "ninth": 9, "tenth": 10, "eleventh": 11, "twelfth": 12]
    private static let suffix = [1: "st", 2: "nd", 3: "rd"]

    static func canonical(_ text: String) -> String {
        var t = text
        // "p.t.o." → "PTO"
        t = replace(#"\b(?:[A-Za-z]\.){2,}"#, in: t) { $0.replacingOccurrences(of: ".", with: "").uppercased() }
        // "thurs" / "sept." → "Thursday" / "September"
        t = replace(#"\b[A-Za-z]+\b\.?"#, in: t) { word in
            let bare = word.hasSuffix(".") ? String(word.dropLast()) : word
            return abbreviations[bare.lowercased()] ?? word
        }
        // "third grade" → "3rd grade" (only before words where the ordinal is clearly a number)
        t = replace(#"(?i)\b(first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|eleventh|twelfth)(?=\s+(grade|graders?|period|time|floor|quarter|place|day|week)\b)"#, in: t) { word in
            let n = ordinals[word.lowercased()]!
            return "\(n)\(suffix[n % 10 == 0 || n > 3 ? 0 : n] ?? "th")"
        }
        // "a hundred" → "100"
        t = replace(#"(?i)\ba hundred\b"#, in: t) { _ in "100" }
        return t
    }

    private static func replace(_ pattern: String, in text: String, with transform: (String) -> String) -> String {
        guard let regex = try? NSRegularExpression(pattern: pattern) else { return text }
        var result = text
        for match in regex.matches(in: text, range: NSRange(text.startIndex..., in: text)).reversed() {
            guard let range = Range(match.range, in: result) else { continue }
            result.replaceSubrange(range, with: transform(String(result[range])))
        }
        return result
    }

    // MARK: Meaning checks

    private static func words(_ text: String) -> [String] {
        let lowered = text.lowercased().replacingOccurrences(of: "’", with: "'")
        guard let regex = try? NSRegularExpression(pattern: #"[a-z]+(?:'[a-z]+)?"#) else { return [] }
        return regex.matches(in: lowered, range: NSRange(lowered.startIndex..., in: lowered))
            .compactMap { Range($0.range, in: lowered).map { String(lowered[$0]) } }
    }

    private static func isNegation(_ w: String) -> Bool {
        ["not", "no", "never", "none", "nobody", "nothing", "cannot", "without"].contains(w) || w.hasSuffix("n't")
    }

    private static func futureMarkers(_ ws: [String]) -> Int {
        var n = ws.filter { ["will", "won't", "tomorrow", "plan", "plans"].contains($0) || $0.hasSuffix("'ll") }.count
        for (a, b) in zip(ws, ws.dropFirst()) where a == "going" && b == "to" { n += 1 }
        return n
    }

    private static let hedges: Set<String> = ["think", "thought", "might", "maybe", "perhaps", "probably", "possibly",
                                              "seems", "seem", "seemed", "appears", "appeared", "apparently",
                                              "likely", "unsure", "guess", "may"]
    private static let causal: Set<String> = ["because", "due", "therefore", "since", "caused", "reason"]
    private static let evaluative: Set<String> = ["excellent", "effective", "effectively", "outstanding", "amazing",
                                                  "great", "wonderful", "impressive", "exceptional", "highly",
                                                  "successful", "successfully", "poor", "poorly", "terrible", "awful",
                                                  "ineffective", "inappropriate", "unprofessional"]
}
