import Foundation

public struct LeanLine: Codable, Equatable, Sendable {
    public var a: Point2
    public var b: Point2
    public var uncertainty: Double
    public init(a: Point2, b: Point2, uncertainty: Double) {
        self.a = a; self.b = b; self.uncertainty = uncertainty
    }
    public func valid(in panel: PixelRect) -> Bool {
        func contains(_ p: Point2) -> Bool {
            p.x.isFinite && p.y.isFinite && p.x >= panel.x && p.y >= panel.y &&
            p.x < panel.x + panel.width && p.y < panel.y + panel.height
        }
        return contains(a) && contains(b) && uncertainty.isFinite && uncertainty > 0 && hypot(b.x-a.x, b.y-a.y) > 2 * uncertainty
    }
}
public enum LeanBasis: String, Codable, CaseIterable, Sendable {
    case imageVertical, assumedLevel, sharedFeature
    public var label: String {
        switch self {
        case .imageVertical: "Image vertical"
        case .assumedLevel: "Assumed level line"
        case .sharedFeature: "Shared stationary feature"
        }
    }
}
public enum LeanState: String, Codable, Sendable { case draft, reviewed, unavailable }

public struct LeanAnnotation: Codable, Equatable, Sendable {
    public let frameID: String
    public let sourceHash: String
    public let panel: PixelRect
    public var wheel: LeanLine?
    public var originalProposal: LeanLine?
    public var reference: LeanLine?
    public var basis: LeanBasis = .imageVertical
    public var state: LeanState = .draft
    public var referenceReviewed = false
    public var featureID = ""
    public var cue = "Visible front tire/rim long axis"
    public var provenance: String
    public init(frame: ReviewFrame, wheel: LeanLine? = nil, provenance: String = "Manual photo annotation") {
        frameID = frame.id; sourceHash = frame.sourceHash; panel = frame.panel
        self.wheel = wheel; self.provenance = provenance
    }
    public func validate(frame: ReviewFrame) throws {
        guard frameID == frame.id, sourceHash == frame.sourceHash, panel == frame.panel,
              !cue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
              !provenance.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
              [wheel, reference, originalProposal].compactMap({ $0 }).allSatisfy({ $0.valid(in: panel) }),
              state != .reviewed || (wheel != nil && (basis == .imageVertical || (reference != nil && referenceReviewed))),
              state != .reviewed || basis != .sharedFeature || !featureID.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            throw PoseError.invalidInput("Lean evidence needs valid, separated endpoints inside its original photo, positive uncertainty, and reviewed reference evidence.")
        }
    }
}

public struct LeanPairContext: Codable, Equatable, Sendable {
    public var approvalDigest: String?
    public var commonCue = false
    public var comparableView = false
    public var comparableHeading = false
    public var comparableReference = false
    public var comparableCameraRoll = false
    public var similarSpeedReported = false
    public init() {}
    public var checksComplete: Bool { commonCue && comparableView && comparableHeading && comparableReference }
}

public enum LeanReview {
    public static func validate(_ document: ReviewDocument) throws {
        guard (1...3).contains(document.schemaVersion), document.schemaVersion == 3 || (document.leanAnnotations.isEmpty && document.leanContexts.isEmpty) else {
            throw PoseError.invalidInput("Lean evidence requires review schema 3.")
        }
        for (id, annotation) in document.leanAnnotations {
            guard let frame = document.frames.first(where: { $0.id == id }) else { throw PoseError.invalidInput("Orphan lean annotation.") }
            try annotation.validate(frame: frame)
        }
        let pairs = try PairReview.resolve(document.frames)
        guard document.leanContexts.keys.allSatisfy({ id in pairs.contains { $0.id == id && $0.referenceID != nil } }) else {
            throw PoseError.invalidInput("Lean comparison requires an existing pair.")
        }
    }
}
