import Foundation

/// On-device deterministic answer grader with partial-credit support.
/// No network calls, no API keys. Handles accent stripping and single-char typos.
public enum AnswerGrader {

    /// Evaluate `userText` against the canonical answer (plus alternates).
    /// Returns `.correct` for exact/normalized match, `.close` for accent-strip
    /// match or Levenshtein distance 1, `.wrong` otherwise.
    public static func grade(userText: String, canonical: String, alternates: [String] = []) -> TextbookGrade {
        let candidates = [canonical] + alternates
        let normalizedUser = normalize(userText)
        if normalizedUser.isEmpty { return .wrong }

        for c in candidates {
            if normalize(c) == normalizedUser { return .correct }
        }
        for c in candidates {
            if stripAccents(normalize(c)) == stripAccents(normalizedUser) {
                return .close
            }
        }
        for c in candidates {
            if levenshtein(normalizedUser, normalize(c)) <= 1 {
                return .close
            }
        }
        return .wrong
    }

    /// Lowercase, collapse whitespace, strip leading/trailing punctuation.
    public static func normalize(_ s: String) -> String {
        let lowered = s.lowercased(with: Locale(identifier: "es"))
        let collapsed = lowered.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression)
        let trimmed = collapsed.trimmingCharacters(in: .whitespacesAndNewlines)
        let punct = CharacterSet(charactersIn: ".,;:!?¿¡\"'()[]{}—–-")
        return trimmed.trimmingCharacters(in: punct)
    }

    /// Remove combining diacritics (á→a, ñ→n, ü→u).
    public static func stripAccents(_ s: String) -> String {
        s.folding(options: .diacriticInsensitive, locale: Locale(identifier: "en"))
    }

    /// Standard Levenshtein edit distance.
    public static func levenshtein(_ a: String, _ b: String) -> Int {
        if a == b { return 0 }
        if a.isEmpty { return b.count }
        if b.isEmpty { return a.count }
        let aa = Array(a)
        let bb = Array(b)
        var prev = Array(0...bb.count)
        var curr = Array(repeating: 0, count: bb.count + 1)
        for i in 1...aa.count {
            curr[0] = i
            for j in 1...bb.count {
                let cost = aa[i - 1] == bb[j - 1] ? 0 : 1
                curr[j] = min(
                    prev[j] + 1,
                    curr[j - 1] + 1,
                    prev[j - 1] + cost
                )
            }
            swap(&prev, &curr)
        }
        return prev[bb.count]
    }
}
