///|
fn validate_barbs_style(
  linewidth : Double,
  barb_length : Double,
) -> Unit raise @core.DataError {
  if linewidth.is_nan() || linewidth.is_inf() || linewidth < 0.0 {
    raise @core.DataError::InvalidQuiver(
      message="barbs linewidth must be finite and non-negative",
    )
  }
  if barb_length.is_nan() || barb_length.is_inf() || barb_length < 0.0 {
    raise @core.DataError::InvalidQuiver(
      message="barb length must be finite and non-negative",
    )
  }
}

///|
fn add_barb_artists(
  axes : Axes,
  x : Double,
  y : Double,
  u : Double,
  v : Double,
  color : String,
  linewidth : Double,
  barb_length : Double,
) -> Unit {
  let magnitude = (u * u + v * v).sqrt()
  if magnitude == 0.0 {
    return
  }
  let unit_x = u / magnitude
  let unit_y = v / magnitude
  let perpendicular_x = -unit_y
  let perpendicular_y = unit_x
  let start = @core.point(x, y)
  let end = @core.point(x + u, y + v)
  let style = @core.style(color, width=linewidth)
  axes.push_artist(Artist::Line([start, end], style))
  let wing_length = Double::min(barb_length, magnitude * 0.35)
  if wing_length > 0.0 {
    for index in 1..<=3 {
      let fraction = Double::from_int(index) / 4.0
      let barb_x = end.x() - unit_x * magnitude * fraction
      let barb_y = end.y() - unit_y * magnitude * fraction
      let side = if index % 2 == 0 { -1.0 } else { 1.0 }
      let wing = @core.point(
        barb_x + perpendicular_x * wing_length * side,
        barb_y + perpendicular_y * wing_length * side,
      )
      axes.push_artist(Artist::Line([@core.point(barb_x, barb_y), wing], style))
    }
  }
}

///|
/// Draw basic wind barbs for paired data-coordinate vectors.
pub fn Figure::barbs(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  us : Array[Double],
  vs : Array[Double],
  color~ : String = "",
  linewidth~ : Double = 1.0,
  barb_length~ : Double = 0.25,
  label~ : String = "",
) -> Unit raise @core.DataError {
  self.axes().barbs(xs, ys, us, vs, color~, linewidth~, barb_length~, label~)
}

///|
/// Draw basic wind barbs for paired data-coordinate vectors.
pub fn Axes::barbs(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  us : Array[Double],
  vs : Array[Double],
  color~ : String = "",
  linewidth~ : Double = 1.0,
  barb_length~ : Double = 0.25,
  label~ : String = "",
) -> Unit raise @core.DataError {
  if xs.length() != ys.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=ys.length(),
    )
  }
  if xs.length() != us.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=us.length(),
    )
  }
  if xs.length() != vs.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=vs.length(),
    )
  }
  validate_barbs_style(linewidth, barb_length)
  let resolved_color = self.resolve_color(color)
  for index in 0.. Unit raise @core.DataError {
  if half_increment.is_nan() ||
    half_increment.is_inf() ||
    half_increment <= 0.0 ||
    full_increment.is_nan() ||
    full_increment.is_inf() ||
    full_increment < half_increment ||
    flag_increment.is_nan() ||
    flag_increment.is_inf() ||
    flag_increment < full_increment {
    raise @core.DataError::InvalidQuiver(
      message="barb increments must be finite and ordered positive values",
    )
  }
  if pivot != "tail" && pivot != "middle" && pivot != "tip" {
    raise @core.DataError::InvalidQuiver(
      message="barb pivot must be tail, middle, or tip",
    )
  }
}

///|
fn barb_anchor(
  x : Double,
  y : Double,
  u : Double,
  v : Double,
  pivot : String,
) -> (@core.Point, @core.Point) {
  if pivot == "tip" {
    (@core.point(x - u, y - v), @core.point(x, y))
  } else if pivot == "middle" {
    (
      @core.point(x - u / 2.0, y - v / 2.0),
      @core.point(x + u / 2.0, y + v / 2.0),
    )
  } else {
    (@core.point(x, y), @core.point(x + u, y + v))
  }
}

///|
fn add_barb_flag(
  axes : Axes,
  end : @core.Point,
  unit_x : Double,
  unit_y : Double,
  perpendicular_x : Double,
  perpendicular_y : Double,
  cursor : Double,
  span : Double,
  side : Double,
  style : @core.Style,
) -> Unit {
  let root = @core.point(
    end.x() - unit_x * (cursor + span * 0.1),
    end.y() - unit_y * (cursor + span * 0.1),
  )
  let outer = @core.point(
    end.x() - unit_x * (cursor + span),
    end.y() - unit_y * (cursor + span),
  )
  let tip = @core.point(
    root.x() + perpendicular_x * span * side,
    root.y() + perpendicular_y * span * side,
  )
  axes.push_artist(Artist::Polygon([root, outer, tip], style))
}

///|
fn add_barb_advanced_artists(
  axes : Axes,
  x : Double,
  y : Double,
  u : Double,
  v : Double,
  color : String,
  linewidth : Double,
  barb_length : Double,
  half_increment : Double,
  full_increment : Double,
  flag_increment : Double,
  rounding : Bool,
  flip : Bool,
  fill_empty : Bool,
  pivot : String,
) -> Unit {
  let magnitude = (u * u + v * v).sqrt()
  let (start, end) = barb_anchor(x, y, u, v, pivot)
  let style = @core.style(color, width=linewidth, fill=color)
  if magnitude == 0.0 {
    if fill_empty {
      axes.push_artist(
        Artist::Scatter(
          [end],
          @core.style(color, width=linewidth, radius=barb_length * 0.3),
        ),
      )
    }
    return
  }
  let unit_x = u / magnitude
  let unit_y = v / magnitude
  let perpendicular_x = -unit_y
  let perpendicular_y = unit_x
  let side = if flip { -1.0 } else { 1.0 }
  let effective_magnitude = if rounding {
    (magnitude / half_increment + 0.5).floor() * half_increment
  } else {
    magnitude
  }
  if effective_magnitude < half_increment {
    if fill_empty {
      axes.push_artist(
        Artist::Scatter(
          [end],
          @core.style(color, width=linewidth, radius=barb_length * 0.3),
        ),
      )
    }
    return
  }
  axes.push_artist(Artist::Line([start, end], style))
  let flag_count = (effective_magnitude / flag_increment).floor().to_int()
  let after_flags = effective_magnitude -
    Double::from_int(flag_count) * flag_increment
  let full_count = (after_flags / full_increment).floor().to_int()
  let remainder = after_flags - Double::from_int(full_count) * full_increment
  let half_count = if remainder + 1.0e-12 >= half_increment { 1 } else { 0 }
  let span = Double::min(barb_length, magnitude * 0.35)
  let mut cursor = 0.0
  for _ in 0.. 0 {
    let root = @core.point(end.x() - unit_x * cursor, end.y() - unit_y * cursor)
    let wing = @core.point(
      root.x() + perpendicular_x * span * 0.5 * side,
      root.y() + perpendicular_y * span * 0.5 * side,
    )
    axes.push_artist(Artist::Line([root, wing], style))
  }
}

///|
/// Draw wind barbs with magnitude increments, pivot, flip, and calm-barb
/// options. The default increments match Matplotlib's half/full/flag values.
pub fn Figure::barbs_with_options(
  self : Figure,
  xs : Array[Double],
  ys : Array[Double],
  us : Array[Double],
  vs : Array[Double],
  color~ : String = "",
  linewidth~ : Double = 1.0,
  barb_length~ : Double = 0.25,
  label~ : String = "",
  half_increment~ : Double = 5.0,
  full_increment~ : Double = 10.0,
  flag_increment~ : Double = 50.0,
  rounding~ : Bool = true,
  flip~ : Bool = false,
  fill_empty~ : Bool = true,
  pivot~ : String = "tail",
) -> Unit raise @core.DataError {
  self
  .axes()
  .barbs_with_options(
    xs,
    ys,
    us,
    vs,
    color~,
    linewidth~,
    barb_length~,
    label~,
    half_increment~,
    full_increment~,
    flag_increment~,
    rounding~,
    flip~,
    fill_empty~,
    pivot~,
  )
}

///|
/// Draw wind barbs with magnitude increments, pivot, flip, and calm-barb
/// options. The default increments match Matplotlib's half/full/flag values.
pub fn Axes::barbs_with_options(
  self : Axes,
  xs : Array[Double],
  ys : Array[Double],
  us : Array[Double],
  vs : Array[Double],
  color~ : String = "",
  linewidth~ : Double = 1.0,
  barb_length~ : Double = 0.25,
  label~ : String = "",
  half_increment~ : Double = 5.0,
  full_increment~ : Double = 10.0,
  flag_increment~ : Double = 50.0,
  rounding~ : Bool = true,
  flip~ : Bool = false,
  fill_empty~ : Bool = true,
  pivot~ : String = "tail",
) -> Unit raise @core.DataError {
  if xs.length() != ys.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=ys.length(),
    )
  }
  if xs.length() != us.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=us.length(),
    )
  }
  if xs.length() != vs.length() {
    raise @core.DataError::LengthMismatch(
      expected=xs.length(),
      actual=vs.length(),
    )
  }
  validate_barbs_style(linewidth, barb_length)
  validate_barb_increments(
    half_increment, full_increment, flag_increment, pivot,
  )
  let resolved_color = self.resolve_color(color)
  for index in 0..