///|
fn log_format_type(value : LogValue) -> String {
  match value {
    NoneValue => "NoneType"
    Text(_) => "str"
    Numeric(Number(_) | NumpyFloat64(_)) => "float"
    Numeric(Boolean(_) | NumpyBool(_)) => "bool"
    Numeric(_) => "int"
  }
}

///|
fn log_format_numeric(
  value : LogValue,
  unary~ : Bool = false,
) -> ScalarNumber raise @core.DataError {
  match value {
    Numeric(value) => value
    _ =>
      raise log_format_error(
        if unary {
          "TypeError: bad operand type for unary -: '" +
          log_format_type(value) +
          "'"
        } else {
          "TypeError: must be real number, not " + log_format_type(value)
        },
      )
  }
}

///|
fn log_format_negative(value : LogValue) -> ScalarNumber raise @core.DataError {
  match log_format_numeric(value, unary=true) {
    Number(value) => ScalarNumber::Number(-value)
    NumpyFloat64(value) => ScalarNumber::NumpyFloat64(-value)
    Integer(value) => ScalarNumber::Integer(value.neg())
    Boolean(value) =>
      ScalarNumber::Integer(
        @bigint.BigInt::from_int(if value { -1 } else { 0 }),
      )
    NumpyInt64(value) =>
      ScalarNumber::NumpyInt64(
        (0UL - value.reinterpret_as_uint64()).reinterpret_as_int64(),
      )
    NumpyBool(_) =>
      raise log_format_error(
        "TypeError: The numpy boolean negative, the `-` operator, is not supported, use the `~` operator or the logical_not function instead.",
      )
  }
}

///|
fn log_format_threshold_greater(
  value : ScalarNumber,
  threshold : LogValue,
) -> Bool raise @core.DataError {
  match threshold {
    Numeric(other) => eng_prefix_less(other, value)
    _ =>
      raise log_format_error(
        "TypeError: '>' not supported between instances of '" +
        log_format_type(LogValue::Numeric(value)) +
        "' and '" +
        log_format_type(threshold) +
        "'",
      )
  }
}

///|
fn log_named_prepare(
  base : BaseFormatter,
  state : LogFormatterState,
) -> Unit raise @core.DataError {
  let first = log_threshold_at(state.minor_thresholds, 0)
  let infinite = match first {
    Numeric(number) => {
      ignore(log_format_isfinite(number, "isinf"))
      match number {
        Number(value) | NumpyFloat64(value) => value.is_inf()
        _ => false
      }
    }
    _ =>
      raise log_format_error(
        "TypeError: ufunc 'isinf' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''",
      )
  }
  if infinite {
    state.sublabels = None
    return
  }
  let mut linthresh = state.linthresh
  if linthresh is NoneValue {
    let transform = try {
      match base.axis {
        Some(axis) => axis.transform_linthresh()
        None =>
          raise log_format_error(
            "AttributeError: 'NoneType' object has no attribute 'get_transform'",
          )
      }
    } catch {
      @core.DataError::InvalidTicks(message~) =>
        if message.has_prefix("AttributeError:") {
          None
        } else {
          raise log_format_error(message)
        }
      error => raise error
    }
    if transform is Some(value) {
      linthresh = LogValue::Numeric(value)
    }
  }
  let (vmin, vmax) = scalar_numeric_view(base, sort=true)
  let zero = ScalarNumber::Integer(@bigint.BigInt::from_int(0))
  if linthresh is NoneValue && !eng_prefix_less(zero, vmin) {
    // Python <= is false for NaN, unlike the complement of >.
    if eng_prefix_compare(vmin, zero) is Some(order) && order <= 0 {
      state.sublabels = Some(log_cache_explicit([1.0]))
      return
    }
  }
  let b = state.base
  let mut numticks = @bigint.BigInt::from_int(0)
  let mut numdec = ScalarNumber::Integer(@bigint.BigInt::from_int(0))
  if !(linthresh is NoneValue) {
    let negative = log_format_negative(linthresh)
    if eng_prefix_less(vmin, negative) {
      let negative = log_format_negative(linthresh)
      let rhs = if eng_prefix_less(negative, vmax) { negative } else { vmax }
      numticks += scalar_numeric_floor(
          log_format_log(scalar_numeric_abs(rhs), b),
        ) -
        scalar_numeric_floor(
          log_format_next_down(log_format_log(scalar_numeric_abs(vmin), b)),
        )
      numdec = scalar_numeric_op(
        numdec,
        ScalarNumber::Number(log_format_log(scalar_numeric_op(vmin, rhs, 3), b)),
        0,
      )
    }
    let positive = log_format_numeric(linthresh)
    if eng_prefix_less(positive, vmax) {
      let positive = log_format_numeric(linthresh)
      let lhs = if eng_prefix_less(vmin, positive) { positive } else { vmin }
      numticks += scalar_numeric_floor(log_format_log(vmax, b)) -
        scalar_numeric_floor(log_format_next_down(log_format_log(lhs, b)))
      numdec = scalar_numeric_op(
        numdec,
        ScalarNumber::Number(log_format_log(scalar_numeric_op(vmax, lhs, 3), b)),
        0,
      )
    }
  } else {
    let lmin = log_format_log(vmin, b)
    let lmax = log_format_log(vmax, b)
    numticks = scalar_numeric_floor(lmax) -
      scalar_numeric_floor(log_format_next_down(lmin))
    numdec = ScalarNumber::Number(Double::abs(lmax - lmin))
  }
  if log_format_threshold_greater(
      ScalarNumber::Integer(numticks),
      log_threshold_at(state.minor_thresholds, 0),
    ) {
    state.sublabels = Some(log_cache_explicit([1.0]))
  } else if log_format_threshold_greater(
      numdec,
      log_threshold_at(state.minor_thresholds, 1),
    ) {
    let cache = log_cache_geom(b)
    state.sublabels = Some(cache)
  } else {
    let cache = log_cache_arange(b)
    state.sublabels = Some(cache)
  }
}