///|
pub(all) struct BinaryInfo {
  object_count : Int32
  root_id : Int32
  offset_table : Int32
  offset_width : Int32
  reference_width : Int32
}

///|
fn read_uint(
  data : Bytes,
  pos : Int32,
  width : Int32,
  end : Int32,
) -> UInt64 raise PlistError {
  if pos < 0 ||
    width < 1 ||
    width > 8 ||
    end > data.length() ||
    pos > end ||
    width > end - pos {
    raise Invalid("truncated binary integer")
  }
  let mut n = 0UL
  for i = pos; i < pos + width; i = i + 1 {
    n = (n << 8) | data[i].to_uint64()
  }
  n
}

///|
fn bounded_int(
  n : UInt64,
  bound : Int32,
  what : String,
) -> Int32 raise PlistError {
  if n > bound.to_uint64() {
    raise Limit(what)
  }
  n.to_int()
}

///|
fn valid_width(n : Int32) -> Bool {
  n >= 1 && n <= 8
}

///|
/// Validates the trailer and every object offset without decoding values.
pub fn inspect_binary(
  data : Bytes,
  limits~ : Limits = Limits::default(),
) -> BinaryInfo raise PlistError {
  limits.check()
  if data.length() > limits.max_bytes {
    raise Limit("binary byte budget")
  }
  if data.length() < 41 || data[0:8] != b"bplist00"[:] {
    raise Invalid("expected bplist00 header and trailer")
  }
  let t = data.length() - 32
  let ow = data[t + 6].to_int()
  let rw = data[t + 7].to_int()
  if !valid_width(ow) || !valid_width(rw) {
    raise Invalid("binary offset/reference width")
  }
  let count = bounded_int(
    read_uint(data, t + 8, 8, data.length()),
    limits.max_objects,
    "binary object count",
  )
  if count == 0 {
    raise Invalid("empty binary object table")
  }
  let root = bounded_int(
    read_uint(data, t + 16, 8, data.length()),
    count - 1,
    "root reference",
  )
  let table = bounded_int(
    read_uint(data, t + 24, 8, data.length()),
    t,
    "offset table position",
  )
  if table < 9 || count > (t - table) / ow || table + count * ow != t {
    raise Invalid("offset table size")
  }
  for i = 0; i < count; i = i + 1 {
    let off = bounded_int(
      read_uint(data, table + i * ow, ow, t),
      table - 1,
      "object offset",
    )
    if off < 8 {
      raise Invalid("object overlaps header")
    }
  }
  {
    object_count: count,
    root_id: root,
    offset_table: table,
    offset_width: ow,
    reference_width: rw,
  }
}