///|
fn finite(value : Double) -> Bool {
  !value.is_nan() && !value.is_inf()
}

///|
fn hit_distance(x : Double, y : Double) -> Double {
  let x = x.abs()
  let y = y.abs()
  let high = x.max(y)
  let low = x.min(y)
  if high == 0.0 {
    0.0
  } else {
    let ratio = low / high
    high * (1.0 + ratio * ratio).sqrt()
  }
}

///|
fn Drawing::nearest(
  self : Drawing,
  x : Double,
  y : Double,
  radius : Double,
  scale : Double,
) -> HitPoint? {
  if self.width <= 0 ||
    self.height <= 0 ||
    !finite(x) ||
    !finite(y) ||
    !finite(radius) ||
    radius < 0.0 {
    return None
  }
  let mut best : HitPoint? = None
  let mut best_distance = radius
  for point in self.points {
    if !finite(point.screen_x) ||
      !finite(point.screen_y) ||
      !finite(point.data_x) ||
      !finite(point.data_y) {
      continue
    }
    let dx = (point.screen_x - x) * scale
    let dy = (point.screen_y - y) * scale
    if !finite(dx) || !finite(dy) {
      continue
    }
    let distance = hit_distance(dx, dy)
    if distance <= best_distance && (best is None || distance < best_distance) {
      best = Some(point)
      best_distance = distance
    }
  }
  best
}

///|
/// Find the nearest finite point in intrinsic drawing units.
/// Equal-distance ties keep the original point-array order.
pub fn Drawing::hit_test(
  self : Drawing,
  x : Double,
  y : Double,
  radius~ : Double = 10.0,
) -> HitPoint? {
  self.nearest(x, y, radius, 1.0)
}

///|
fn Drawing::viewport_mapping(
  self : Drawing,
  client_x : Double,
  client_y : Double,
  left : Double,
  top : Double,
  width : Double,
  height : Double,
) -> (Double, Double, Double)? {
  if self.width <= 0 ||
    self.height <= 0 ||
    !finite(client_x) ||
    !finite(client_y) ||
    !finite(left) ||
    !finite(top) ||
    !finite(width) ||
    !finite(height) ||
    width <= 0.0 ||
    height <= 0.0 {
    return None
  }
  let intrinsic_width = self.width.to_double()
  let intrinsic_height = self.height.to_double()
  let scale = (width / intrinsic_width).min(height / intrinsic_height)
  if !finite(scale) || scale <= 0.0 {
    return None
  }
  let x = client_x - left
  let y = client_y - top
  if !finite(x) || !finite(y) || x < 0.0 || y < 0.0 || x > width || y > height {
    return None
  }
  let horizontal_padding = (width - intrinsic_width * scale) / 2.0
  let vertical_padding = (height - intrinsic_height * scale) / 2.0
  let x = (x - horizontal_padding) / scale
  let y = (y - vertical_padding) / scale
  if !finite(x) ||
    !finite(y) ||
    x < 0.0 ||
    y < 0.0 ||
    x > intrinsic_width ||
    y > intrinsic_height {
    None
  } else {
    Some((x, y, scale))
  }
}

///|
/// Convert client coordinates for an SVG viewBox="0 0 width height" with
/// preserveAspectRatio="xMidYMid meet". Outside and letterbox hits are absent.
/// The rectangle must be the actual SVG viewport's client rectangle.
pub fn Drawing::viewport_point(
  self : Drawing,
  client_x : Double,
  client_y : Double,
  left : Double,
  top : Double,
  width : Double,
  height : Double,
) -> (Double, Double)? {
  self
  .viewport_mapping(client_x, client_y, left, top, width, height)
  .map(mapping => (mapping.0, mapping.1))
}

///|
/// Hit-test client coordinates with a radius measured in CSS pixels.
pub fn Drawing::hit_test_client(
  self : Drawing,
  client_x : Double,
  client_y : Double,
  left : Double,
  top : Double,
  width : Double,
  height : Double,
  radius~ : Double = 10.0,
) -> HitPoint? {
  match self.viewport_mapping(client_x, client_y, left, top, width, height) {
    Some((x, y, scale)) => self.nearest(x, y, radius, scale)
    None => None
  }
}