import Foundation

public enum DifferenceDirection: String, Sendable { case greater, smaller }
public struct FeedbackRule: Sendable {
    public let id: String
    public let version: String
    public let feature: PairFeature
    public let direction: DifferenceDirection
    public let threshold: Double?
    public let priority: Int
    public let suggestion: String
    public let limitation: String
    public let reviewProvenance: String?
    public var requiresCorner: Bool
    public var isSupported: Bool {
        guard let threshold, threshold.isFinite, threshold > 0, threshold <= 180,
              let reviewProvenance, !reviewProvenance.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return false }
        return !suggestion.isEmpty && !limitation.isEmpty && !version.isEmpty && !id.isEmpty
    }
    public init(id: String, version: String, feature: PairFeature, direction: DifferenceDirection, threshold: Double?, priority: Int,
                suggestion: String, limitation: String, reviewProvenance: String?, requiresCorner: Bool) {
        self.id = id; self.version = version; self.feature = feature; self.direction = direction
        self.threshold = threshold; self.priority = priority; self.suggestion = suggestion; self.limitation = limitation
        self.reviewProvenance = reviewProvenance; self.requiresCorner = requiresCorner
    }
}
public struct FocusPoint: Equatable, Sendable, Identifiable {
    public let ruleID: String
    public let ruleVersion: String
    public let pairID: String
    public let feature: PairFeature
    public let joints: [String]
    public let observation: PairObservation
    public let suggestion: String
    public let limitation: String
    public let reviewProvenance: String
    public var id: String { ruleID }
}
public struct RuleCatalog: Sendable {
    public let version: String
    public let rules: [FeedbackRule]
    public init(version: String, rules: [FeedbackRule]) { self.version = version; self.rules = rules }
    // No coach-reviewed thresholds or wording have been supplied. Test catalogs are never used by the app.
    public static let production = RuleCatalog(version: "1-pending-review", rules: [])
    public func evaluate(_ observations: [PairObservation], context: PairContext) -> [FocusPoint] {
        var emitted = Set<String>()
        return Array(rules.sorted { ($0.priority, $0.id) < ($1.priority, $1.id) }.compactMap { rule -> FocusPoint? in
            guard rule.isSupported,
                  context.comparability[rule.feature] == .comparable,
                  !rule.requiresCorner || context.cornerDirection != .unknown,
                  let observation = observations.first(where: { $0.feature == rule.feature }), let delta = observation.delta,
                  (rule.direction == .greater ? delta : -delta) >= rule.threshold! else { return nil }
            guard emitted.insert(rule.feature.rawValue + rule.direction.rawValue).inserted else { return nil }
            return FocusPoint(ruleID: rule.id, ruleVersion: rule.version, pairID: context.pairID, feature: rule.feature,
                              joints: rule.feature.joints, observation: observation, suggestion: rule.suggestion,
                              limitation: rule.limitation, reviewProvenance: rule.reviewProvenance!)
        }.prefix(3))
    }
}
