import Foundation

public struct Point2: Codable, Equatable, Sendable {
    public var x: Double
    public var y: Double
    public init(x: Double, y: Double) { self.x = x; self.y = y }
    public func distance(to other: Self) -> Double { hypot(x - other.x, y - other.y) }
}
public struct PixelRect: Codable, Equatable, Sendable {
    public var x: Double
    public var y: Double
    public var width: Double
    public var height: Double
    public init(x: Double, y: Double, width: Double, height: Double) {
        self.x = x; self.y = y; self.width = width; self.height = height
    }
    public func validate(imageWidth: Int, imageHeight: Int) throws {
        guard [x,y,width,height].allSatisfy({ $0.isFinite && $0.rounded() == $0 }),
              x >= 0, y >= 0, width > 0, height > 0,
              x + width <= Double(imageWidth), y + height <= Double(imageHeight) else {
            throw PoseError.invalidInput("Crop is outside the image or not integral pixels.")
        }
    }
    public func sourcePoint(normalizedX: Double, normalizedY: Double) -> Point2 {
        Point2(x: x + normalizedX * width, y: y + (1 - normalizedY) * height)
    }
}
public enum PoseError: Error, LocalizedError {
    case invalidInput(String)
    public var errorDescription: String? { switch self { case .invalidInput(let message): message } }
}
public struct Landmark: Codable, Sendable {
    public var name: String
    public var point: Point2
    public var uncertaintyPixels: Double
    public init(name: String, point: Point2, uncertaintyPixels: Double) {
        self.name = name; self.point = point; self.uncertaintyPixels = uncertaintyPixels
    }
}
public struct LandmarkError: Codable, Sendable {
    public var name: String
    public var pixels: Double?
    public var normalized: Double?
    public var uncertaintyPixels: Double
}
public struct Localization: Codable, Sendable {
    public var assessedCount: Int
    public var withinToleranceCount: Int
    public var errors: [LandmarkError]
    public var passes: Bool
    public var reason: String?
    public static func evaluate(predicted: [String: Point2], annotations: [Landmark], torsoLength: Double?) -> Self {
        let validScale = torsoLength.flatMap { $0.isFinite && $0 > 0 ? $0 : nil }
        let errors = annotations.map { mark in
            let delta = predicted[mark.name].flatMap { p -> Double? in
                guard p.x.isFinite, p.y.isFinite else { return nil }
                return mark.point.distance(to: p)
            }
            return LandmarkError(name: mark.name, pixels: delta, normalized: validScale.flatMap { scale in delta.map { $0 / scale } }, uncertaintyPixels: mark.uncertaintyPixels)
        }
        let hits = errors.filter { error in
            guard let scale = validScale, let ratio = error.normalized else { return false }
            return ratio <= 0.05 && error.uncertaintyPixels >= 0 && error.uncertaintyPixels <= scale * 0.05
        }.count
        let passes = validScale != nil && !errors.isEmpty && Double(hits) / Double(errors.count) >= 0.9
        return Self(assessedCount: errors.count, withinToleranceCount: hits, errors: errors, passes: passes,
                    reason: validScale == nil ? "Torso scale is not independently assessable; no normalized accuracy claim." : nil)
    }
}
