import Foundation

/// Review criteria, not a pose classifier. Every subject gets every topic, including missing evidence.
public enum CoachingTopic: String, Codable, CaseIterable, Sendable {
    case grip, levers, insideElbow, outsideArm, torso, hips, knees, footAngle, pegContact
    public var title: String {
        switch self {
        case .grip: "Wrist and grip freedom"
        case .levers: "Fingers and control access"
        case .insideElbow: "Inside elbow"
        case .outsideArm: "Outside arm"
        case .torso: "Head, shoulders and torso"
        case .hips: "Hip position and room to move"
        case .knees: "Inside and outside knees"
        case .footAngle: "Foot angle and toe clearance"
        case .pegContact: "Ball of foot and peg placement"
        }
    }
    public var cue: String {
        switch self {
        case .grip: "Check whether a comfortable diagonal screwdriver grip lets the inside arm and torso move together. A straight wrist is not a fault; do not force extra bend. A still photo cannot prove grip tension."
        case .levers: "Check brake access and smooth throttle closure in both turn directions. Index/middle fingers may cover the brake while the others retain light contact, if reach, force and full lever travel permit. Covering is not squeezing. Clutch coverage depends on the task; remaining fingers must not obstruct disengagement."
        case .insideElbow: "Look for room for the inside elbow to fold and move down/out with the torso. Assess the whole arm and control access, without copying an exact angle or inferring bar load."
        case .outsideArm: "Look for a comfortable longer reach across the tank that accommodates inward torso movement. More extended does not mean locked; pressure and tension are not visible."
        case .torso: "Assess head and inside shoulder displacement with the chest and hip hinge. Consider the cornering moment and camera view; do not prescribe maximum head drop or infer gaze through an opaque visor."
        case .hips: "Inspect visible pelvis/seat relationship and space for the chest to move inward. Do not infer hidden hip rotation, seat contact or a universal amount of hang-off."
        case .knees: "Assess inside thigh opening and outside knee proximity to the tank together with hips and feet. Knee bend alone does not measure thigh rotation, contact or support."
        case .footAngle: "Look for a downward or outward toe and apparent ground clearance. Check a more tucked toe while retaining control access if relevant; perspective alone cannot prove pavement contact."
        case .pegContact: "Inspect the boot sole and peg when visible. Consider ball-of-foot support during settled cornering while retaining access to shifting/braking as needed. If the contact area is hidden, record it as unassessable; do not infer pressure or demand constant placement."
        }
    }
}

public enum CoachingReviewStatus: String, Codable, Sendable {
    case needsReview, focus, observed, hypothesis, unassessable, notApplicable
    public var label: String {
        switch self {
        case .needsReview: "Needs photo review"
        case .focus: "Focus"
        case .observed: "Observed · no change suggested"
        case .hypothesis: "Check with rider / coach"
        case .unassessable: "Not assessable in this view"
        case .notApplicable: "Not applicable"
        }
    }
}

public struct CoachingChecklistItem: Codable, Equatable, Sendable, Identifiable {
    public var topic: CoachingTopic
    public var status: CoachingReviewStatus
    public var observation: String
    public var id: String { topic.rawValue }
    public init(topic: CoachingTopic, status: CoachingReviewStatus = .needsReview, observation: String = "") {
        self.topic = topic; self.status = status; self.observation = observation
    }
}

public struct CoachingChecklistReview: Codable, Equatable, Sendable {
    public var frameID: String
    public var sourceHash: String
    public var panel: PixelRect
    public var catalogVersion: String
    public var cornerDirection: CornerDirection
    public var provenance: String
    public var items: [CoachingChecklistItem]
    public init(frame: ReviewFrame) {
        frameID = frame.id; sourceHash = frame.sourceHash; panel = frame.panel
        catalogVersion = CoachingChecklist.version; cornerDirection = .unknown
        provenance = "Pending photo review"
        items = CoachingChecklist.rows(review: nil)
    }
    public func validate(frames: [ReviewFrame]) throws {
        guard let frame = frames.first(where: { $0.id == frameID }), frame.role == "subject",
              sourceHash == frame.sourceHash, panel == frame.panel, catalogVersion == CoachingChecklist.version,
              !provenance.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
              items.count == CoachingTopic.allCases.count, Set(items.map(\.topic)) == Set(CoachingTopic.allCases),
              items.allSatisfy({ $0.status == .needsReview || !$0.observation.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }) else {
            throw PoseError.invalidInput("Coaching checklist needs a matching subject source, current catalog, and one evidenced result for every reviewed topic.")
        }
    }
}

public enum CoachingChecklist {
    public static let version = "track-photo-1"
    public static func rows(review: CoachingChecklistReview?) -> [CoachingChecklistItem] {
        CoachingTopic.allCases.map { topic in
            review?.items.first { $0.topic == topic } ?? CoachingChecklistItem(topic: topic)
        }
    }
    public static func load(from url: URL, frames: [ReviewFrame]) throws -> [String: CoachingChecklistReview] {
        let entries = try JSONDecoder().decode([CoachingChecklistReview].self, from: Data(contentsOf: url))
        var result: [String: CoachingChecklistReview] = [:]
        for entry in entries {
            try entry.validate(frames: frames)
            guard result[entry.frameID] == nil else { throw PoseError.invalidInput("Duplicate coaching checklist subject.") }
            result[entry.frameID] = entry
        }
        return result
    }
}
