///|
/// Original coordinate kind is independent of the Axis's current converter.
pub(all) enum LineData {
  Numeric(Array[Double])
  Category(Array[String])
  Date(Array[@date.RRuleDateTime])
}

///|
fn LineData::copy(self : LineData) -> LineData {
  match self {
    Numeric(values) => Numeric(values.copy())
    Category(values) => Category(values.copy())
    Date(values) => Date(values.copy())
  }
}

///|
fn LineData::length(self : LineData) -> Int32 {
  match self {
    Numeric(values) => values.length()
    Category(values) => values.length()
    Date(values) => values.length()
  }
}

///|
fn LineData::convert(
  self : LineData,
  axis : @axis.Axis,
) -> Array[Double] raise @core.DataError {
  match self {
    // Python-list Float64 coercion finishes before converting the other axis.
    Numeric(values) => values.copy()
    Category(values) => axis.convert_category_units(values)
    Date(values) => axis.convert_date_units(values)
  }
}

///|
fn LineData::convert_detached(
  self : LineData,
) -> Array[Double] raise @core.DataError {
  match self {
    Numeric(values) => values.copy()
    Category(values) => values.map(value => @ticker.parse_float_text(value))
    Date(values) =>
      if values.is_empty() {
        []
      } else {
        raise @core.DataError::InvalidTicks(
          message="TypeError: float() argument must be a string or a real number, not 'datetime.datetime'",
        )
      }
  }
}

///|
fn Axes::attach_line_handle(
  self : Axes,
  points : Array[@core.Point],
  x_original : LineData,
  y_original : LineData,
  color~ : String = "",
  label~ : String = "",
) -> Line2D raise @core.DataError {
  let attached = copy_points(points)
  let x = x_original.copy()
  let y = y_original.copy()
  let cache = line2d_cache_from_points(attached)
  // Preserve copy/cache failure order before consuming an automatic color.
  let resolved_color = self.resolve_color(color)
  let style = @core.style(resolved_color)
  let line : Line2D = {
    points: attached,
    style,
    unscaled_dash_pattern: { offset: 0.0, dashes: None, },
    scaled_dashes_as_list: true,
    source_color: false,
    x,
    y,
    cache,
    drawstyle: "default",
    path_values: cache.flat,
    invalid_x: false,
    invalid_y: false,
    owner: Some(self),
    removal_parent: Some(self),
    visible: true,
    stale: true,
  }
  self.state.val.line_originals.push(line)
  self.push_artist(Artist::Line(attached, style))
  self.add_auto_legend(label, resolved_color, LegendKind::Line)
  line
}

///|
// Preserve the existing pure Unit convenience signatures. Their supplied point
// buffers must fit the interleaved storage, as with the original allocation.
fn Axes::attach_line_with_originals(
  self : Axes,
  points : Array[@core.Point],
  x_original : LineData,
  y_original : LineData,
  color~ : String = "",
  label~ : String = "",
) -> Unit {
  ignore(
    try! self.attach_line_handle(points, x_original, y_original, color~, label~),
  )
}

///|
/// A forced unit recache visits real attached Lines in artist order.
fn Axes::recache_line_units(self : Axes) -> Unit raise @core.DataError {
  let state = self.state.val
  for artist in state.artists {
    guard artist is Artist::Line(points, _) else { continue }
    for line in state.line_originals {
      if physical_equal(points, line.points) {
        line.recache_checked(always=true)
        break
      }
    }
  }
}

///|
/// Contour rewriting can erase an attachment without destroying retained handles.
fn Axes::prune_line_originals(self : Axes) -> Unit {
  let artists = self.state.val.artists
  self.state.val.line_originals.retain(line => {
    let attached = artists.any(artist => {
      match artist {
        Artist::Line(points, _) => physical_equal(points, line.points)
        _ => false
      }
    })
    if !attached {
      line.detach()
    }
    attached
  })
}