///|
priv enum NumericArrayStorage {
  Boolean(Array[Bool])
  Unsigned64(Array[UInt64])
  Objects(Array[ScalarNumber])
}

///|
/// A readonly numeric ndarray snapshot outside Float64 and Int64 tick buffers.
/// UInt64 values project to exact Python integers through get_value.
pub struct NumericArray {
  priv storage : NumericArrayStorage
}

///|
pub fn NumericArray::length(self : NumericArray) -> Int32 {
  match self.storage {
    Boolean(values) => values.length()
    Unsigned64(values) => values.length()
    Objects(values) => values.length()
  }
}

///|
pub fn NumericArray::dtype(self : NumericArray) -> String {
  match self.storage {
    Boolean(_) => "bool"
    Unsigned64(_) => "uint64"
    Objects(_) => "object"
  }
}

///|
fn NumericArray::checked_index(
  self : NumericArray,
  index : Int32,
) -> Int32 raise @core.DataError {
  let normalized = if index < 0 {
    Int64::from_int(self.length()) + Int64::from_int(index)
  } else {
    Int64::from_int(index)
  }
  if normalized < 0L || normalized >= Int64::from_int(self.length()) {
    raise @core.DataError::InvalidTicks(
      message="IndexError: index " +
        index.to_string() +
        " is out of bounds for axis 0 with size " +
        self.length().to_string(),
    )
  }
  normalized.to_int()
}

///|
pub fn NumericArray::get_value(
  self : NumericArray,
  index : Int32,
) -> ScalarNumber raise @core.DataError {
  let index = self.checked_index(index)
  match self.storage {
    Boolean(values) => ScalarNumber::NumpyBool(values[index])
    Unsigned64(values) =>
      ScalarNumber::Integer(@bigint.BigInt::from_uint64(values[index]))
    Objects(values) => values[index]
  }
}

///|
pub fn NumericArray::get(
  self : NumericArray,
  index : Int32,
) -> Double raise @core.DataError {
  scalar_numeric_double(self.get_value(index))
}

///|
/// Exact UInt64 access; other storage kinds raise a typed bridge error.
pub fn NumericArray::get_uint64(
  self : NumericArray,
  index : Int32,
) -> UInt64 raise @core.DataError {
  let index = self.checked_index(index)
  match self.storage {
    Unsigned64(values) => values[index]
    _ =>
      raise @core.DataError::InvalidTicks(
        message="numeric array is not UInt64 storage",
      )
  }
}

///|
priv enum LogitInput {
  Sequence(Array[LogitKey])
  NdArray(TickValues)
}

///|
fn LogitInput::from_array(values : Array[Double]) -> LogitInput {
  Sequence(values.map(value => LogitKey::new(ScalarNumber::Number(value))))
}

///|
fn LogitInput::from_view(values : TickValues) -> LogitInput {
  NdArray(values)
}

///|
fn LogitInput::from_keys(values : Array[LogitKey]) -> LogitInput {
  Sequence(values)
}

///|
fn LogitInput::length(self : LogitInput) -> Int32 {
  match self {
    Sequence(values) => values.length()
    NdArray(values) => values.length()
  }
}

///|
fn LogitInput::key_at(
  self : LogitInput,
  index : Int32,
) -> LogitKey raise @core.DataError {
  let normalized = if index < 0 {
    Int64::from_int(self.length()) + Int64::from_int(index)
  } else {
    Int64::from_int(index)
  }
  if normalized < 0L || normalized >= Int64::from_int(self.length()) {
    let message = match self {
      Sequence(_) => "IndexError: list index out of range"
      NdArray(_) =>
        "IndexError: index " +
        index.to_string() +
        " is out of bounds for axis 0 with size " +
        self.length().to_string()
    }
    raise @core.DataError::InvalidTicks(message~)
  }
  let index = normalized.to_int()
  match self {
    Sequence(values) => values[index]
    NdArray(values) =>
      LogitKey::new(
        if values.is_int64() {
          ScalarNumber::NumpyInt64(values.get_int64(index))
        } else {
          ScalarNumber::NumpyFloat64(values.get(index))
        },
      )
  }
}

///|
priv enum LogitArrayKind {
  Boolean
  Signed64
  Unsigned64
  Float64
  Objects
}

///|
fn logit_array_kind(value : ScalarNumber) -> LogitArrayKind {
  match value {
    Boolean(_) | NumpyBool(_) => LogitArrayKind::Boolean
    NumpyInt64(_) => LogitArrayKind::Signed64
    Number(_) | NumpyFloat64(_) => LogitArrayKind::Float64
    Integer(value) =>
      if value >= @bigint.BigInt::from_int64(-9223372036854775808L) &&
        value <= @bigint.BigInt::from_int64(9223372036854775807L) {
        LogitArrayKind::Signed64
      } else if value >= @bigint.BigInt::from_int(0) &&
        value <= @bigint.BigInt::from_uint64(18446744073709551615UL) {
        LogitArrayKind::Unsigned64
      } else {
        LogitArrayKind::Objects
      }
  }
}

///|
fn logit_array_promote(
  left : LogitArrayKind,
  right : LogitArrayKind,
) -> LogitArrayKind {
  match (left, right) {
    (Objects, _) | (_, Objects) => LogitArrayKind::Objects
    (Float64, _) | (_, Float64) => LogitArrayKind::Float64
    (Signed64, Unsigned64) | (Unsigned64, Signed64) => LogitArrayKind::Float64
    (Unsigned64, _) | (_, Unsigned64) => LogitArrayKind::Unsigned64
    (Signed64, _) | (_, Signed64) => LogitArrayKind::Signed64
    _ => LogitArrayKind::Boolean
  }
}

///|
// np.array makes an independent dtype-preserving snapshot. Original keys
// remain in LogitInput for the later ideal scan, count and set update stages.
fn LogitInput::snapshot(
  self : LogitInput,
) -> FormatterLocations raise @core.DataError {
  if self is NdArray(values) {
    return FormatterLocations::NdArray(values.copy())
  }
  let mut kind = if self.length() == 0 {
    LogitArrayKind::Float64
  } else {
    LogitArrayKind::Boolean
  }
  for index in 0.. {
      let values : Array[Double] = []
      for index in 0.. {
      let values : Array[Int64] = []
      for index in 0.. {
      let values : Array[UInt64] = []
      for index in 0.. {
      let values : Array[Bool] = []
      for index in 0.. {
      let values : Array[ScalarNumber] = []
      for index in 0..