///|
pub fn SecondaryAxis::set_tick_label_size(
  self : SecondaryAxis,
  size : Double,
) -> Unit {
  self.numeric_axis.set_tick_label_size(size)
}

///|
pub fn SecondaryAxis::tick_label_size(self : SecondaryAxis) -> Double {
  self.numeric_axis.tick_label_size()
}

///|
pub fn SecondaryAxis::get_tick_space(self : SecondaryAxis) -> Double {
  self.numeric_axis.get_tick_space()
}

///|
/// Synchronize limits using the currently installed context, without scale reset.
pub fn SecondaryAxis::apply_aspect(
  self : SecondaryAxis,
) -> Unit raise @core.DataError {
  let (lower, upper) = self.mapped_parent_view_interval()
  self.store_formatter_view(lower, upper)
  self.active_bounds = self.original_bounds
}

///|
// Positioned aspect is distinct from draw's later parent-scale synchronization.
fn SecondaryAxis::synchronize_formatter_frame(
  self : SecondaryAxis,
) -> Unit raise @core.DataError {
  let (lower, upper) = self.mapped_parent_view_interval()
  self.store_formatter_view(lower, upper)
  let (origin, length) = match self.parent_axis {
    Some(parent) =>
      parent
      .formatter_display_geometry()
      .unwrap_or_else(() => {
        let original = self.original_bounds.transformed(self.figure_to())
        if self.horizontal {
          (original.x0(), original.width())
        } else {
          (original.y0(), original.height())
        }
      })
    None => {
      let original = self.original_bounds.transformed(self.figure_to())
      if self.horizontal {
        (original.x0(), original.width())
      } else {
        (original.y0(), original.height())
      }
    }
  }
  // Retain the D40 controlled one-dimensional geometry profile without a second
  // authority. Real positioned rendering supplies the full parent frame.
  let bounds = self.active_bounds
  if self.horizontal {
    let reciprocal = 1.0 / self.figure_bounds.width()
    self.active_bounds = @transforms.bbox(
      origin * reciprocal,
      bounds.y0(),
      (origin + length) * reciprocal,
      bounds.y1(),
    )
  } else {
    let reciprocal = 1.0 / self.figure_bounds.height()
    self.active_bounds = @transforms.bbox(
      bounds.x0(),
      origin * reciprocal,
      bounds.x1(),
      (origin + length) * reciprocal,
    )
  }
}

///|
fn SecondaryAxis::store_formatter_view(
  self : SecondaryAxis,
  lower : Double,
  upper : Double,
) -> Unit raise @core.DataError {
  // Match the existing checked secondary error before touching old scale state.
  if lower.is_nan() || lower.is_inf() || upper.is_nan() || upper.is_inf() {
    raise @core.DataError::InvalidTicks(
      message="ValueError: Axis limits cannot be NaN or Inf",
    )
  }
  ignore(
    self.numeric_axis.set_bound_limits_checked(
      lower,
      upper,
      automatic=Some(false),
    ),
  )
}

///|
fn SecondaryAxis::initialize_formatter_view(
  self : SecondaryAxis,
) -> Unit raise @core.DataError {
  self.synchronize_parent_scale()
}

///|
// Locators and formatters query the child's actual stored view, not its parent.
fn SecondaryAxis::numeric_view_interval(
  self : SecondaryAxis,
) -> (Double, Double) raise @core.DataError {
  self.numeric_axis.view_interval_checked()
}

///|
// Parent limits are consumed only at the source aspect/draw synchronization stage.
fn SecondaryAxis::mapped_parent_view_interval(
  self : SecondaryAxis,
) -> (Double, Double) raise @core.DataError {
  let (lower, upper) = match self.parent_axis {
    Some(parent) => parent.view_interval_checked()
    None => (0.0, 1.0)
  }
  let first = (self.forward)(lower)
  let last = (self.forward)(upper)
  if (first < last) != (lower < upper) {
    (last, first)
  } else {
    (first, last)
  }
}