///|
/// The result of a contour or filled-contour calculation.
pub struct ContourSet {
  levels : Array[Double]
  filled : Bool
  segments : Array[Array[@core.Point]]
  segment_levels : Array[Double]
  extend_mode : String
  corner_mask : Bool
}

///|
/// Return the contour levels in ascending order.
pub fn ContourSet::levels(self : ContourSet) -> Array[Double] {
  self.levels.copy()
}

///|
/// Return whether this set contains filled bands.
pub fn ContourSet::filled(self : ContourSet) -> Bool {
  self.filled
}

///|
/// Return the filled-contour extension mode.
/// This corresponds to Matplotlib's extend property.
pub fn ContourSet::extend_mode(self : ContourSet) -> String {
  self.extend_mode
}

///|
/// Return whether masked quadrilateral corners are clipped independently.
pub fn ContourSet::corner_mask(self : ContourSet) -> Bool {
  self.corner_mask
}

///|
/// Return the rendered contour paths, grouped in deterministic artist order.
pub fn ContourSet::segments(self : ContourSet) -> Array[Array[@core.Point]] {
  let result : Array[Array[@core.Point]] = []
  for segment in self.segments {
    result.push(segment.copy())
  }
  result
}

///|
/// Return the level associated with each path returned by `segments`.
pub fn ContourSet::segment_levels(self : ContourSet) -> Array[Double] {
  self.segment_levels.copy()
}

///|
/// Return contour paths grouped by their level, like Matplotlib `allsegs`.
pub fn ContourSet::allsegs(
  self : ContourSet,
) -> Array[Array[Array[@core.Point]]] {
  let result : Array[Array[Array[@core.Point]]] = []
  for level in contour_set_group_levels(self) {
    let paths : Array[Array[@core.Point]] = []
    for index in 0.. Array[Double] {
  if !self.filled || self.levels.length() < 2 {
    return self.levels.copy()
  }
  if self.segment_levels.length() > 0 {
    return contour_unique_levels(self.segment_levels)
  }
  let result : Array[Double] = []
  for index in 0..<(self.levels.length() - 1) {
    result.push((self.levels[index] + self.levels[index + 1]) / 2.0)
  }
  result
}

///|
fn contour_unique_levels(values : Array[Double]) -> Array[Double] {
  let result : Array[Double] = []
  for value in values {
    let mut present = false
    for existing in result {
      if existing == value {
        present = true
        break
      }
    }
    if !present {
      result.push(value)
    }
  }
  result
}

///|
fn contour_set_group_levels(set : ContourSet) -> Array[Double] {
  if set.filled && set.segment_levels.length() > 0 {
    contour_unique_levels(set.segment_levels)
  } else {
    set.levels.copy()
  }
}

///|
priv struct ContourVertex {
  point : @core.Point
  value : Double
}

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

///|
fn validate_contour_extend(value : String) -> Unit raise @core.DataError {
  if value != "neither" && value != "min" && value != "max" && value != "both" {
    raise @core.DataError::InvalidContour(
      message="contour extend must be 'neither', 'min', 'max', or 'both'",
    )
  }
}

///|
fn contour_extend_min(value : String) -> Bool {
  value == "min" || value == "both"
}

///|
fn contour_extend_max(value : String) -> Bool {
  value == "max" || value == "both"
}

///|
fn contour_quad_allowed(
  first : ContourVertex,
  second : ContourVertex,
  third : ContourVertex,
  fourth : ContourVertex,
  corner_mask : Bool,
) -> Bool {
  corner_mask ||
  (
    contour_finite(first.value) &&
    contour_finite(second.value) &&
    contour_finite(third.value) &&
    contour_finite(fourth.value)
  )
}

///|
fn contour_axis(
  values : Array[Double],
  count : Int32,
  axis : String,
) -> Array[Double] raise @core.DataError {
  if values.length() == 0 {
    let result = Array(capacity=count)
    for index in 0.. 1 {
      let increasing = values[1] > values[0]
      for index in 1..= previous {
          raise @core.DataError::InvalidContour(
            message="contour coordinates must be strictly monotonic",
          )
        }
      }
    }
    values.copy()
  }
}

///|
fn contour_grid(
  data : Array[Array[Double]],
  x : Array[Double],
  y : Array[Double],
) -> (Array[Array[Double]], Array[Double], Array[Double]) raise @core.DataError {
  if (x.length() == 0) != (y.length() == 0) {
    raise @core.DataError::InvalidContour(
      message="contour x and y coordinates must be supplied together",
    )
  }
  if data.length() < 2 || data[0].length() < 2 {
    raise @core.DataError::InvalidContour(
      message="contour data must have at least two rows and two columns",
    )
  }
  let columns = data[0].length()
  for row in data {
    if row.length() != columns {
      raise @core.DataError::InvalidContour(
        message="contour data rows must have equal length",
      )
    }
  }
  let xs = contour_axis(x, columns, "x")
  let ys = contour_axis(y, data.length(), "y")
  (data, xs, ys)
}

///|
fn contour_data_limits(
  data : Array[Array[Double]],
) -> (Double, Double) raise @core.DataError {
  let mut lower = 0.0
  let mut upper = 0.0
  let mut found = false
  for row in data {
    for value in row {
      if contour_finite(value) {
        if !found {
          lower = value
          upper = value
          found = true
        } else {
          lower = Double::min(lower, value)
          upper = Double::max(upper, value)
        }
      }
    }
  }
  if !found {
    raise @core.DataError::InvalidContour(
      message="contour data must contain at least one finite value",
    )
  }
  (lower, upper)
}

///|
fn contour_levels(
  data : Array[Array[Double]],
  requested : Array[Double],
  nlevels : Int32,
  filled : Bool,
  locator : @ticker.Locator?,
) -> Array[Double] raise @core.DataError {
  if nlevels < 1 {
    raise @core.DataError::InvalidContour(
      message="contour nlevels must be positive",
    )
  }
  if requested.length() > 0 {
    if filled && requested.length() < 2 {
      raise @core.DataError::InvalidContour(
        message="filled contours require at least two levels",
      )
    }
    for index, level in requested {
      if !contour_finite(level) {
        raise @core.DataError::InvalidContour(
          message="contour levels must be finite",
        )
      }
      if index > 0 && level <= requested[index - 1] {
        raise @core.DataError::InvalidContour(
          message="contour levels must be strictly increasing",
        )
      }
    }
    requested.copy()
  } else {
    let (lower, upper) = contour_data_limits(data)
    match locator {
      Some(locator) => {
        let resolved = locator.checked_tick_values(lower, upper)
        if resolved.length() == 0 {
          raise @core.DataError::InvalidContour(
            message="contour locator produced no finite levels",
          )
        }
        if filled && resolved.length() < 2 {
          raise @core.DataError::InvalidContour(
            message="filled contours require at least two locator levels",
          )
        }
        for index, level in resolved {
          if !contour_finite(level) ||
            (index > 0 && level <= resolved[index - 1]) {
            raise @core.DataError::InvalidContour(
              message="contour locator levels must be finite and increasing",
            )
          }
        }
        resolved
      }
      None =>
        if lower == upper {
          if filled {
            [lower - 0.5, upper + 0.5]
          } else {
            [lower]
          }
        } else {
          let count = if filled { nlevels + 1 } else { nlevels }
          let actual_count = Int32::max(count, 2)
          let result = Array(capacity=actual_count)
          for index in 0.. @core.Point? {
  if !contour_finite(first.value) || !contour_finite(second.value) {
    None
  } else if (first.value < level && second.value >= level) ||
    (second.value < level && first.value >= level) {
    let denominator = second.value - first.value
    if denominator == 0.0 {
      Some(first.point)
    } else {
      let fraction = (level - first.value) / denominator
      Some(
        @core.point(
          first.point.x() + (second.point.x() - first.point.x()) * fraction,
          first.point.y() + (second.point.y() - first.point.y()) * fraction,
        ),
      )
    }
  } else {
    None
  }
}

///|
fn contour_push_unique(
  points : Array[@core.Point],
  point : @core.Point,
) -> Unit {
  for existing in points {
    if existing.x() == point.x() && existing.y() == point.y() {
      return
    }
  }
  points.push(point)
}

///|
fn contour_triangle_segments(
  first : ContourVertex,
  second : ContourVertex,
  third : ContourVertex,
  level : Double,
) -> Array[Array[@core.Point]] {
  let intersections = Array(capacity=3)
  match contour_intersection(first, second, level) {
    Some(point) => contour_push_unique(intersections, point)
    None => ()
  }
  match contour_intersection(second, third, level) {
    Some(point) => contour_push_unique(intersections, point)
    None => ()
  }
  match contour_intersection(third, first, level) {
    Some(point) => contour_push_unique(intersections, point)
    None => ()
  }
  let result : Array[Array[@core.Point]] = []
  if intersections.length() >= 2 {
    result.push([intersections[0], intersections[1]])
  }
  result
}

///|
/// A graph node used to join contour crossings from adjacent cells or faces.
///
/// Triangle interpolation can produce the same crossing twice with tiny
/// floating-point differences when two faces share an edge.  Building a
/// small tolerance graph lets the renderer emit one continuous polyline.
priv struct ContourGraphNode {
  point : @core.Point
  edges : Array[Int32]
}

///|
priv struct ContourGraphEdge {
  first : Int32
  second : Int32
}

///|
fn contour_points_close(first : @core.Point, second : @core.Point) -> Bool {
  let scale = Double::max(
    1.0,
    Double::max(
      Double::max(
        Double::max(Double::abs(first.x()), Double::abs(first.y())),
        Double::max(Double::abs(second.x()), Double::abs(second.y())),
      ),
      Double::max(
        Double::abs(first.x() - second.x()),
        Double::abs(first.y() - second.y()),
      ),
    ),
  )
  let tolerance = 1.0e-12 * scale
  Double::abs(first.x() - second.x()) <= tolerance &&
  Double::abs(first.y() - second.y()) <= tolerance
}

///|
fn contour_graph_node_index(
  nodes : Array[ContourGraphNode],
  point : @core.Point,
) -> Int32? {
  for index, node in nodes {
    if contour_points_close(node.point, point) {
      return Some(index)
    }
  }
  None
}

///|
fn contour_stitch_segments(
  segments : Array[Array[@core.Point]],
) -> Array[Array[@core.Point]] {
  let nodes : Array[ContourGraphNode] = []
  let edges : Array[ContourGraphEdge] = []
  for segment in segments {
    if segment.length() < 2 {
      continue
    }
    let first = segment[0]
    let second = segment[segment.length() - 1]
    if contour_points_close(first, second) {
      continue
    }
    let first_index = match contour_graph_node_index(nodes, first) {
      Some(index) => index
      None => {
        nodes.push({ point: first, edges: [], })
        nodes.length() - 1
      }
    }
    let second_index = match contour_graph_node_index(nodes, second) {
      Some(index) => index
      None => {
        nodes.push({ point: second, edges: [], })
        nodes.length() - 1
      }
    }
    let edge_index = edges.length()
    edges.push({ first: first_index, second: second_index, })
    nodes[first_index].edges.push(edge_index)
    nodes[second_index].edges.push(edge_index)
  }
  let visited : Array[Bool] = []
  for _ in edges {
    visited.push(false)
  }
  let result : Array[Array[@core.Point]] = []
  for edge_index in 0.. break
        Some(candidate) => {
          visited[candidate] = true
          let candidate_edge = edges[candidate]
          let next = if candidate_edge.first == current {
            candidate_edge.second
          } else {
            candidate_edge.first
          }
          path.push(nodes[next].point)
          current = next
          if current == start {
            break
          }
        }
      }
    }
    if path.length() >= 2 {
      // Keep the repeated endpoint for closed contours so SVG can represent
      // the loop without a separate polygon artist.
      result.push(path)
    }
  }
  result
}

///|
priv struct ContourBoundaryEdge {
  first : Int32
  second : Int32
}

///|
fn contour_boundary_edge_matches(
  edge : ContourBoundaryEdge,
  first : Int32,
  second : Int32,
) -> Bool {
  (edge.first == first && edge.second == second) ||
  (edge.first == second && edge.second == first)
}

///|
/// Merge clipped triangle polygons into boundary loops for one filled band.
///
/// Each internal mesh edge occurs twice.  Removing those pairs leaves only
/// the outer boundary (and any holes), which can then be emitted as SVG
/// polygons without drawing one overlapping polygon per face.
fn contour_union_polygons(
  polygons : Array[Array[@core.Point]],
) -> Array[Array[@core.Point]] {
  let nodes : Array[ContourGraphNode] = []
  let mut boundary : Array[ContourBoundaryEdge] = []
  for polygon in polygons {
    if polygon.length() < 3 {
      continue
    }
    let polygon_nodes : Array[Int32] = []
    for point in polygon {
      let node_index = match contour_graph_node_index(nodes, point) {
        Some(index) => index
        None => {
          nodes.push({ point, edges: [], })
          nodes.length() - 1
        }
      }
      polygon_nodes.push(node_index)
    }
    for index in 0.. Array[ContourVertex] {
  let result : Array[ContourVertex] = []
  if polygon.length() == 0 {
    return result
  }
  let inside = (value : Double) => {
    if keep_above {
      value >= level
    } else {
      value <= level
    }
  }
  for index in 0.. result.push({ point, value: level, })
        None => ()
      }
    } else if !first_inside && second_inside {
      match contour_intersection(first, second, level) {
        Some(point) => result.push({ point, value: level, })
        None => ()
      }
      result.push(second)
    }
  }
  result
}

///|
fn contour_clipped_triangle(
  first : ContourVertex,
  second : ContourVertex,
  third : ContourVertex,
  lower : Double,
  upper : Double,
) -> Array[@core.Point] {
  let initial = [first, second, third]
  let above = contour_clip_polygon(initial, lower, true)
  let clipped = contour_clip_polygon(above, upper, false)
  let result = Array(capacity=clipped.length())
  for vertex in clipped {
    contour_push_unique(result, vertex.point)
  }
  result
}

///|
fn contour_palette_color(
  index : Int32,
  count : Int32,
  colors : Array[String],
  cmap : String,
) -> String raise @core.DataError {
  if colors.length() > 0 {
    colors[index % colors.length()]
  } else {
    let color_map = match @colors.find_colormap(cmap) {
      Some(value) => value
      None =>
        raise @core.DataError::InvalidContour(
          message="unknown contour colormap: \{cmap}",
        )
    }
    let fraction = (Double::from_int(index) + 0.5) /
      Double::from_int(Int32::max(count, 1))
    color_map.sample_extended(fraction).to_css()
  }
}

///|
fn contour_render_filled_band(
  axes : Axes,
  grid : Array[Array[Double]],
  xs : Array[Double],
  ys : Array[Double],
  lower : Double,
  upper : Double,
  style : @core.Style,
  corner_mask : Bool,
) -> Array[Array[@core.Point]] {
  if lower >= upper {
    return []
  }
  let rows = grid.length()
  let columns = grid[0].length()
  let polygons : Array[Array[@core.Point]] = []
  for row in 0..<(rows - 1) {
    for column in 0..<(columns - 1) {
      let (first, second, third, fourth) = contour_triangles(
        grid, xs, ys, row, column,
      )
      if contour_quad_allowed(first, second, third, fourth, corner_mask) {
        let first_polygon = contour_clipped_triangle(
          first, second, third, lower, upper,
        )
        if first_polygon.length() >= 3 {
          polygons.push(first_polygon)
        }
        let second_polygon = contour_clipped_triangle(
          first, third, fourth, lower, upper,
        )
        if second_polygon.length() >= 3 {
          polygons.push(second_polygon)
        }
      }
    }
  }
  let merged = contour_union_polygons(polygons)
  if merged.length() == 1 {
    axes.push_artist(Artist::Polygon(merged[0], style))
  } else if merged.length() > 1 {
    axes.push_artist(Artist::CompoundPolygon(merged, style))
  }
  merged
}

///|
fn contour_triangles(
  data : Array[Array[Double]],
  xs : Array[Double],
  ys : Array[Double],
  row : Int32,
  column : Int32,
) -> (ContourVertex, ContourVertex, ContourVertex, ContourVertex) {
  let first = {
    point: @core.point(xs[column], ys[row]),
    value: data[row][column],
  }
  let second = {
    point: @core.point(xs[column + 1], ys[row]),
    value: data[row][column + 1],
  }
  let third = {
    point: @core.point(xs[column + 1], ys[row + 1]),
    value: data[row + 1][column + 1],
  }
  let fourth = {
    point: @core.point(xs[column], ys[row + 1]),
    value: data[row + 1][column],
  }
  (first, second, third, fourth)
}

///|
fn validate_contour_style(width : Double) -> Unit raise @core.DataError {
  if width.is_nan() || width.is_inf() || width < 0.0 {
    raise @core.DataError::InvalidContour(
      message="contour linewidth must be finite and non-negative",
    )
  }
}

///|
fn validate_contour_linewidths(
  linewidths : Array[Double],
) -> Unit raise @core.DataError {
  for width in linewidths {
    validate_contour_style(width)
  }
}

///|
fn contour_line_color(
  fallback : String,
  colors : Array[String],
  index : Int32,
) -> String {
  if colors.length() == 0 {
    fallback
  } else {
    colors[index % colors.length()]
  }
}

///|
fn contour_linewidth(
  fallback : Double,
  linewidths : Array[Double],
  index : Int32,
) -> Double {
  if linewidths.length() == 0 {
    fallback
  } else {
    linewidths[index % linewidths.length()]
  }
}

///|
/// Draw contour lines over a regular rectangular grid.
pub fn Figure::contour(
  self : Figure,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  corner_mask~ : Bool = true,
  locator? : @ticker.Locator,
  colors~ : Array[String] = [],
  linewidths~ : Array[Double] = [],
) -> ContourSet raise @core.DataError {
  self
  .axes()
  .contour(
    data,
    x~,
    y~,
    levels~,
    nlevels~,
    color~,
    linewidth~,
    label~,
    corner_mask~,
    locator~,
    colors~,
    linewidths~,
  )
}

///|
/// Draw contour lines over a regular rectangular grid on this axes.
pub fn Axes::contour(
  self : Axes,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  corner_mask~ : Bool = true,
  locator~ : @ticker.Locator? = None,
  colors~ : Array[String] = [],
  linewidths~ : Array[Double] = [],
) -> ContourSet raise @core.DataError {
  let (grid, xs, ys) = contour_grid(data, x, y)
  let resolved_levels = contour_levels(grid, levels, nlevels, false, locator)
  validate_contour_style(linewidth)
  validate_contour_linewidths(linewidths)
  let resolved_color = if colors.length() > 0 {
    if color == "" {
      colors[0]
    } else {
      color
    }
  } else {
    self.resolve_color(color)
  }
  let start = self.state.val.artists.length()
  let segments : Array[Array[@core.Point]] = []
  let segment_levels : Array[Double] = []
  let rows = grid.length()
  let columns = grid[0].length()
  for level_index, level in resolved_levels {
    let style = @core.style(
      contour_line_color(resolved_color, colors, level_index),
      width=contour_linewidth(linewidth, linewidths, level_index),
    )
    let raw_segments : Array[Array[@core.Point]] = []
    for row in 0..<(rows - 1) {
      for column in 0..<(columns - 1) {
        let (first, second, third, fourth) = contour_triangles(
          grid, xs, ys, row, column,
        )
        if contour_quad_allowed(first, second, third, fourth, corner_mask) {
          let first_segments = contour_triangle_segments(
            first, second, third, level,
          )
          for segment in first_segments {
            raw_segments.push(segment)
          }
          let second_segments = contour_triangle_segments(
            first, third, fourth, level,
          )
          for segment in second_segments {
            raw_segments.push(segment)
          }
        }
      }
    }
    for path in contour_stitch_segments(raw_segments) {
      self.push_artist(Artist::Line(path, style))
      segments.push(path.copy())
      segment_levels.push(level)
    }
  }
  self.add_auto_legend(
    label,
    contour_line_color(resolved_color, colors, 0),
    LegendKind::Line,
  )
  self.add_container(ContainerKind::Contour, start, label)
  {
    levels: resolved_levels,
    filled: false,
    segments,
    segment_levels,
    extend_mode: "neither",
    corner_mask,
  }
}

///|
/// Draw filled contour bands over a regular rectangular grid.
pub fn Figure::contourf(
  self : Figure,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  colors~ : Array[String] = [],
  cmap~ : String = "viridis",
  label~ : String = "",
  extend_mode~ : String = "neither",
  corner_mask~ : Bool = true,
  locator? : @ticker.Locator,
) -> ContourSet raise @core.DataError {
  self
  .axes()
  .contourf(
    data,
    x~,
    y~,
    levels~,
    nlevels~,
    colors~,
    cmap~,
    label~,
    extend_mode~,
    corner_mask~,
    locator~,
  )
}

///|
/// Draw filled contour bands over a regular rectangular grid on this axes.
pub fn Axes::contourf(
  self : Axes,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  colors~ : Array[String] = [],
  cmap~ : String = "viridis",
  label~ : String = "",
  extend_mode~ : String = "neither",
  corner_mask~ : Bool = true,
  locator~ : @ticker.Locator? = None,
) -> ContourSet raise @core.DataError {
  let (grid, xs, ys) = contour_grid(data, x, y)
  validate_contour_extend(extend_mode)
  let resolved_levels = contour_levels(grid, levels, nlevels, true, locator)
  let bands = resolved_levels.length() - 1
  let (data_lower, data_upper) = contour_data_limits(grid)
  let extension_count = (if contour_extend_min(extend_mode) { 1 } else { 0 }) +
    (if contour_extend_max(extend_mode) { 1 } else { 0 })
  let rendered_bands = bands + extension_count
  let start = self.state.val.artists.length()
  let mut color_index = 0
  let filled_segments : Array[Array[@core.Point]] = []
  let filled_segment_levels : Array[Double] = []
  if contour_extend_min(extend_mode) {
    let fill_color = contour_palette_color(
      color_index, rendered_bands, colors, cmap,
    )
    let paths = contour_render_filled_band(
      self,
      grid,
      xs,
      ys,
      data_lower,
      resolved_levels[0],
      @core.style(fill_color, width=0.0, fill=fill_color),
      corner_mask,
    )
    for path in paths {
      filled_segments.push(path.copy())
      filled_segment_levels.push((data_lower + resolved_levels[0]) / 2.0)
    }
    color_index += 1
  }
  for band in 0.. @core.Point {
  @core.point(
    first.x() + (second.x() - first.x()) * fraction,
    first.y() + (second.y() - first.y()) * fraction,
  )
}

///|
fn contour_same_segment(
  first : Array[@core.Point],
  second : Array[@core.Point],
) -> Bool {
  if first.length() != second.length() {
    return false
  }
  for index in 0.. Array[Array[@core.Point]] {
  if points.length() < 2 {
    return [points.copy()]
  }
  let segment_index = Int32::max(points.length() / 2 - 1, 0)
  let first = points[segment_index]
  let second = points[segment_index + 1]
  let dx = second.x() - first.x()
  let dy = second.y() - first.y()
  let length = (dx * dx + dy * dy).sqrt()
  if length == 0.0 {
    return [points.copy()]
  }
  let left : Array[@core.Point] = []
  for index in 0..<=segment_index {
    left.push(points[index])
  }
  left.push(contour_interpolate(first, second, 0.375))
  let right : Array[@core.Point] = []
  right.push(contour_interpolate(first, second, 0.625))
  for index in (segment_index + 1).. Unit {
  let updated : Array[Artist] = []
  for artist in self.state.val.artists {
    match artist {
      Artist::Line(points, style) if contour_same_segment(points, target) =>
        for part in contour_inline_parts(points) {
          updated.push(Artist::Line(part, style))
        }
      _ => updated.push(artist)
    }
  }
  self.state.val.artists.clear()
  for artist in updated {
    self.state.val.artists.push(artist)
  }
  self.prune_line_originals()
}

///|
/// Add one text label at a representative segment for each contour level.
///
/// `inline` removes a small gap from the representative segment before the
/// text is added. `fontsize` controls the SVG text style.
pub fn Figure::clabel(
  self : Figure,
  contours : ContourSet,
  inline~ : Bool = true,
  fontsize~ : Int32 = 10,
  color~ : String = "#111827",
) -> Unit raise @core.DataError {
  self.axes().clabel(contours, inline~, fontsize~, color~)
}

///|
/// Add one text label at a representative segment for each contour level.
pub fn Axes::clabel(
  self : Axes,
  contours : ContourSet,
  inline~ : Bool = true,
  fontsize~ : Int32 = 10,
  color~ : String = "#111827",
) -> Unit raise @core.DataError {
  if fontsize < 1 {
    raise @core.DataError::InvalidContour(
      message="contour label fontsize must be positive",
    )
  }
  if contours.filled() {
    return
  }
  let style = @core.style(color, font_size=Double::from_int(fontsize))
  for level in contours.levels {
    for index in 0.. 0 {
          if inline {
            self.erase_inline_contour_segment(segment)
          }
          let point = segment[segment.length() / 2]
          self.push_artist(
            Artist::Text(point.x(), point.y(), Double::to_string(level), style),
          )
        }
        break
      }
    }
  }
}