///|
/// Probability ticks share live MaxN parameters and retain their own axis context.
pub struct LogitLocator {
  priv maxn : MaxNLocator
  priv mut minor : Bool
  priv mut minpos_provider : (() -> Double raise @core.DataError)?
  priv mut projection_provider : (() -> String raise @core.DataError)?
  priv diagnostics : Array[String]
}

///|
/// Omitted nbins selects auto; explicit None selects the inherited default ten.
pub fn LogitLocator::new(
  minor~ : Bool = false,
  nbins? : MaxNBins?,
) -> LogitLocator raise @core.DataError {
  let nbins = match nbins {
    None => MaxNBins::Auto
    Some(None) => MaxNBins::Count(10)
    Some(Some(value)) => value
  }
  {
    maxn: MaxNLocator::new(
      nbins~,
      steps=TickValues::from_int64_array([1L, 2L, 5L, 10L]),
    ),
    minor,
    minpos_provider: None,
    projection_provider: None,
    diagnostics: [],
  }
}

///|
/// Return live inherited state; its numerical methods retain base MaxN behavior.
pub fn LogitLocator::as_maxn_locator(self : LogitLocator) -> MaxNLocator {
  self.maxn
}

///|
pub fn LogitLocator::minor(self : LogitLocator) -> Bool {
  self.minor
}

///|
/// The Python minor property setter treats None as keep.
pub fn LogitLocator::set_minor(self : LogitLocator, value : Bool?) -> Unit {
  if value is Some(value) {
    self.minor = value
  }
}

///|
/// Set minor first; inherited validation failures retain that earlier write.
pub fn LogitLocator::set_params(
  self : LogitLocator,
  minor? : Bool?,
  nbins? : MaxNBins,
  steps? : TickValues?,
  integer? : Bool,
  symmetric? : Bool,
  prune? : String?,
  min_n_ticks? : Int32,
) -> Unit raise @core.DataError {
  if minor is Some(Some(value)) {
    self.minor = value
  }
  self.maxn.set_params(
    nbins?,
    steps?,
    integer?,
    symmetric?,
    prune?,
    min_n_ticks?,
  )
}

///|
pub fn LogitLocator::copy(self : LogitLocator) -> LogitLocator {
  {
    maxn: self.maxn.copy(),
    minor: self.minor,
    minpos_provider: self.minpos_provider,
    projection_provider: self.projection_provider,
    diagnostics: self.diagnostics.copy(),
  }
}

///|
pub fn LogitLocator::warnings(self : LogitLocator) -> Array[String] {
  self.diagnostics.copy()
}

///|
pub fn LogitLocator::bind_tick_space(
  self : LogitLocator,
  provider : () -> Double,
) -> Unit {
  self.maxn.bind_tick_space(provider)
}

///|
pub fn LogitLocator::tick_space_provider(
  self : LogitLocator,
) -> (() -> Double)? {
  self.maxn.tick_space_provider()
}

///|
pub fn LogitLocator::bind_view_interval(
  self : LogitLocator,
  provider : () -> (Double, Double) raise @core.DataError,
) -> Unit {
  self.maxn.bind_view_interval(provider)
}

///|
pub fn LogitLocator::view_interval_provider(
  self : LogitLocator,
) -> (() -> (Double, Double) raise @core.DataError)? {
  self.maxn.view_interval_provider()
}

///|
pub fn LogitLocator::bind_minpos(
  self : LogitLocator,
  provider : () -> Double raise @core.DataError,
) -> Unit {
  self.minpos_provider = Some(provider)
}

///|
pub fn LogitLocator::minpos_provider(
  self : LogitLocator,
) -> (() -> Double raise @core.DataError)? {
  self.minpos_provider
}

///|
pub fn LogitLocator::bind_projection(
  self : LogitLocator,
  provider : () -> String raise @core.DataError,
) -> Unit {
  self.projection_provider = Some(provider)
}

///|
pub fn LogitLocator::projection_provider(
  self : LogitLocator,
) -> (() -> String raise @core.DataError)? {
  self.projection_provider
}

///|
fn LogitLocator::is_bound(self : LogitLocator) -> Bool {
  self.maxn.tick_space_provider() is Some(_) ||
  self.maxn.view_interval_provider() is Some(_) ||
  self.minpos_provider is Some(_) ||
  self.projection_provider is Some(_)
}

///|
pub fn LogitLocator::call(
  self : LogitLocator,
) -> Array[Double] raise @core.DataError {
  self.diagnostics.clear()
  let (lower, upper) = match self.maxn.view_interval_provider() {
    Some(provider) => provider()
    None =>
      raise @core.DataError::InvalidTicks(
        message="logit locator is not bound to a view interval",
      )
  }
  self.tick_values(lower, upper)
}

///|
pub fn LogitLocator::tick_values(
  self : LogitLocator,
  lower : Double,
  upper : Double,
) -> Array[Double] raise @core.DataError {
  self.diagnostics.clear()
  named_logit_locator_ticks(self, lower, upper)
}

///|
pub fn LogitLocator::nonsingular(
  self : LogitLocator,
  lower : Double,
  upper : Double,
) -> (Double, Double) raise @core.DataError {
  self.diagnostics.clear()
  let (first, last) = if lower > upper {
    (upper, lower)
  } else {
    (lower, upper)
  }
  let mut first = first
  let mut last = last
  let standard = 1.0e-7
  if first.is_nan() || first.is_inf() || last.is_nan() || last.is_inf() {
    return (standard, 1.0 - standard)
  }
  if last <= 0.0 || first >= 1.0 {
    self.diagnostics.push(
      "Data has no values between 0 and 1, and therefore cannot be logit-scaled.",
    )
    return (standard, 1.0 - standard)
  }
  let mut minimum = match self.minpos_provider {
    Some(provider) => provider()
    None =>
      if self.is_bound() {
        raise @core.DataError::InvalidTicks(
          message="bound logit locator has no minimum positive data provider",
        )
      } else {
        standard
      }
  }
  if minimum.is_nan() || minimum.is_inf() {
    minimum = standard
  }
  if first <= 0.0 {
    first = minimum
  }
  if last >= 1.0 {
    last = 1.0 - minimum
  }
  if first == last {
    first = 0.1 * first
    last = 1.0 - first
  }
  (first, last)
}

///|
/// Inherited MaxN view selection does not call the Logit probability nonsingular.
pub fn LogitLocator::view_limits(
  self : LogitLocator,
  lower : Double,
  upper : Double,
) -> (Double, Double) raise @core.DataError {
  self.diagnostics.clear()
  self.inherited_view_limits(lower, upper)
}