///|
fn bits_at(bytes : Bytes, offset : Int, width : Int, little : Bool) -> UInt64 {
  let mut bits = 0UL
  for i = 0; i < width; i = i + 1 {
    let index = if little { offset + i } else { offset + width - 1 - i }
    bits = bits | (bytes[index].to_uint64() << (i * 8))
  }
  bits
}

///|
fn push_bits(out : Array[Byte], bits : UInt64, width : Int) -> Unit {
  for i = 0; i < width; i = i + 1 {
    out.push(((bits >> (i * 8)) & 255UL).to_byte())
  }
}

///|
fn scalar_at(
  bytes : Bytes,
  offset : Int,
  dtype : DType,
  little : Bool,
) -> Value {
  let bits = bits_at(bytes, offset, dtype.width(), little)
  match dtype {
    Float64 => Floating(bits.reinterpret_as_double())
    Float32 =>
      Floating(Float::reinterpret_from_uint(bits.to_uint()).to_double())
    Int8 => Signed(bits.reinterpret_as_int64() << 56 >> 56)
    Int16 => Signed(bits.reinterpret_as_int64() << 48 >> 48)
    Int32 => Signed(bits.reinterpret_as_int64() << 32 >> 32)
    Int64 => Signed(bits.reinterpret_as_int64())
    UInt8 | UInt16 | UInt32 | UInt64 => Unsigned(bits)
    Logical => Boolean(bits != 0UL)
  }
}

///|
fn signed_value(
  value : Value,
  low : Int64,
  high : Int64,
  offset : Int,
) -> Int64 raise MatError {
  let result = match value {
    Signed(n) => n
    Unsigned(n) => {
      if n > high.reinterpret_as_uint64() {
        fail("value-range", offset, "Integer exceeds signed type range")
      }
      n.reinterpret_as_int64()
    }
    Boolean(b) => if b { 1L } else { 0L }
    Floating(_) => {
      fail(
        "value-type", offset, "Integer arrays require integer scalars, without floating conversion",
      )
      0L
    }
  }
  if result < low || result > high {
    fail("value-range", offset, "Integer exceeds signed type range")
  }
  result
}

///|
fn unsigned_value(
  value : Value,
  high : UInt64,
  offset : Int,
) -> UInt64 raise MatError {
  let result = match value {
    Unsigned(n) => n
    Signed(n) => {
      if n < 0L {
        fail("value-range", offset, "Negative value for unsigned type")
      }
      n.reinterpret_as_uint64()
    }
    Boolean(b) => if b { 1UL } else { 0UL }
    Floating(_) => {
      fail(
        "value-type", offset, "Integer arrays require integer scalars, without floating conversion",
      )
      0UL
    }
  }
  if result > high {
    fail("value-range", offset, "Integer exceeds unsigned type range")
  }
  result
}

///|
fn floating_value(value : Value, offset : Int) -> Double raise MatError {
  match value {
    Floating(n) => n
    Signed(n) => {
      if n < -9007199254740992L || n > 9007199254740992L {
        fail(
          "precision-loss", offset, "Integer cannot be converted safely to floating point",
        )
      }
      n.to_double()
    }
    Unsigned(n) => {
      if n > 9007199254740992UL {
        fail(
          "precision-loss", offset, "Integer cannot be converted safely to floating point",
        )
      }
      n.to_double()
    }
    Boolean(b) => if b { 1.0 } else { 0.0 }
  }
}

///|
fn append_value(
  out : Array[Byte],
  value : Value,
  dtype : DType,
  offset : Int,
) -> Unit raise MatError {
  let bits = match dtype {
    Float64 => floating_value(value, offset).reinterpret_as_uint64()
    Float32 => {
      let number = floating_value(value, offset)
      let narrow = Float::from_double(number)
      if !number.is_inf() && !number.is_nan() && narrow.to_double().is_inf() {
        fail("value-range", offset, "Finite number overflows float32")
      }
      narrow.reinterpret_as_uint().to_uint64()
    }
    Int8 => signed_value(value, -128L, 127L, offset).reinterpret_as_uint64()
    Int16 =>
      signed_value(value, -32768L, 32767L, offset).reinterpret_as_uint64()
    Int32 =>
      signed_value(value, -2147483648L, 2147483647L, offset).reinterpret_as_uint64()
    Int64 =>
      signed_value(value, -9223372036854775808L, 9223372036854775807L, offset).reinterpret_as_uint64()
    UInt8 => unsigned_value(value, 255UL, offset)
    UInt16 => unsigned_value(value, 65535UL, offset)
    UInt32 => unsigned_value(value, 4294967295UL, offset)
    UInt64 => unsigned_value(value, 18446744073709551615UL, offset)
    Logical =>
      match value {
        Boolean(b) => if b { 1UL } else { 0UL }
        Signed(n) => if n != 0L { 1UL } else { 0UL }
        Unsigned(n) => if n != 0UL { 1UL } else { 0UL }
        Floating(n) => if n != 0.0 { 1UL } else { 0UL }
      }
  }
  push_bits(out, bits, dtype.width())
}

///|
fn validate_name(
  name : String,
  limits : Limits,
  allow_empty? : Bool = false,
) -> Unit raise MatError {
  let bytes = @utf8.encode(name[:])
  if (!allow_empty && bytes.length() == 0) ||
    bytes.length() > limits.max_name_bytes ||
    name.contains("\u{0000}") {
    fail(
      "invalid-name", 0, "Name must be nonempty UTF-8 without NUL and within max_name_bytes",
    )
  }
}

///|
fn validate_layout(array : NumericArray, limits : Limits) -> Int raise MatError {
  let count = element_count(array.shape, limits, 0)
  let expected = count * array.dtype.width()
  let planes = if array.is_complex() { 2 } else { 1 }
  if expected > limits.max_file_bytes / planes {
    fail("file-limit", 0, "Array payload exceeds configured file byte limit")
  }
  if array.real.length() != expected {
    fail(
      "payload-size", 0, "Real payload length does not match shape and dtype",
    )
  }
  if array.imag is Some(imag) {
    if array.dtype != Float64 && array.dtype != Float32 {
      fail(
        "unsupported-complex", 0, "Complex arrays require float32 or float64",
      )
    }
    if imag.length() != expected {
      fail(
        "payload-size", 0, "Imaginary payload length does not match real payload",
      )
    }
  }
  count
}

///|
fn validate_array(
  array : NumericArray,
  limits : Limits,
  allow_empty? : Bool = false,
) -> Int raise MatError {
  validate_name(array.name, limits, allow_empty~)
  let count = validate_layout(array, limits)
  if array.dtype == Logical {
    for value in array.real {
      if value != b'\x00' && value != b'\x01' {
        fail(
          "invalid-logical", 0, "Canonical logical payload must contain only 0 or 1",
        )
      }
    }
  }
  count
}

///|
/// Build an array from exact scalar values in column-major order. Float32 conversion rounds to IEEE binary32.
pub fn numeric_array(
  name : String,
  dtype : DType,
  shape : Array[Int],
  values : Array[Value],
  imaginary? : Array[Value],
  is_global? : Bool = false,
  limits? : Limits = default_limits(),
) -> NumericArray raise MatError {
  validate_limits(limits)
  validate_name(name, limits)
  let count = element_count(shape, limits, 0)
  if values.length() != count {
    fail("payload-size", 0, "Value count does not match shape")
  }
  let planes = if imaginary is Some(_) { 2 } else { 1 }
  if count * dtype.width() > limits.max_file_bytes / planes {
    fail("file-limit", 0, "Array payload exceeds configured file byte limit")
  }
  let real = []
  for value in values {
    append_value(real, value, dtype, 0)
  }
  let imag = match imaginary {
    None => None
    Some(values) => {
      if dtype != Float64 && dtype != Float32 {
        fail("unsupported-complex", 0, "Complex arrays require floating dtype")
      }
      if values.length() != count {
        fail("payload-size", 0, "Imaginary value count does not match shape")
      }
      let bytes = []
      for value in values {
        append_value(bytes, value, dtype, 0)
      }
      Some(Bytes::from_array(bytes))
    }
  }
  {
    name,
    dtype,
    shape: shape.copy(),
    real: Bytes::from_array(real),
    imag,
    is_global,
  }
}

///|
/// Build an array from canonical little-endian raw payload; preserves NaN payload bits and signed zero.
pub fn raw_array(
  name : String,
  dtype : DType,
  shape : Array[Int],
  real : Bytes,
  imag? : Bytes,
  is_global? : Bool = false,
  limits? : Limits = default_limits(),
) -> NumericArray raise MatError {
  validate_limits(limits)
  let result : NumericArray = {
    name,
    dtype,
    shape: shape.copy(),
    real,
    imag,
    is_global,
  }
  ignore(validate_array(result, limits))
  result
}

///|
pub fn NumericArray::value(
  self : NumericArray,
  index : Int,
  imaginary? : Bool = false,
) -> Value raise MatError {
  let count = validate_layout(self, default_limits())
  if index < 0 || index >= count {
    fail("index-range", index, "Scalar index outside array")
  }
  let bytes = if imaginary {
    match self.imag {
      Some(bytes) => bytes
      None => {
        fail("no-imaginary", index, "Array is real")
        self.real
      }
    }
  } else {
    self.real
  }
  if self.dtype == Logical && bytes[index] != b'\x00' && bytes[index] != b'\x01' {
    fail("invalid-logical", index, "Canonical logical value must be 0 or 1")
  }
  scalar_at(bytes, index * self.dtype.width(), self.dtype, true)
}

///|
/// Convert multidimensional zero-based coordinates to a column-major linear index.
pub fn NumericArray::index(
  self : NumericArray,
  coordinates : Array[Int],
) -> Int raise MatError {
  if coordinates.length() != self.shape.length() {
    fail("index-range", 0, "Coordinate rank differs from shape")
  }
  // Revalidate shape, because public fields may have been constructed by callers.
  if validate_array(self, default_limits(), allow_empty=true) == 0 {
    fail("index-range", 0, "Empty array has no coordinates")
  }
  let mut stride = 1
  let mut index = 0
  for i = 0; i < coordinates.length(); i = i + 1 {
    let coordinate = coordinates[i]
    if coordinate < 0 || coordinate >= self.shape[i] {
      fail("index-range", i, "Coordinate outside dimension")
    }
    index = index + coordinate * stride
    stride = stride * self.shape[i]
  }
  index
}