///|
pub(all) suberror CarError {
  InvalidHeader(String)
  InvalidCid(String)
  InvalidVarint(Int)
  LimitExceeded(String, UInt64, UInt64)
  Truncated(Int)
  InvalidState(String)
  Integrity(Int, String)
  MissingRoot(String)
  NotFound
} derive(Debug)

///|
pub extend CarError with @debug.Debug::{to_repr}

///|
/// Parser budgets apply before allocating a declared payload.
pub struct Limits {
  max_header : Int
  max_block : Int
  max_roots : Int
  max_blocks : Int
  max_archive : Int
}

///|
pub fn Limits::new(
  max_header? : Int = 1024 * 1024,
  max_block? : Int = 16 * 1024 * 1024,
  max_roots? : Int = 1024,
  max_blocks? : Int = 1000000,
  max_archive? : Int = 256 * 1024 * 1024,
) -> Limits raise CarError {
  if max_header <= 0 ||
    max_block <= 0 ||
    max_roots < 0 ||
    max_blocks < 0 ||
    max_archive <= 0 {
    raise InvalidState(
      "limits must be positive; root and block counts may be zero",
    )
  }
  { max_header, max_block, max_roots, max_blocks, max_archive, }
}

///|
pub fn Limits::default() -> Limits {
  {
    max_header: 1024 * 1024,
    max_block: 16 * 1024 * 1024,
    max_roots: 1024,
    max_blocks: 1000000,
    max_archive: 256 * 1024 * 1024,
  }
}

///|
pub struct Header {
  roots : Array[@cid.Cid]
}

///|
pub fn Header::new(roots : Array[@cid.Cid]) -> Header {
  { roots: roots.copy(), }
}

///|
pub fn Header::roots(self : Header) -> Array[@cid.Cid] {
  self.roots.copy()
}

///|
pub struct Block {
  cid : @cid.Cid
  data : Bytes
}

///|
pub fn Block::new(cid : @cid.Cid, data : Bytes) -> Block {
  { cid, data, }
}

///|
pub fn Block::cid(self : Block) -> @cid.Cid {
  self.cid
}

///|
pub fn Block::data(self : Block) -> Bytes {
  self.data
}

///|
/// Absolute offsets into the CARv1 bytes, including the header and framing.
pub(all) struct Location {
  section_offset : Int
  data_offset : Int
  data_length : Int
  section_length : Int
} derive(Debug)

///|
pub extend Location with @debug.Debug::{to_repr}

///|
pub(all) enum Event {
  Header(Header)
  Block(Block, Location)
}

///|
fn cid_limits(limits : Limits) -> @cid.Limits {
  @cid.Limits::new(
    max_input_bytes=limits.max_block.max(2048),
    max_digest_bytes=1024,
  )
}

///|
fn checked_cid(bytes : BytesView, limits : Limits) -> @cid.Cid raise CarError {
  match @cid.Cid::decode_bytes(bytes, cid_limits(limits)) {
    Ok(cid) => cid
    Err(err) => raise InvalidCid(err.to_string())
  }
}

///|
fn budget(name : String, actual : Int, maximum : Int) -> Unit raise CarError {
  if actual > maximum {
    raise LimitExceeded(name, maximum.to_uint64(), actual.to_uint64())
  }
}