///|
pub(all) struct QuantizationReport {
  grid : Int
  samples : Int
  stored_before : Int
  stored_after : Int
  max_error_x : Double
  max_error_y : Double
  half_step_x : Double
  half_step_y : Double
} derive(Debug, ToJson)

///|
fn point_samples(g : Json, out : Array[Array[Double]]) -> Unit {
  match kind(g) {
    "Point" => out.push(array(field(g, "coordinates")).map(number))
    "MultiPoint" =>
      for p in array(field(g, "coordinates")) {
        out.push(array(p).map(number))
      }
    "GeometryCollection" =>
      for c in array(field(g, "geometries")) {
        point_samples(c, out)
      }
    _ => ()
  }
}

///|
/// Measure coordinate rounding error against every source position before coincident-point removal.
pub fn Topology::quantization_report(
  self : Topology,
  grid : Int,
) -> QuantizationReport raise TopoError {
  let output = self.quantize(grid)
  let tr = field(output.data, "transform")
  let scale = array(field(tr, "scale")).map(number)
  let translate = array(field(tr, "translate")).map(number)
  let q = quantizer(scale, translate)
  let samples = []
  for arc in array(field(self.data, "arcs")) {
    for p in array(arc) {
      samples.push(array(p).map(number))
    }
  }
  for name in self.names() {
    point_samples(self.lookup(name), samples)
  }
  let mut ex = 0.0
  let mut ey = 0.0
  for p in samples {
    let encoded = q.point(p)
    ex = ex.max((encoded[0] * scale[0] + translate[0] - p[0]).abs())
    ey = ey.max((encoded[1] * scale[1] + translate[1] - p[1]).abs())
  }
  {
    grid,
    samples: samples.length(),
    stored_before: self.statistics().stored_positions,
    stored_after: output.statistics().stored_positions,
    max_error_x: ex,
    max_error_y: ey,
    half_step_x: scale[0].abs() / 2.0,
    half_step_y: scale[1].abs() / 2.0,
  }
}