///|
let receive_magic : Bytes = b"MOONRX\x00\x00"

///|
let max_receive_bundle_bytes = 16777216

///|
let max_receive_objects = 4096

///|
pub(all) struct ReceivedObject {
  id : ObjectId
  encoded : Bytes
}

///|
pub(all) struct ReceiveRequest {
  ref_name : String
  expected_old : ObjectId?
  target : ObjectId
  objects : Array[ReceivedObject]
}

///|
pub struct ReceiveReport {
  ref_name : String
  target : ObjectId
  checked_commits : Int32
  promoted_objects : Int32
  policy_revision : String
}

///|
fn validate_receive_request(request : ReceiveRequest) -> Unit raise {
  if @utf8.encode(request.ref_name).length() > 1024 {
    raise VcsError::ReceiveInvalid("Ref name exceeds limit")
  }
  ignore(ref_path("/unused", request.ref_name))
  ignore(ObjectId::parse(request.target.hex))
  if request.expected_old is Some(id) {
    ignore(ObjectId::parse(id.hex))
  }
  if request.objects.length() > max_receive_objects {
    raise VcsError::ReceiveInvalid("too many received objects")
  }
  let seen : Map[String, Bool] = Map([])
  let mut size = 128 + @utf8.encode(request.ref_name).length()
  for item in request.objects {
    ignore(ObjectId::parse(item.id.hex))
    if seen.contains(item.id.hex) {
      raise VcsError::ReceiveInvalid("duplicate received ObjectId")
    }
    seen[item.id.hex] = true
    if item.encoded.length() > max_receive_bundle_bytes {
      raise VcsError::ReceiveInvalid("received object exceeds bundle limit")
    }
    size = size + item.encoded.length() + 68
    if size > max_receive_bundle_bytes {
      raise VcsError::ReceiveInvalid("receive bundle exceeds size limit")
    }
  }
}

///|
pub fn encode_receive_request(request : ReceiveRequest) -> Bytes raise {
  validate_receive_request(request)
  let encoder = Encoder::new()
  encoder.bytes(receive_magic)
  encoder.byte(1)
  encoder.string(request.ref_name)
  match request.expected_old {
    None => encoder.byte(0)
    Some(id) => {
      encoder.byte(1)
      encoder.object_id(id)
    }
  }
  encoder.object_id(request.target)
  encoder.u32(request.objects.length())
  for item in request.objects {
    encoder.object_id(item.id)
    encoder.u32(item.encoded.length())
    encoder.bytes(item.encoded)
  }
  let bytes = encoder.finish()
  if bytes.length() > max_receive_bundle_bytes {
    raise VcsError::ReceiveInvalid("encoded bundle exceeds size limit")
  }
  bytes
}

///|
pub fn decode_receive_request(bytes : Bytes) -> ReceiveRequest raise {
  try {
    if bytes.length() > max_receive_bundle_bytes {
      raise VcsError::ReceiveInvalid("bundle exceeds size limit")
    }
    let decoder = Decoder::new(bytes)
    if decoder.bytes(8) != receive_magic || decoder.byte() != 1 {
      raise VcsError::ReceiveInvalid("unsupported receive protocol")
    }
    let ref_name = decoder.string()
    let expected_old = match decoder.byte() {
      0 => None
      1 => Some(decoder.object_id())
      _ => raise VcsError::ReceiveInvalid("invalid expected-old flag")
    }
    let target = decoder.object_id()
    let count = decoder.u32()
    if count > max_receive_objects {
      raise VcsError::ReceiveInvalid("too many received objects")
    }
    let objects : Array[ReceivedObject] = []
    for _ in 0.. raise VcsError::ReceiveInvalid("invalid or oversized receive bundle")
  }
}

///|
pub async fn prepare_receive_request(
  source : Repository,
  ref_name : String,
  expected_old : ObjectId?,
  target : ObjectId,
) -> ReceiveRequest {
  let graph = inspect_receive_graph(source.store, target)
  let objects : Array[ReceivedObject] = []
  for id in graph.ordered {
    objects.push({ id, encoded: encode_object(source.store.get(id)), })
  }
  let request : ReceiveRequest = { ref_name, expected_old, target, objects, }
  validate_receive_request(request)
  request
}

///|
pub async fn receive_bundle_file(
  server : Repository,
  path : String,
) -> ReceiveReport {
  let request = decode_receive_request(
    read_bounded_storage_file(path, max_receive_bundle_bytes),
  ) catch {
    _ => raise VcsError::ReceiveInvalid("unreadable or invalid receive bundle")
  }
  receive_candidate(server, request)
}