///|
fn validate_mesh_data(
  data : Array[Array[Double]],
) -> (Int32, Int32) raise @core.DataError {
  if data.length() == 0 || data[0].length() == 0 {
    raise @core.DataError::InvalidImage(
      message="pcolor data must have at least one cell",
    )
  }
  let columns = data[0].length()
  for row in data {
    if row.length() != columns {
      raise @core.DataError::InvalidImage(
        message="pcolor data rows must have equal length",
      )
    }
  }
  (data.length(), columns)
}

///|
fn mesh_axis(
  values : Array[Double],
  count : Int32,
  axis : String,
) -> Array[Double] raise @core.DataError {
  if values.length() == 0 {
    let result = Array(capacity=count + 1)
    for index in 0..<=count {
      result.push(Double::from_int(index))
    }
    result
  } else {
    if values.length() != count + 1 {
      raise @core.DataError::InvalidImage(
        message="pcolor \{axis} boundaries must have one more value than cells",
      )
    }
    for value in values {
      if value.is_nan() || value.is_inf() {
        raise @core.DataError::InvalidImage(
          message="pcolor coordinates must be finite",
        )
      }
    }
    if values.length() > 1 {
      let increasing = values[1] > values[0]
      for index in 1..= previous {
          raise @core.DataError::InvalidImage(
            message="pcolor coordinates must be strictly monotonic",
          )
        }
      }
    }
    values.copy()
  }
}

///|
fn mesh_transform(
  norm : @colors.ColorNorm,
  value : Double,
) -> Double raise @core.DataError {
  norm.transform(value) catch {
    @colors.NormalizeError::InvalidBounds(message~)
    | @colors.NormalizeError::InvalidBase(message~)
    | @colors.NormalizeError::InvalidValue(message~) =>
      raise @core.DataError::InvalidImage(message~)
    @colors.NormalizeError::NotScaled =>
      raise @core.DataError::InvalidImage(
        message="pcolor normalization is not scaled",
      )
    @colors.NormalizeError::NoFiniteData =>
      raise @core.DataError::InvalidImage(message="pcolor has no finite data")
    @colors.NormalizeError::NotInvertible =>
      raise @core.DataError::InvalidImage(
        message="pcolor normalization is not invertible",
      )
  }
}

///|
fn mesh_values(data : Array[Array[Double]]) -> Array[Double] {
  let values = Array(capacity=data.length())
  for row in data {
    for value in row {
      values.push(value)
    }
  }
  values
}

///|
fn add_mesh(
  axes : Axes,
  data : Array[Array[Double]],
  x : Array[Double],
  y : Array[Double],
  vmin : Double?,
  vmax : Double?,
  cmap : String,
  draw_bad : Bool,
  antialiased : Bool,
) -> Unit raise @core.DataError {
  let color_map = resolve_colormap(cmap)
  let norm = linear_color_norm(vmin, vmax)
  add_mesh_with_norm(axes, data, x, y, norm, color_map, draw_bad, antialiased)
}

///|
fn add_mesh_with_norm(
  axes : Axes,
  data : Array[Array[Double]],
  x : Array[Double],
  y : Array[Double],
  norm : @colors.ColorNorm,
  color_map : @colors.ColorMap,
  draw_bad : Bool,
  antialiased : Bool,
) -> Unit raise @core.DataError {
  let (rows, columns) = validate_mesh_data(data)
  if (x.length() == 0) != (y.length() == 0) {
    raise @core.DataError::InvalidImage(
      message="pcolor x and y boundaries must be supplied together",
    )
  }
  let xs = mesh_axis(x, columns, "x")
  let ys = mesh_axis(y, rows, "y")
  norm.autoscale_none(mesh_values(data))
  for row in 0.. {
          let color = color_map.sample_extended(normalized).to_css()
          axes.push_artist(
            Artist::Polygon(
              [
                @core.point(xs[column], ys[row]),
                @core.point(xs[column + 1], ys[row]),
                @core.point(xs[column + 1], ys[row + 1]),
                @core.point(xs[column], ys[row + 1]),
              ],
              @core.style(
                color,
                width=if antialiased { 0.5 } else { 0.0 },
                fill=color,
              ),
            ),
          )
        }
        None => ()
      }
    }
  }
}

///|
/// Draw scalar values as filled quadrilaterals on a rectangular grid.
///
/// When supplied, `x` and `y` contain cell boundaries and therefore have one
/// more value than the number of columns and rows. Empty coordinates use an
/// integer grid. Source masked or non-finite cells are omitted from the
/// polygon set; values rejected by a normalization use the colormap bad color
/// when one is configured.
pub fn Figure::pcolor(
  self : Figure,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  match (vmin, vmax) {
    (Some(vmin), Some(vmax)) =>
      self.axes().pcolor(data, x~, y~, vmin~, vmax~, cmap~, antialiased~)
    (Some(vmin), None) =>
      self.axes().pcolor(data, x~, y~, vmin~, cmap~, antialiased~)
    (None, Some(vmax)) =>
      self.axes().pcolor(data, x~, y~, vmax~, cmap~, antialiased~)
    (None, None) => self.axes().pcolor(data, x~, y~, cmap~, antialiased~)
  }
}

///|
/// Draw scalar values as filled quadrilaterals on a rectangular grid.
pub fn Axes::pcolor(
  self : Axes,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  add_mesh(self, data, x, y, vmin, vmax, cmap, false, antialiased)
}

///|
/// Draw a scalar mesh using an explicit normalization and colormap object.
pub fn Figure::pcolor_with_norm_and_colormap(
  self : Figure,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap : @colors.ColorMap,
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  self
  .axes()
  .pcolor_with_norm_and_colormap(data, norm, cmap, x~, y~, antialiased~)
}

///|
/// Draw a scalar mesh through the axes view with explicit normalization.
pub fn Axes::pcolor_with_norm_and_colormap(
  self : Axes,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap : @colors.ColorMap,
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  add_mesh_with_norm(self, data, x, y, norm, cmap, false, antialiased)
}

///|
/// Draw a scalar mesh with an explicit normalization and named colormap.
pub fn Figure::pcolor_with_norm(
  self : Figure,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap~ : String = "viridis",
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  self.axes().pcolor_with_norm(data, norm, cmap~, x~, y~, antialiased~)
}

///|
/// Draw a scalar mesh through the axes view with explicit normalization.
pub fn Axes::pcolor_with_norm(
  self : Axes,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap~ : String = "viridis",
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  add_mesh_with_norm(
    self,
    data,
    x,
    y,
    norm,
    resolve_colormap(cmap),
    false,
    antialiased,
  )
}

///|
/// Draw a rectangular scalar mesh with 1D cell boundaries. Masked or
/// non-finite cells use the colormap's bad color, which is transparent by default.
pub fn Figure::pcolormesh(
  self : Figure,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  match (vmin, vmax) {
    (Some(vmin), Some(vmax)) =>
      self.axes().pcolormesh(data, x~, y~, vmin~, vmax~, cmap~, antialiased~)
    (Some(vmin), None) =>
      self.axes().pcolormesh(data, x~, y~, vmin~, cmap~, antialiased~)
    (None, Some(vmax)) =>
      self.axes().pcolormesh(data, x~, y~, vmax~, cmap~, antialiased~)
    (None, None) => self.axes().pcolormesh(data, x~, y~, cmap~, antialiased~)
  }
}

///|
/// Draw a fast rectangular scalar mesh.
pub fn Axes::pcolormesh(
  self : Axes,
  data : Array[Array[Double]],
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  add_mesh(self, data, x, y, vmin, vmax, cmap, true, antialiased)
}

///|
/// Draw a fast scalar mesh using an explicit normalization and colormap.
pub fn Figure::pcolormesh_with_norm_and_colormap(
  self : Figure,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap : @colors.ColorMap,
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  self
  .axes()
  .pcolormesh_with_norm_and_colormap(data, norm, cmap, x~, y~, antialiased~)
}

///|
/// Draw a fast scalar mesh through the axes view with explicit normalization.
pub fn Axes::pcolormesh_with_norm_and_colormap(
  self : Axes,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap : @colors.ColorMap,
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  add_mesh_with_norm(self, data, x, y, norm, cmap, true, antialiased)
}

///|
/// Draw a fast scalar mesh with an explicit normalization and named colormap.
pub fn Figure::pcolormesh_with_norm(
  self : Figure,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap~ : String = "viridis",
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  self.axes().pcolormesh_with_norm(data, norm, cmap~, x~, y~, antialiased~)
}

///|
/// Draw a fast scalar mesh through the axes view with explicit normalization.
pub fn Axes::pcolormesh_with_norm(
  self : Axes,
  data : Array[Array[Double]],
  norm : @colors.ColorNorm,
  cmap~ : String = "viridis",
  x~ : Array[Double] = [],
  y~ : Array[Double] = [],
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  add_mesh_with_norm(
    self,
    data,
    x,
    y,
    norm,
    resolve_colormap(cmap),
    true,
    antialiased,
  )
}