///|
fn validate_tri_mesh(
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
) -> Unit raise @core.DataError {
  if xs.length() != ys.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=ys.length(),
    )
  }
  if xs.length() < 3 || triangles.length() == 0 {
    raise @core.DataError::InvalidContour(
      message="triangular mesh requires vertices and at least one triangle",
    )
  }
  for index in 0..= xs.length() ||
      second >= xs.length() ||
      third >= xs.length() ||
      first == second ||
      first == third ||
      second == third {
      raise @core.DataError::InvalidContour(
        message="triangular mesh indices must be distinct and in range",
      )
    }
  }
}

///|
fn validate_tri_mask(
  mask : Array[Bool],
  triangles : Array[Array[Int32]],
) -> Unit raise @core.DataError {
  if mask.length() != 0 && mask.length() != triangles.length() {
    raise @core.DataError::LengthMismatch(
      expected=triangles.length(),
      actual=mask.length(),
    )
  }
}

///|
fn triangle_is_visible(mask : Array[Bool], index : Int32) -> Bool {
  mask.length() == 0 || !mask[index]
}

///|
fn validate_tri_contour_values(
  values : Array[Double],
) -> Unit raise @core.DataError {
  for value in values {
    if value.is_nan() || value.is_inf() {
      raise @core.DataError::NonFiniteLimit(axis="z")
    }
  }
}

///|
fn tricontour_render_filled_band(
  axes : Axes,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  mask : Array[Bool],
  lower : Double,
  upper : Double,
  style : @core.Style,
) -> Array[Array[@core.Point]] {
  if lower >= upper {
    return []
  }
  let polygons : Array[Array[@core.Point]] = []
  for index, triangle in triangles {
    if !triangle_is_visible(mask, index) {
      continue
    }
    let first = {
      point: @core.point(xs[triangle[0]], ys[triangle[0]]),
      value: values[triangle[0]],
    }
    let second = {
      point: @core.point(xs[triangle[1]], ys[triangle[1]]),
      value: values[triangle[1]],
    }
    let third = {
      point: @core.point(xs[triangle[2]], ys[triangle[2]]),
      value: values[triangle[2]],
    }
    let polygon = contour_clipped_triangle(first, second, third, lower, upper)
    if polygon.length() >= 3 {
      polygons.push(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
}

///|
/// Draw triangle edges from vertices and explicit index triples.
///
/// Use `triangulation` and `triplot_triangulation` when the faces should be
/// generated automatically and reused.
pub fn Figure::triplot(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  mask~ : Array[Bool] = [],
) -> Unit raise @core.DataError {
  self.axes().triplot(xs, ys, triangles, color~, linewidth~, label~, mask~)
}

///|
/// Draw triangle edges from vertices and explicit index triples.
pub fn Axes::triplot(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  mask~ : Array[Bool] = [],
) -> Unit raise @core.DataError {
  let mesh = triangulation(xs, ys, triangles~, mask~)
  let mesh_x = mesh.x()
  let mesh_y = mesh.y()
  let mesh_triangles = mesh.triangles()
  let mesh_mask = mesh.mask()
  if linewidth.is_nan() || linewidth.is_inf() || linewidth < 0.0 {
    raise @core.DataError::InvalidContour(
      message="triplot linewidth must be finite and non-negative",
    )
  }
  let resolved_color = self.resolve_color(color)
  let style = @core.style(resolved_color, width=linewidth)
  for index, triangle in mesh_triangles {
    if !triangle_is_visible(mesh_mask, index) {
      continue
    }
    let first = @core.point(mesh_x[triangle[0]], mesh_y[triangle[0]])
    let second = @core.point(mesh_x[triangle[1]], mesh_y[triangle[1]])
    let third = @core.point(mesh_x[triangle[2]], mesh_y[triangle[2]])
    self.push_artist(Artist::Line([first, second, third, first], style))
  }
  self.add_auto_legend(label, resolved_color, LegendKind::Line)
}

///|
/// Draw each visible edge of a validated triangulation once.
pub fn Figure::triplot_triangulation(
  self : Figure,
  mesh : Triangulation,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
) -> Unit raise @core.DataError {
  self.axes().triplot_triangulation(mesh, color~, linewidth~, label~)
}

///|
/// Draw each visible edge of a validated triangulation once.
pub fn Axes::triplot_triangulation(
  self : Axes,
  mesh : Triangulation,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
) -> Unit raise @core.DataError {
  if linewidth.is_nan() || linewidth.is_inf() || linewidth < 0.0 {
    raise @core.DataError::InvalidContour(
      message="triplot linewidth must be finite and non-negative",
    )
  }
  let xs = mesh.x()
  let ys = mesh.y()
  let resolved_color = self.resolve_color(color)
  let style = @core.style(resolved_color, width=linewidth)
  for (first, second) in mesh.edges() {
    self.push_artist(
      Artist::Line(
        [@core.point(xs[first], ys[first]), @core.point(xs[second], ys[second])],
        style,
      ),
    )
  }
  self.add_auto_legend(label, resolved_color, LegendKind::Line)
}

///|
fn tripcolor_mesh(
  axes : Axes,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  mask : Array[Bool],
  vmin : Double?,
  vmax : Double?,
  cmap : String,
) -> String? raise @core.DataError {
  validate_tri_mesh(xs, ys, triangles)
  validate_tri_mask(mask, triangles)
  if values.length() != xs.length() && values.length() != triangles.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=values.length(),
    )
  }
  let color_map = resolve_colormap(cmap)
  let norm = linear_color_norm(vmin, vmax)
  norm.autoscale_none(values)
  if !norm.scaled() {
    return None
  }
  let mut first_color : String? = None
  let face_values = values.length() == triangles.length()
  for index, triangle in triangles {
    if !triangle_is_visible(mask, index) {
      continue
    }
    let value = if face_values {
      values[index]
    } else {
      let first_value = values[triangle[0]]
      let second_value = values[triangle[1]]
      let third_value = values[triangle[2]]
      (first_value + second_value + third_value) / 3.0
    }
    if norm.is_valid(value) {
      let color = color_map
        .sample_extended(mesh_transform(norm, value))
        .to_css()
      if first_color is None {
        first_color = Some(color)
      }
      axes.push_artist(
        Artist::Polygon(
          [
            @core.point(xs[triangle[0]], ys[triangle[0]]),
            @core.point(xs[triangle[1]], ys[triangle[1]]),
            @core.point(xs[triangle[2]], ys[triangle[2]]),
          ],
          @core.style(color, width=0.0, fill=color),
        ),
      )
    }
  }
  first_color
}

///|
/// Fill triangle faces using vertex means or one scalar per face.
pub fn Figure::tripcolor(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  label~ : String = "",
  mask~ : Array[Bool] = [],
) -> Unit raise @core.DataError {
  match (vmin, vmax) {
    (Some(vmin), Some(vmax)) =>
      self
      .axes()
      .tripcolor(xs, ys, triangles, values, vmin~, vmax~, cmap~, label~, mask~)
    (Some(vmin), None) =>
      self
      .axes()
      .tripcolor(xs, ys, triangles, values, vmin~, cmap~, label~, mask~)
    (None, Some(vmax)) =>
      self
      .axes()
      .tripcolor(xs, ys, triangles, values, vmax~, cmap~, label~, mask~)
    (None, None) =>
      self.axes().tripcolor(xs, ys, triangles, values, cmap~, label~, mask~)
  }
}

///|
/// Fill triangle faces using the mean of their vertex scalar values.
pub fn Axes::tripcolor(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  label~ : String = "",
  mask~ : Array[Bool] = [],
) -> Unit raise @core.DataError {
  let mesh = triangulation(xs, ys, triangles~, mask~)
  let legend_color = match
    tripcolor_mesh(
      self,
      mesh.x(),
      mesh.y(),
      mesh.triangles(),
      values,
      mesh.mask(),
      vmin,
      vmax,
      cmap,
    ) {
    Some(color) => color
    None => "#000000"
  }
  self.add_auto_legend(label, legend_color, LegendKind::Patch)
}

///|
/// Fill faces from a validated triangulation using vertex or face values.
pub fn Figure::tripcolor_triangulation(
  self : Figure,
  mesh : Triangulation,
  values : Array[Double],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  label~ : String = "",
) -> Unit raise @core.DataError {
  match (vmin, vmax) {
    (Some(vmin), Some(vmax)) =>
      self
      .axes()
      .tripcolor_triangulation(mesh, values, vmin~, vmax~, cmap~, label~)
    (Some(vmin), None) =>
      self.axes().tripcolor_triangulation(mesh, values, vmin~, cmap~, label~)
    (None, Some(vmax)) =>
      self.axes().tripcolor_triangulation(mesh, values, vmax~, cmap~, label~)
    (None, None) =>
      self.axes().tripcolor_triangulation(mesh, values, cmap~, label~)
  }
}

///|
/// Fill faces from a validated triangulation using vertex or face values.
pub fn Axes::tripcolor_triangulation(
  self : Axes,
  mesh : Triangulation,
  values : Array[Double],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  label~ : String = "",
) -> Unit raise @core.DataError {
  let legend_color = match
    tripcolor_mesh(
      self,
      mesh.x(),
      mesh.y(),
      mesh.triangles(),
      values,
      mesh.mask(),
      vmin,
      vmax,
      cmap,
    ) {
    Some(color) => color
    None => "#000000"
  }
  self.add_auto_legend(label, legend_color, LegendKind::Patch)
}

///|
/// Draw contour lines over a validated triangular mesh.
pub fn Figure::tricontour(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  mask~ : Array[Bool] = [],
  locator? : @ticker.Locator,
  colors~ : Array[String] = [],
  linewidths~ : Array[Double] = [],
) -> ContourSet raise @core.DataError {
  self
  .axes()
  .tricontour(
    xs,
    ys,
    triangles,
    values,
    levels~,
    nlevels~,
    color~,
    linewidth~,
    label~,
    mask~,
    locator~,
    colors~,
    linewidths~,
  )
}

///|
/// Draw contour lines over a validated triangular mesh.
pub fn Axes::tricontour(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  mask~ : Array[Bool] = [],
  locator~ : @ticker.Locator? = None,
  colors~ : Array[String] = [],
  linewidths~ : Array[Double] = [],
) -> ContourSet raise @core.DataError {
  let mesh = triangulation(xs, ys, triangles~, mask~)
  let mesh_x = mesh.x()
  let mesh_y = mesh.y()
  let mesh_triangles = mesh.triangles()
  let mesh_mask = mesh.mask()
  if values.length() != mesh_x.length() {
    raise @core.DataError::LengthMismatch(
      expected=mesh_x.length(),
      actual=values.length(),
    )
  }
  validate_tri_contour_values(values)
  let resolved_levels = contour_levels(
    [values],
    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] = []
  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 index, triangle in mesh_triangles {
      if !triangle_is_visible(mesh_mask, index) {
        continue
      }
      let first = {
        point: @core.point(mesh_x[triangle[0]], mesh_y[triangle[0]]),
        value: values[triangle[0]],
      }
      let second = {
        point: @core.point(mesh_x[triangle[1]], mesh_y[triangle[1]]),
        value: values[triangle[1]],
      }
      let third = {
        point: @core.point(mesh_x[triangle[2]], mesh_y[triangle[2]]),
        value: values[triangle[2]],
      }
      for segment in contour_triangle_segments(first, second, third, level) {
        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: true,
  }
}

///|
/// Draw contour lines from a validated triangulation.
pub fn Figure::tricontour_triangulation(
  self : Figure,
  mesh : Triangulation,
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  locator? : @ticker.Locator,
  colors~ : Array[String] = [],
  linewidths~ : Array[Double] = [],
) -> ContourSet raise @core.DataError {
  self
  .axes()
  .tricontour_triangulation(
    mesh,
    values,
    levels~,
    nlevels~,
    color~,
    linewidth~,
    label~,
    locator~,
    colors~,
    linewidths~,
  )
}

///|
/// Draw contour lines from a validated triangulation.
pub fn Axes::tricontour_triangulation(
  self : Axes,
  mesh : Triangulation,
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  color~ : String = "",
  linewidth~ : Double = 1.0,
  label~ : String = "",
  locator~ : @ticker.Locator? = None,
  colors~ : Array[String] = [],
  linewidths~ : Array[Double] = [],
) -> ContourSet raise @core.DataError {
  self.tricontour(
    mesh.x(),
    mesh.y(),
    mesh.triangles(),
    values,
    levels~,
    nlevels~,
    color~,
    linewidth~,
    label~,
    mask=mesh.mask(),
    locator~,
    colors~,
    linewidths~,
  )
}

///|
/// Draw filled contour bands over a validated triangular mesh.
pub fn Figure::tricontourf(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  colors~ : Array[String] = [],
  cmap~ : String = "viridis",
  label~ : String = "",
  extend_mode~ : String = "neither",
  mask~ : Array[Bool] = [],
  locator? : @ticker.Locator,
) -> ContourSet raise @core.DataError {
  self
  .axes()
  .tricontourf(
    xs,
    ys,
    triangles,
    values,
    levels~,
    nlevels~,
    colors~,
    cmap~,
    label~,
    extend_mode~,
    mask~,
    locator~,
  )
}

///|
/// Draw filled contour bands over a validated triangular mesh.
pub fn Axes::tricontourf(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  triangles : Array[Array[Int32]],
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  colors~ : Array[String] = [],
  cmap~ : String = "viridis",
  label~ : String = "",
  extend_mode~ : String = "neither",
  mask~ : Array[Bool] = [],
  locator~ : @ticker.Locator? = None,
) -> ContourSet raise @core.DataError {
  let mesh = triangulation(xs, ys, triangles~, mask~)
  let mesh_x = mesh.x()
  let mesh_y = mesh.y()
  let mesh_triangles = mesh.triangles()
  let mesh_mask = mesh.mask()
  if values.length() != mesh_x.length() {
    raise @core.DataError::LengthMismatch(
      expected=mesh_x.length(),
      actual=values.length(),
    )
  }
  validate_tri_contour_values(values)
  validate_contour_extend(extend_mode)
  let resolved_levels = contour_levels([values], levels, nlevels, true, locator)
  let bands = resolved_levels.length() - 1
  let (data_lower, data_upper) = contour_data_limits([values])
  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 = tricontour_render_filled_band(
      self,
      mesh_x,
      mesh_y,
      mesh_triangles,
      values,
      mesh_mask,
      data_lower,
      resolved_levels[0],
      @core.style(fill_color, width=0.0, fill=fill_color),
    )
    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..= 3 {
        polygons.push(polygon)
      }
    }
    let merged = contour_union_polygons(polygons)
    if merged.length() == 1 {
      self.push_artist(Artist::Polygon(merged[0], style))
      filled_segments.push(merged[0].copy())
      filled_segment_levels.push((lower + upper) / 2.0)
    } else if merged.length() > 1 {
      self.push_artist(Artist::CompoundPolygon(merged, style))
      for polygon in merged {
        filled_segments.push(polygon.copy())
        filled_segment_levels.push((lower + upper) / 2.0)
      }
    }
    color_index += 1
  }
  if contour_extend_max(extend_mode) {
    let fill_color = contour_palette_color(
      color_index, rendered_bands, colors, cmap,
    )
    let paths = tricontour_render_filled_band(
      self,
      mesh_x,
      mesh_y,
      mesh_triangles,
      values,
      mesh_mask,
      resolved_levels[resolved_levels.length() - 1],
      data_upper,
      @core.style(fill_color, width=0.0, fill=fill_color),
    )
    for path in paths {
      filled_segments.push(path.copy())
      filled_segment_levels.push(
        (resolved_levels[resolved_levels.length() - 1] + data_upper) / 2.0,
      )
    }
  }
  let legend_color = contour_palette_color(0, rendered_bands, colors, cmap)
  self.add_auto_legend(label, legend_color, LegendKind::Patch)
  self.add_container(ContainerKind::Contour, start, label)
  {
    levels: resolved_levels,
    filled: true,
    segments: filled_segments,
    segment_levels: filled_segment_levels,
    extend_mode,
    corner_mask: true,
  }
}

///|
/// Draw filled contour bands from a validated triangulation.
pub fn Figure::tricontourf_triangulation(
  self : Figure,
  mesh : Triangulation,
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  colors~ : Array[String] = [],
  cmap~ : String = "viridis",
  label~ : String = "",
  extend_mode~ : String = "neither",
  locator? : @ticker.Locator,
) -> ContourSet raise @core.DataError {
  self
  .axes()
  .tricontourf_triangulation(
    mesh,
    values,
    levels~,
    nlevels~,
    colors~,
    cmap~,
    label~,
    extend_mode~,
    locator~,
  )
}

///|
/// Draw filled contour bands from a validated triangulation.
pub fn Axes::tricontourf_triangulation(
  self : Axes,
  mesh : Triangulation,
  values : Array[Double],
  levels~ : Array[Double] = [],
  nlevels~ : Int32 = 7,
  colors~ : Array[String] = [],
  cmap~ : String = "viridis",
  label~ : String = "",
  extend_mode~ : String = "neither",
  locator~ : @ticker.Locator? = None,
) -> ContourSet raise @core.DataError {
  self.tricontourf(
    mesh.x(),
    mesh.y(),
    mesh.triangles(),
    values,
    levels~,
    nlevels~,
    colors~,
    cmap~,
    label~,
    extend_mode~,
    mask=mesh.mask(),
    locator~,
  )
}