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

///|
/// Fill a closed polygon from paired x/y coordinates.
pub fn Figure::fill(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  color~ : String = "",
  label~ : String = "",
) -> Unit raise @core.DataError {
  self.axes().fill(xs, ys, color~, label~)
}

///|
/// Fill a closed polygon through the axes view.
pub fn Axes::fill(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  color~ : String = "",
  label~ : String = "",
) -> Unit raise @core.DataError {
  let values = @core.series(xs, ys)
  if values.length() < 3 {
    raise @core.DataError::InvalidReference(
      message="fill requires at least three points",
    )
  }
  let resolved_color = self.resolve_color(color)
  self.push_artist(
    Artist::Polygon(
      values.points(),
      @core.style(resolved_color, width=0.0, fill=resolved_color),
    ),
  )
  self.add_auto_legend(label, resolved_color, LegendKind::Patch)
}

///|
/// Draw horizontal broken bars from `(start, width)` ranges.
pub fn Figure::broken_barh(
  self : Figure,
  xranges : Array[(Double, Double)],
  yrange : (Double, Double),
  color~ : String = "",
  label~ : String = "",
) -> Unit raise @core.DataError {
  self.axes().broken_barh(xranges, yrange, color~, label~)
}

///|
/// Draw horizontal broken bars through the axes view.
pub fn Axes::broken_barh(
  self : Axes,
  xranges : Array[(Double, Double)],
  yrange : (Double, Double),
  color~ : String = "",
  label~ : String = "",
) -> Unit raise @core.DataError {
  let (y, height) = yrange
  if !finite_additional(y) || !finite_additional(height) || height <= 0.0 {
    raise @core.DataError::InvalidReference(
      message="broken_barh y range must be finite with positive height",
    )
  }
  let resolved_color = self.resolve_color(color)
  for (x, width) in xranges {
    if !finite_additional(x) || !finite_additional(width) || width < 0.0 {
      raise @core.DataError::InvalidReference(
        message="broken_barh ranges must be finite with non-negative widths",
      )
    }
    self.push_artist(
      Artist::Polygon(
        [
          @core.point(x, y),
          @core.point(x + width, y),
          @core.point(x + width, y + height),
          @core.point(x, y + height),
        ],
        @core.style(resolved_color, width=0.0, fill=resolved_color),
      ),
    )
  }
  self.add_auto_legend(label, resolved_color, LegendKind::Patch)
}

///|
fn histogram_edges(
  minimum : Double,
  maximum : Double,
  bins : Int32,
) -> Array[Double] {
  let (lower, upper) = if minimum == maximum {
    (minimum - 0.5, maximum + 0.5)
  } else {
    (minimum, maximum)
  }
  let step = (upper - lower) / Double::from_int(bins)
  let result = Array(capacity=bins + 1)
  for index in 0..<=bins {
    result.push(lower + Double::from_int(index) * step)
  }
  result
}

///|
fn finite_pair_bounds(
  xs : Array[Double],
  ys : Array[Double],
) -> (Double, Double, Double, Double)? {
  let mut xmin : Double? = None
  let mut xmax : Double? = None
  let mut ymin : Double? = None
  let mut ymax : Double? = None
  let count = Int32::min(xs.length(), ys.length())
  for index in 0.. x
          Some(value) => Double::min(value, x)
        },
      )
      xmax = Some(
        match xmax {
          None => x
          Some(value) => Double::max(value, x)
        },
      )
      ymin = Some(
        match ymin {
          None => y
          Some(value) => Double::min(value, y)
        },
      )
      ymax = Some(
        match ymax {
          None => y
          Some(value) => Double::max(value, y)
        },
      )
    }
  }
  match (xmin, xmax, ymin, ymax) {
    (Some(x0), Some(x1), Some(y0), Some(y1)) => Some((x0, x1, y0, y1))
    _ => None
  }
}

///|
/// Compute a rectangular two-dimensional histogram and draw it as a mesh.
pub fn Figure::hist2d(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  bins_x~ : Int32 = 10,
  bins_y~ : Int32 = 10,
  cmap~ : String = "viridis",
) -> Unit raise @core.DataError {
  self.axes().hist2d(xs, ys, bins_x~, bins_y~, cmap~)
}

///|
/// Compute and draw a rectangular two-dimensional histogram.
pub fn Axes::hist2d(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  bins_x~ : Int32 = 10,
  bins_y~ : Int32 = 10,
  cmap~ : String = "viridis",
) -> Unit raise @core.DataError {
  if bins_x <= 0 || bins_y <= 0 {
    raise @core.DataError::InvalidStatistics(
      message="hist2d bins must be positive",
    )
  }
  if xs.length() != ys.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=ys.length(),
    )
  }
  let bounds = match finite_pair_bounds(xs, ys) {
    Some(value) => value
    None =>
      raise @core.DataError::InvalidStatistics(
        message="hist2d requires at least one finite pair",
      )
  }
  let (xmin, xmax, ymin, ymax) = bounds
  let x_edges = histogram_edges(xmin, xmax, bins_x)
  let y_edges = histogram_edges(ymin, ymax, bins_y)
  let counts = Array(capacity=bins_y)
  for _ in 0..= 0 && x_index < bins_x && y_index >= 0 && y_index < bins_y {
        counts[y_index][x_index] = counts[y_index][x_index] + 1.0
      }
    }
  }
  self.pcolormesh(counts, x=x_edges, y=y_edges, cmap~)
}

///|
/// Draw a vector key arrow and label in data coordinates.
pub fn Figure::quiverkey(
  self : Figure,
  x : Double,
  y : Double,
  value : Double,
  label : String,
  color~ : String = "",
) -> Unit raise @core.DataError {
  self.axes().quiverkey(x, y, value, label, color~)
}

///|
/// Draw a vector key arrow and label through the axes view.
pub fn Axes::quiverkey(
  self : Axes,
  x : Double,
  y : Double,
  value : Double,
  label : String,
  color~ : String = "",
) -> Unit raise @core.DataError {
  if !finite_additional(x) || !finite_additional(y) || !finite_additional(value) {
    raise @core.DataError::InvalidQuiver(
      message="quiverkey values must be finite",
    )
  }
  let resolved_color = self.resolve_color(color)
  self.arrow(x, y, value, 0.0, color=resolved_color, label="")
  self.text(x + value, y, label, color=resolved_color)
}

///|
/// Return the effective legend entries for the default axes.
pub fn Figure::get_legend_handles_labels(self : Figure) -> Array[LegendEntry] {
  self.legend_entries()
}

///|
/// Return the effective legend entries for this axes.
pub fn Axes::get_legend_handles_labels(self : Axes) -> Array[LegendEntry] {
  self.legend_entries()
}