///|
/// Errors for malformed input, unsupported features, and invalid batches.
pub(all) suberror ArrowError {
  Invalid(String)
  Unsupported(String)
  LimitExceeded(String)
} derive(Debug)

///|
/// Types supported by the initial IPC implementation.
pub(all) enum DataType {
  Null
  Boolean
  Int32
  Int64
  Float64
  Utf8
  Binary
} derive(Eq, Debug)

///|
/// UTF-8 key/value metadata. Order and duplicate keys are preserved.
pub(all) struct KeyValue {
  key : String
  value : String?
} derive(Eq, Debug)

///|
/// A named field. Extension metadata is preserved without interpreting it.
pub(all) struct Field {
  name : String
  data_type : DataType
  nullable : Bool
  metadata : Array[KeyValue]
} derive(Eq, Debug)

///|
pub fn Field::new(
  name : String,
  data_type : DataType,
  nullable? : Bool = true,
  metadata? : Array[KeyValue] = [],
) -> Field {
  { name, data_type, nullable, metadata, }
}

///|
/// A schema, including ordered custom metadata.
pub(all) struct Schema {
  fields : Array[Field]
  metadata : Array[KeyValue]
} derive(Eq, Debug)

///|
pub fn Schema::new(
  fields : Array[Field],
  metadata? : Array[KeyValue] = [],
) -> Schema {
  { fields, metadata, }
}

///|
/// Owned, decoded column values. None represents an Arrow null slot.
/// Binary data is never interpreted as text; Int64 values retain all 64 bits.
pub(all) enum Column {
  Nulls(Int)
  Booleans(Array[Bool?])
  Int32s(Array[Int?])
  Int64s(Array[Int64?])
  Float64s(Array[Double?])
  Strings(Array[String?])
  Binaries(Array[Bytes?])
} derive(Debug)

///|
pub fn Column::length(self : Column) -> Int {
  match self {
    Nulls(n) => n
    Booleans(a) => a.length()
    Int32s(a) => a.length()
    Int64s(a) => a.length()
    Float64s(a) => a.length()
    Strings(a) => a.length()
    Binaries(a) => a.length()
  }
}

///|
pub fn Column::data_type(self : Column) -> DataType {
  match self {
    Nulls(_) => Null
    Booleans(_) => Boolean
    Int32s(_) => Int32
    Int64s(_) => Int64
    Float64s(_) => Float64
    Strings(_) => Utf8
    Binaries(_) => Binary
  }
}

///|
fn Column::valid_at(self : Column, i : Int) -> Bool {
  match self {
    Nulls(_) => false
    Booleans(a) => a[i] is Some(_)
    Int32s(a) => a[i] is Some(_)
    Int64s(a) => a[i] is Some(_)
    Float64s(a) => a[i] is Some(_)
    Strings(a) => a[i] is Some(_)
    Binaries(a) => a[i] is Some(_)
  }
}

///|
pub fn Column::null_count(self : Column) -> Int {
  let mut count = 0
  for i in 0.. RecordBatch raise ArrowError {
  let rows = match num_rows {
    Some(n) => n
    None => if columns.is_empty() { 0 } else { columns[0].length() }
  }
  let batch = { schema, columns, num_rows: rows, }
  batch.validate()
  batch
}

///|
pub fn RecordBatch::validate(self : RecordBatch) -> Unit raise ArrowError {
  if self.num_rows < 0 {
    raise Invalid("negative row count")
  }
  if self.columns.length() != self.schema.fields.length() {
    raise Invalid("column count does not match schema")
  }
  for i in 0.. ReadLimits {
  {
    max_rows_per_batch: 1_000_000,
    max_values_per_batch: 8_000_000,
    max_fields: 1024,
    max_batches: 10_000,
    max_metadata_bytes: 16 * 1024 * 1024,
    max_body_bytes: 256 * 1024 * 1024,
  }
}

///|
fn ReadLimits::validate(self : ReadLimits) -> Unit raise ArrowError {
  if self.max_rows_per_batch < 0 ||
    self.max_values_per_batch < 0 ||
    self.max_fields < 0 ||
    self.max_batches < 0 ||
    self.max_metadata_bytes < 0 ||
    self.max_body_bytes < 0 {
    raise Invalid("reader limits must be nonnegative")
  }
}

///|
/// Materialized IPC data. For batch-at-a-time decoding use StreamReader/FileReader.
pub struct IpcData {
  schema : Schema
  batches : Array[RecordBatch]
} derive(Debug)