///|
fn validate_grid_coordinates(
  coordinates : Array[Array[Double]],
  rows : Int32,
  columns : Int32,
  expected_rows : Int32,
  expected_columns : Int32,
  axis : String,
) -> Unit raise @core.DataError {
  if coordinates.length() != expected_rows {
    raise @core.DataError::InvalidImage(
      message="pcolor {axis} grid has an invalid row count",
    )
  }
  for row in coordinates {
    if row.length() != expected_columns {
      raise @core.DataError::InvalidImage(
        message="pcolor {axis} grid has an invalid column count",
      )
    }
    for value in row {
      if value.is_nan() || value.is_inf() {
        raise @core.DataError::InvalidImage(
          message="pcolor grid coordinates must be finite",
        )
      }
    }
  }
  ignore(rows)
  ignore(columns)
  ignore(axis)
}

///|
fn validate_grid_mask(
  mask : Array[Array[Bool]],
  expected_rows : Int32,
  expected_columns : Int32,
  axis : String,
) -> Unit raise @core.DataError {
  if mask.length() == 0 {
    return
  }
  if mask.length() != expected_rows {
    raise @core.DataError::InvalidImage(
      message="pcolor \{axis} mask has an invalid row count",
    )
  }
  for row in mask {
    if row.length() != expected_columns {
      raise @core.DataError::InvalidImage(
        message="pcolor \{axis} mask has an invalid column count",
      )
    }
  }
}

///|
fn mask_at(mask : Array[Array[Bool]], row : Int32, column : Int32) -> Bool {
  mask.length() > 0 && mask[row][column]
}

///|
fn grid_cell_coordinates_masked(
  x_mask : Array[Array[Bool]],
  y_mask : Array[Array[Bool]],
  row : Int32,
  column : Int32,
  rows : Int32,
  columns : Int32,
  shading : String,
) -> Bool {
  if shading == "flat" || shading == "gouraud" {
    mask_at(x_mask, row, column) ||
    mask_at(x_mask, row, column + 1) ||
    mask_at(x_mask, row + 1, column) ||
    mask_at(x_mask, row + 1, column + 1) ||
    mask_at(y_mask, row, column) ||
    mask_at(y_mask, row, column + 1) ||
    mask_at(y_mask, row + 1, column) ||
    mask_at(y_mask, row + 1, column + 1)
  } else {
    // Nearest derives each edge from the center and its immediate neighbor.
    mask_at(x_mask, row, column) ||
    (column > 0 && mask_at(x_mask, row, column - 1)) ||
    (column + 1 < columns && mask_at(x_mask, row, column + 1)) ||
    mask_at(y_mask, row, column) ||
    (row > 0 && mask_at(y_mask, row - 1, column)) ||
    (row + 1 < rows && mask_at(y_mask, row + 1, column))
  }
}

///|
fn nearest_grid_edge(
  coordinates : Array[Array[Double]],
  row : Int32,
  column : Int32,
  rows : Int32,
  columns : Int32,
  horizontal : Bool,
  lower : Bool,
) -> Double {
  let center = coordinates[row][column]
  if horizontal {
    if lower {
      if column == 0 {
        if columns == 1 {
          center - 0.5
        } else {
          center - (coordinates[row][column + 1] - center) / 2.0
        }
      } else {
        (coordinates[row][column - 1] + center) / 2.0
      }
    } else if column + 1 == columns {
      if columns == 1 {
        center + 0.5
      } else {
        center + (center - coordinates[row][column - 1]) / 2.0
      }
    } else {
      (center + coordinates[row][column + 1]) / 2.0
    }
  } else if lower {
    if row == 0 {
      if rows == 1 {
        center - 0.5
      } else {
        center - (coordinates[row + 1][column] - center) / 2.0
      }
    } else {
      (coordinates[row - 1][column] + center) / 2.0
    }
  } else if row + 1 == rows {
    if rows == 1 {
      center + 0.5
    } else {
      center + (center - coordinates[row - 1][column]) / 2.0
    }
  } else {
    (center + coordinates[row + 1][column]) / 2.0
  }
}

///|
fn grid_cell_corners(
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  row : Int32,
  column : Int32,
  rows : Int32,
  columns : Int32,
  shading : String,
) -> Array[@core.Point] {
  if shading == "flat" {
    [
      @core.point(x[row][column], y[row][column]),
      @core.point(x[row][column + 1], y[row][column + 1]),
      @core.point(x[row + 1][column + 1], y[row + 1][column + 1]),
      @core.point(x[row + 1][column], y[row + 1][column]),
    ]
  } else {
    let left_x = nearest_grid_edge(x, row, column, rows, columns, true, true)
    let right_x = nearest_grid_edge(x, row, column, rows, columns, true, false)
    let top_y = nearest_grid_edge(y, row, column, rows, columns, false, true)
    let bottom_y = nearest_grid_edge(
      y, row, column, rows, columns, false, false,
    )
    [
      @core.point(left_x, top_y),
      @core.point(right_x, top_y),
      @core.point(right_x, bottom_y),
      @core.point(left_x, bottom_y),
    ]
  }
}

///|
fn mesh_vertex_color(
  norm : @colors.ColorNorm,
  color_map : @colors.ColorMap,
  value : Double,
) -> String? raise @core.DataError {
  let normalized : Double? = if norm.is_valid(value) {
    Some(mesh_transform(norm, value))
  } else if color_map.bad() is Some(_) {
    Some(0.0 / 0.0)
  } else {
    None
  }
  match normalized {
    Some(value) => Some(color_map.sample_extended(value).to_css())
    None => None
  }
}

///|
fn add_gouraud_grid_with_norm(
  axes : Axes,
  data : Array[Array[Double]],
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  data_mask : Array[Array[Bool]],
  x_mask : Array[Array[Bool]],
  y_mask : Array[Array[Bool]],
  norm : @colors.ColorNorm,
  color_map : @colors.ColorMap,
  draw_bad : Bool,
) -> Unit raise @core.DataError {
  let (rows, columns) = validate_mesh_data(data)
  if rows < 2 || columns < 2 {
    raise @core.DataError::InvalidImage(
      message="pcolor gouraud shading requires at least a 2 by 2 grid",
    )
  }
  validate_grid_coordinates(x, rows, columns, rows, columns, "x")
  validate_grid_coordinates(y, rows, columns, rows, columns, "y")
  validate_grid_mask(data_mask, rows, columns, "data")
  validate_grid_mask(x_mask, rows, columns, "x")
  validate_grid_mask(y_mask, rows, columns, "y")
  norm.autoscale_none(mesh_values(data))
  for row in 0..<(rows - 1) {
    for column in 0..<(columns - 1) {
      if grid_cell_coordinates_masked(
          x_mask, y_mask, row, column, rows, columns, "gouraud",
        ) {
        continue
      }
      let v00 = if mask_at(data_mask, row, column) {
        0.0 / 0.0
      } else {
        data[row][column]
      }
      let v01 = if mask_at(data_mask, row, column + 1) {
        0.0 / 0.0
      } else {
        data[row][column + 1]
      }
      let v11 = if mask_at(data_mask, row + 1, column + 1) {
        0.0 / 0.0
      } else {
        data[row + 1][column + 1]
      }
      let v10 = if mask_at(data_mask, row + 1, column) {
        0.0 / 0.0
      } else {
        data[row + 1][column]
      }
      let source_invalid = v00.is_nan() ||
        v00.is_inf() ||
        v01.is_nan() ||
        v01.is_inf() ||
        v11.is_nan() ||
        v11.is_inf() ||
        v10.is_nan() ||
        v10.is_inf()
      if source_invalid && !draw_bad {
        continue
      }
      let c00 = mesh_vertex_color(norm, color_map, v00)
      let c01 = mesh_vertex_color(norm, color_map, v01)
      let c11 = mesh_vertex_color(norm, color_map, v11)
      let c10 = mesh_vertex_color(norm, color_map, v10)
      match (c00, c01, c11, c10) {
        (Some(c00), Some(c01), Some(c11), Some(c10)) => {
          let p00 = @core.point(x[row][column], y[row][column])
          let p01 = @core.point(x[row][column + 1], y[row][column + 1])
          let p11 = @core.point(x[row + 1][column + 1], y[row + 1][column + 1])
          let p10 = @core.point(x[row + 1][column], y[row + 1][column])
          axes.push_artist(
            Artist::GouraudTriangle([p00, p01, p11], [c00, c01, c11]),
          )
          axes.push_artist(
            Artist::GouraudTriangle([p00, p11, p10], [c00, c11, c10]),
          )
        }
        _ => ()
      }
    }
  }
}

///|
fn add_grid_mesh_with_norm(
  axes : Axes,
  data : Array[Array[Double]],
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  data_mask : Array[Array[Bool]],
  x_mask : Array[Array[Bool]],
  y_mask : Array[Array[Bool]],
  norm : @colors.ColorNorm,
  color_map : @colors.ColorMap,
  shading : String,
  draw_bad : Bool,
  antialiased : Bool,
) -> Unit raise @core.DataError {
  let (rows, columns) = validate_mesh_data(data)
  let actual_shading = if shading == "auto" {
    if x.length() == rows + 1 {
      "flat"
    } else {
      "nearest"
    }
  } else {
    shading
  }
  if actual_shading != "flat" &&
    actual_shading != "nearest" &&
    actual_shading != "gouraud" {
    raise @core.DataError::InvalidImage(
      message="pcolor shading must be flat, nearest, gouraud, or auto",
    )
  }
  let expected_rows = if actual_shading == "flat" { rows + 1 } else { rows }
  let expected_columns = if actual_shading == "flat" {
    columns + 1
  } else {
    columns
  }
  validate_grid_coordinates(
    x, rows, columns, expected_rows, expected_columns, "x",
  )
  validate_grid_coordinates(
    y, rows, columns, expected_rows, expected_columns, "y",
  )
  validate_grid_mask(data_mask, rows, columns, "data")
  validate_grid_mask(x_mask, expected_rows, expected_columns, "x")
  validate_grid_mask(y_mask, expected_rows, expected_columns, "y")
  if actual_shading == "gouraud" {
    add_gouraud_grid_with_norm(
      axes, data, x, y, data_mask, x_mask, y_mask, norm, color_map, draw_bad,
    )
    return
  }
  norm.autoscale_none(mesh_values(data))
  for row in 0.. {
          let color = color_map.sample_extended(normalized).to_css()
          axes.push_artist(
            Artist::Polygon(
              grid_cell_corners(
                x, y, row, column, rows, columns, actual_shading,
              ),
              @core.style(
                color,
                width=if antialiased { 0.5 } else { 0.0 },
                fill=color,
              ),
            ),
          )
        }
        None => ()
      }
    }
  }
}

///|
fn add_masked_grid_mesh(
  axes : Axes,
  data : @core.MaskedMatrix,
  x : @core.MaskedMatrix,
  y : @core.MaskedMatrix,
  shading : String,
  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_grid_mesh_with_norm(
    axes,
    data.values(),
    x.values(),
    y.values(),
    data.mask(),
    x.mask(),
    y.mask(),
    norm,
    color_map,
    shading,
    draw_bad,
    antialiased,
  )
}

///|
/// Draw a scalar mesh on a two-dimensional coordinate grid.
///
/// `flat` expects corner grids with shape `(rows + 1, columns + 1)`;
/// `nearest` expects center grids with the same shape as `data` and derives
/// cell edges from neighboring centers. `gouraud` expects vertex grids with
/// the same shape as `data` and linearly interpolates vertex colors across
/// each cell. `auto` selects flat when the corner shape is supplied and
/// nearest otherwise.
pub fn Figure::pcolor_grid(
  self : Figure,
  data : Array[Array[Double]],
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  match (vmin, vmax) {
    (Some(vmin), Some(vmax)) =>
      self
      .axes()
      .pcolor_grid(data, x, y, shading~, vmin~, vmax~, cmap~, antialiased~)
    (Some(vmin), None) =>
      self.axes().pcolor_grid(data, x, y, shading~, vmin~, cmap~, antialiased~)
    (None, Some(vmax)) =>
      self.axes().pcolor_grid(data, x, y, shading~, vmax~, cmap~, antialiased~)
    (None, None) =>
      self.axes().pcolor_grid(data, x, y, shading~, cmap~, antialiased~)
  }
}

///|
/// Draw a scalar mesh on a two-dimensional coordinate grid.
pub fn Axes::pcolor_grid(
  self : Axes,
  data : Array[Array[Double]],
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  let color_map = resolve_colormap(cmap)
  let norm = linear_color_norm(vmin, vmax)
  add_grid_mesh_with_norm(
    self,
    data,
    x,
    y,
    [],
    [],
    [],
    norm,
    color_map,
    shading,
    false,
    antialiased,
  )
}

///|
/// Draw a fast scalar mesh on a two-dimensional coordinate grid.
pub fn Figure::pcolormesh_grid(
  self : Figure,
  data : Array[Array[Double]],
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  match (vmin, vmax) {
    (Some(vmin), Some(vmax)) =>
      self
      .axes()
      .pcolormesh_grid(data, x, y, shading~, vmin~, vmax~, cmap~, antialiased~)
    (Some(vmin), None) =>
      self
      .axes()
      .pcolormesh_grid(data, x, y, shading~, vmin~, cmap~, antialiased~)
    (None, Some(vmax)) =>
      self
      .axes()
      .pcolormesh_grid(data, x, y, shading~, vmax~, cmap~, antialiased~)
    (None, None) =>
      self.axes().pcolormesh_grid(data, x, y, shading~, cmap~, antialiased~)
  }
}

///|
/// Draw a fast scalar mesh on a two-dimensional coordinate grid.
pub fn Axes::pcolormesh_grid(
  self : Axes,
  data : Array[Array[Double]],
  x : Array[Array[Double]],
  y : Array[Array[Double]],
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  let color_map = resolve_colormap(cmap)
  let norm = linear_color_norm(vmin, vmax)
  add_grid_mesh_with_norm(
    self,
    data,
    x,
    y,
    [],
    [],
    [],
    norm,
    color_map,
    shading,
    true,
    antialiased,
  )
}

///|
/// Draw a masked scalar mesh on a two-dimensional coordinate grid.
///
/// The data, X, and Y matrices must be rectangular. A masked data cell is
/// omitted by `pcolor_grid_masked`; `pcolormesh_grid_masked` maps it through
/// the colormap's bad color when one is configured. A masked coordinate
/// vertex omits every cell that depends on that vertex.
pub fn Figure::pcolor_grid_masked(
  self : Figure,
  data : @core.MaskedMatrix,
  x : @core.MaskedMatrix,
  y : @core.MaskedMatrix,
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  add_masked_grid_mesh(
    self.axes(),
    data,
    x,
    y,
    shading,
    vmin,
    vmax,
    cmap,
    false,
    antialiased,
  )
}

///|
/// Draw a fast masked scalar mesh on a two-dimensional coordinate grid.
pub fn Figure::pcolormesh_grid_masked(
  self : Figure,
  data : @core.MaskedMatrix,
  x : @core.MaskedMatrix,
  y : @core.MaskedMatrix,
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  add_masked_grid_mesh(
    self.axes(),
    data,
    x,
    y,
    shading,
    vmin,
    vmax,
    cmap,
    true,
    antialiased,
  )
}

///|
/// Draw a masked scalar mesh through the axes view.
pub fn Axes::pcolor_grid_masked(
  self : Axes,
  data : @core.MaskedMatrix,
  x : @core.MaskedMatrix,
  y : @core.MaskedMatrix,
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = true,
) -> Unit raise @core.DataError {
  add_masked_grid_mesh(
    self, data, x, y, shading, vmin, vmax, cmap, false, antialiased,
  )
}

///|
/// Draw a fast masked scalar mesh through the axes view.
pub fn Axes::pcolormesh_grid_masked(
  self : Axes,
  data : @core.MaskedMatrix,
  x : @core.MaskedMatrix,
  y : @core.MaskedMatrix,
  shading~ : String = "auto",
  vmin? : Double,
  vmax? : Double,
  cmap~ : String = "viridis",
  antialiased~ : Bool = false,
) -> Unit raise @core.DataError {
  add_masked_grid_mesh(
    self, data, x, y, shading, vmin, vmax, cmap, true, antialiased,
  )
}