///|
/// The three practical fit outcomes for a hole and shaft pair.
pub enum FitClassification {
  Clearance
  Transition
  Interference
} derive(Debug, Eq)

///|
/// Worst-case clearance analysis for a cylindrical fit.
pub struct FitAnalysis {
  hole_nominal : Double
  shaft_nominal : Double
  nominal_clearance : Double
  minimum_clearance : Double
  maximum_clearance : Double
  classification : FitClassification
} derive(Debug, Eq)

///|
/// Classify a clearance interval by its position relative to zero.
pub fn classify_clearance(clearance : Interval) -> FitClassification {
  if clearance.lower > 0.0 {
    Clearance
  } else if clearance.upper < 0.0 {
    Interference
  } else {
    Transition
  }
}

///|
/// Analyze the complete worst-case range of a hole and shaft fit.
pub fn analyze_fit(hole : Dimension, shaft : Dimension) -> FitAnalysis {
  let nominal_clearance = hole.nominal - shaft.nominal
  let total_tolerance = hole.tolerance + shaft.tolerance
  let minimum_clearance = nominal_clearance - total_tolerance
  let maximum_clearance = nominal_clearance + total_tolerance
  let clearance = Interval::new(minimum_clearance, maximum_clearance)
  {
    hole_nominal: hole.nominal,
    shaft_nominal: shaft.nominal,
    nominal_clearance,
    minimum_clearance,
    maximum_clearance,
    classification: classify_clearance(clearance),
  }
}

///|
/// Return the complete clearance interval for a fit.
pub fn FitAnalysis::clearance(self : FitAnalysis) -> Interval {
  Interval::new(self.minimum_clearance, self.maximum_clearance)
}

///|
/// Return whether every possible fit outcome lies in the requested interval.
pub fn FitAnalysis::accepts(self : FitAnalysis, requested : Interval) -> Bool {
  self.minimum_clearance >= requested.lower &&
  self.maximum_clearance <= requested.upper
}

///|
/// Return the signed margin to the requested clearance interval.
pub fn FitAnalysis::margin(self : FitAnalysis, requested : Interval) -> Double {
  let lower_margin = self.minimum_clearance - requested.lower
  let upper_margin = requested.upper - self.maximum_clearance
  if lower_margin < upper_margin {
    lower_margin
  } else {
    upper_margin
  }
}

///|
/// Return whether the fit guarantees at least the requested clearance.
pub fn FitAnalysis::guarantees_clearance(
  self : FitAnalysis,
  minimum : Double,
) -> Bool {
  self.minimum_clearance >= minimum
}

///|
/// Return whether the fit guarantees no more than the requested clearance.
pub fn FitAnalysis::limits_clearance(
  self : FitAnalysis,
  maximum : Double,
) -> Bool {
  self.maximum_clearance <= maximum
}

///|
/// A named assembly fit requirement for batch evaluation.
pub struct FitRequirement {
  name : String
  minimum_clearance : Double
  maximum_clearance : Double
} derive(Debug, Eq)

///|
/// Create a fit requirement with explicit lower and upper bounds.
pub fn FitRequirement::new(
  name : String,
  minimum_clearance : Double,
  maximum_clearance : Double,
) -> FitRequirement {
  if maximum_clearance < minimum_clearance {
    abort("fit requirement maximum must not be below minimum")
  }
  { name, minimum_clearance, maximum_clearance }
}

///|
/// Evaluate a fit against a named requirement.
pub fn FitRequirement::evaluate(
  self : FitRequirement,
  fit : FitAnalysis,
) -> ConstraintStatus {
  let requested = Interval::new(self.minimum_clearance, self.maximum_clearance)
  if fit.accepts(requested) {
    Satisfied
  } else if fit.clearance().intersects(requested) {
    Inconclusive
  } else {
    Violated
  }
}

///|
/// Summarize a collection of fit requirements for one analyzed fit.
pub struct FitRequirementReport {
  passed : Int
  failed : Int
  inconclusive : Int
  statuses : Array[(String, ConstraintStatus)]
} derive(Debug, Eq)

///|
/// Evaluate all requirements while preserving their names.
pub fn evaluate_fit_requirements(
  fit : FitAnalysis,
  requirements : Array[FitRequirement],
) -> FitRequirementReport {
  let mut passed = 0
  let mut failed = 0
  let mut inconclusive = 0
  let statuses = requirements.map(requirement => {
    let status = requirement.evaluate(fit)
    match status {
      Satisfied => passed += 1
      Violated => failed += 1
      Inconclusive => inconclusive += 1
    }
    (requirement.name, status)
  })
  { passed, failed, inconclusive, statuses }
}