///|
fn valid_polar_circle(circle : (Double, Double, Double)) -> Bool {
  finite(circle.0) && finite(circle.1) && finite(circle.2) && circle.2 > 0.0
}

///|
fn copy_polar_circles(
  records : Array[(Int32, (Double, Double, Double))],
) -> Map[Int32, (Double, Double, Double)?] {
  let result : Map[Int32, (Double, Double, Double)?] = Map([])
  for (panel, circle) in records {
    let value = if panel >= 0 && valid_polar_circle(circle) {
      Some(circle)
    } else {
      None
    }
    match result.get(panel) {
      None => result[panel] = value
      Some(previous) => if previous != value { result[panel] = None }
    }
  }
  result
}

///|
/// The exact immutable data-clip circle for this prepared polar panel.
/// Invalid or conflicting records return None and keep their panel unselectable.
pub fn Drawing::polar_circle(
  self : Drawing,
  panel_index : Int32,
) -> (Double, Double, Double)? {
  self.polar_circles.get(panel_index).bind(value => value)
}

///|
fn Drawing::polar_admits(
  self : Drawing,
  panel_index : Int32,
  x : Double,
  y : Double,
  required : Bool,
) -> Bool {
  match self.polar_circles.get(panel_index) {
    None => !required
    Some(None) => false
    Some(Some((cx, cy, radius))) => {
      let dx = x - cx
      let dy = y - cy
      let distance = hit_distance(dx, dy)
      // Use the same projection-roundoff bound as the polar collector. It is
      // intrinsic numeric error, never a CSS or caller-supplied hit tolerance.
      let roundoff = 4.0 *
        2.220446049250313e-16 *
        x.abs().max(y.abs()).max(cx.abs()).max(cy.abs()).max(radius.abs())
      finite(dx) &&
      finite(dy) &&
      finite(distance) &&
      (distance <= radius || distance - radius <= roundoff)
    }
  }
}

///|
fn HitKind::requires_polar_circle(self : HitKind) -> Bool {
  self is (Polar | PolarBar)
}