///|
pub fn measure_distance(first : Point, second : Point) -> Double {
  distance(first, second)
}

///|
pub fn measure_signed_distance(line : Line, point : Point) -> Double {
  line.a * point.x + line.b * point.y + line.c
}

///|
pub fn measure_angle(first : Point, second : Point) -> Double {
  @math.atan2(second.y - first.y, second.x - first.x)
}

///|
pub fn measure_diameter(circle : Circle) -> Double {
  circle.radius * 2.0
}

///|
pub fn measure_radius(points : Array[Point]) -> Circle {
  detect_circle_from_points(points)
}

///|
pub fn measure_line(points : Array[Point]) -> Line {
  fit_line(points)
}

///|
pub fn line_length(points : Array[Point]) -> Double {
  if points.length() < 2 {
    0.0
  } else {
    let mut result = 0.0
    for i in 1.. Double {
  angle_between_lines(first, second)
}

///|
pub fn parallelism_error(
  reference : Line,
  candidate : Line,
  points : Array[Point],
) -> Double {
  if points.is_empty() {
    0.0
  } else {
    let mut maximum = 0.0
    for point in points {
      maximum = maximum.max(
        (reference.distance_to(point) - candidate.distance_to(point)).abs(),
      )
    }
    maximum
  }
}

///|
pub fn perpendicularity_error(first : Line, second : Line) -> Double {
  (angle_between_lines(first, second) - 1.5707963267948966).abs()
}

///|
pub fn circle_roundness(circle : Circle) -> Double {
  circle.rms_error
}

///|
pub fn circular_runout(points : Array[Point], reference : Point) -> Double {
  if points.is_empty() {
    0.0
  } else {
    let distances = points.map(fn(point) { distance(point, reference) })
    maximum_value(distances) - minimum_value(distances)
  }
}

///|
pub fn bounding_width(bounds : Bounds) -> Double {
  Double::from_int((bounds.max_x - bounds.min_x + 1).max(0))
}

///|
pub fn bounding_height(bounds : Bounds) -> Double {
  Double::from_int((bounds.max_y - bounds.min_y + 1).max(0))
}

///|
pub fn bounding_area(bounds : Bounds) -> Double {
  bounding_width(bounds) * bounding_height(bounds)
}

///|
pub fn region_width(mask : BinaryMask) -> Double {
  bounding_width(mask.bounds())
}

///|
pub fn region_height(mask : BinaryMask) -> Double {
  bounding_height(mask.bounds())
}

///|
pub fn edge_pair_width(edges : Array[EdgePoint], direction : Vector2) -> Double {
  if edges.length() < 2 {
    0.0
  } else {
    let axis = direction.normalized()
    let mut minimum = axis.dot(
      Vector2::Vector2(x=edges[0].point.x, y=edges[0].point.y),
    )
    let mut maximum = minimum
    for edge in edges[1:] {
      let value = axis.dot(Vector2::Vector2(x=edge.point.x, y=edge.point.y))
      minimum = minimum.min(value)
      maximum = maximum.max(value)
    }
    maximum - minimum
  }
}

///|
pub fn repeatability(values : Array[Double]) -> Double {
  describe_samples(values).standard_deviation
}

///|
pub fn bias_against_nominal(values : Array[Double], nominal : Double) -> Double {
  mean_value(values) - nominal
}

///|
pub fn capability_index(
  values : Array[Double],
  lower : Double,
  upper : Double,
) -> Double {
  let summary = describe_samples(values)
  let spread = 6.0 * summary.standard_deviation
  if spread <= 0.0000000001 {
    0.0
  } else {
    (upper - lower) / spread
  }
}

///|
pub fn to_feature_measurement(
  name : String,
  measurement : Measurement,
  confidence : Double,
) -> FeatureMeasurement {
  {
    name,
    value: measurement.value,
    unit: measurement.unit,
    confidence: clamp_double(confidence, 0.0, 1.0),
    decision: measurement.decision,
  }
}

///|
pub fn feature_to_measurement(
  feature : FeatureMeasurement,
  nominal : Double,
  lower : Double,
  upper : Double,
) -> Measurement {
  let tolerance = Tolerance::Tolerance(
    nominal~,
    lower~,
    upper~,
    unit=feature.unit,
  )
  {
    name: feature.name,
    value: feature.value,
    unit: feature.unit,
    decision: tolerance.judge(feature.value),
    deviation: feature.value - nominal,
  }
}

///|
pub fn measurement_passes(measurement : Measurement) -> Bool {
  match measurement.decision {
    Pass => true
    Warn => true
    Fail => false
  }
}

///|
pub fn measurement_score(
  measurement : Measurement,
  tolerance : Tolerance,
) -> Double {
  let span = tolerance.upper - tolerance.lower
  if span <= 0.0 {
    0.0
  } else {
    1.0 - (measurement.value - tolerance.nominal).abs() / span
  }
}