///|
pub(all) struct ReconstructionReport {
  total : Int
  positive_depth : Int
  median_depth : Double
  median_parallax : Double
} derive(Debug, Eq)

///|
pub fn reconstruction_report(
  points : ArrayView[@core.Point3],
  left : ArrayView[@core.Ray3],
  right : ArrayView[@core.Ray3],
) -> ReconstructionReport raise @core.GeometryError {
  if points.length() != left.length() ||
    points.length() != right.length() ||
    points.length() == 0 {
    raise @core.GeometryError::NotEnoughPoints(
      "reconstruction report inputs differ",
    )
  }
  let depths : Array[Double] = []
  let mut positive = 0
  for p in points {
    depths.push(p.z)
    if p.z > 0.0 {
      positive += 1
    }
  }
  {
    total: points.length(),
    positive_depth: positive,
    median_depth: @core.median(depths),
    median_parallax: median_parallax(left, right),
  }
}

///|
pub fn reconstruction_usable(
  report : ReconstructionReport,
  minimum_positive_ratio? : Double = 0.8,
  minimum_parallax? : Double = 0.5,
) -> Bool {
  Double::from_int(report.positive_depth) / Double::from_int(report.total) >=
  minimum_positive_ratio &&
  report.median_parallax >= minimum_parallax
}

///|
pub fn reprojection_residual_matrix(
  predicted : ArrayView[@core.Point2],
  observed : ArrayView[@core.Point2],
) -> Array[Array[Double]] raise @core.GeometryError {
  if predicted.length() != observed.length() {
    raise @core.GeometryError::DegenerateInput("residual matrix inputs differ")
  }
  let result : Array[Array[Double]] = []
  for i in 0.. Array[Double] {
  let result : Array[Double] = []
  for row in matrix {
    if row.length() >= 3 {
      result.push(row[2])
    }
  }
  result
}

///|
pub fn triangulation_reprojection_check(
  result : TriangulatedPoint,
  left : @core.Ray3,
  right : @core.Ray3,
  threshold~ : Double,
) -> Bool {
  result.separation <= threshold &&
  triangulated_point_is_in_front(result, left, right)
}