///|
priv struct ContourSource {
  view : @render.ContourHitView
  // Rebuilt once from the frame's records, so inline fragments advance the
  // existing metadata cursor without scanning old paths or unrelated records.
  cursor : @ref.Ref[Int32]
}

///|
priv struct ContourIntent {
  artist : Artist
  source : ContourSource
  view : @render.ContourHitView
}

///|
fn Axes::push_contour_path(
  self : Axes,
  points : Array[@core.Point],
  style : @core.Style,
  slot : Int32,
  level : Double,
  series : String,
) -> Unit {
  let view = @render.contour_hit_view(points, slot, level, series)
  let source : ContourSource = { view, cursor: @ref.Ref(0), }
  let artist = Artist::Line(points, style)
  self.push_artist(artist)
  let records = self.state.val.artist_hits
  let record = records[records.length() - 1]
  record.contour = Some(source)
  record.series = series
  self.state.val.contour_intents[self.state.val.artists.length() - 1] = {
    artist,
    source,
    view,
  }
}

///|
fn ContourIntent::split(
  self : ContourIntent,
  original : Artist,
  fragment : Artist,
) -> ContourIntent {
  let valid = same_hit_artist(self.artist, original) &&
    (match original {
      Line(points, _) => self.view.matches_points(points)
      _ => false
    })
  let view = if valid { self.view } else { self.view.invalidated() }
  let view = match fragment {
    Line(points, _) => view.fragment(points)
    _ => view.invalidated()
  }
  { artist: fragment, source: self.source, view, }
}

///|
fn AxesData::remove_contour_slot(self : AxesData, removed : Int32) -> Unit {
  if self.contour_intents.is_empty() {
    return
  }
  let next = Map([])
  for slot, intent in self.contour_intents {
    if slot != removed {
      next[if slot > removed { slot - 1 } else { slot }] = intent
    }
  }
  self.contour_intents.clear()
  for slot, intent in next {
    self.contour_intents[slot] = intent
  }
}