///|
pub(open) trait AsyncWriter {
  async fn write(Self, BytesView) -> Unit
}

///|
pub async fn[T : AsyncWriter] async_write_varint(
  writer : T,
  input : UInt64,
) -> Unit {
  let mut v = input >> 7
  let mut length = 0
  while v > 0 {
    v = v >> 7
    length += 1
  }
  let bytes = Bytes::makei(length + 1, fn(i) {
    let byte = ((input >> (i * 7)) & 0x7F).to_byte()
    if i == length {
      byte
    } else {
      byte | 0x80
    }
  })
  writer.write(bytes)
}

///|
pub async fn[T : AsyncWriter] async_write_tag(
  writer : T,
  tag : (UInt, UInt),
) -> Unit {
  let (tag, wire_type) = tag
  writer |> async_write_varint(((tag << 3) | wire_type).to_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_int32(writer : T, v : Int) -> Unit {
  writer |> async_write_varint(v.to_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_int64(writer : T, v : Int64) -> Unit {
  writer |> async_write_varint(v.reinterpret_as_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_uint32(writer : T, v : UInt) -> Unit {
  writer |> async_write_varint(v.to_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_uint64(
  writer : T,
  v : UInt64,
) -> Unit {
  writer |> async_write_varint(v)
}

///|
pub async fn[T : AsyncWriter] async_write_sint32(writer : T, v : Int) -> Unit {
  let v64 = v.to_int64()
  writer
  |> async_write_varint(((v64 << 1) ^ (v64 >> 31)).reinterpret_as_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_sint64(writer : T, v : Int64) -> Unit {
  writer |> async_write_varint(((v << 1) ^ (v >> 63)).reinterpret_as_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_fixed32(writer : T, v : UInt) -> Unit {
  writer.write(Bytes::makei(4, fn(i) { ((v >> (i * 8)) & 0xFF).to_byte() }))
}

///|
pub async fn[T : AsyncWriter] async_write_fixed64(
  writer : T,
  v : UInt64,
) -> Unit {
  writer.write(Bytes::makei(8, fn(i) { ((v >> (i * 8)) & 0xFF).to_byte() }))
}

///|
pub async fn[T : AsyncWriter] async_write_sfixed32(writer : T, v : Int) -> Unit {
  writer |> async_write_fixed32(v.reinterpret_as_uint())
}

///|
pub async fn[T : AsyncWriter] async_write_sfixed64(
  writer : T,
  v : Int64,
) -> Unit {
  writer |> async_write_fixed64(v.reinterpret_as_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_float(writer : T, v : Float) -> Unit {
  writer |> async_write_fixed32(v.reinterpret_as_uint())
}

///|
pub async fn[T : AsyncWriter] async_write_double(
  writer : T,
  v : Double,
) -> Unit {
  writer |> async_write_fixed64(v.reinterpret_as_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_bool(writer : T, v : Bool) -> Unit {
  writer.write([if v { 1 } else { 0 }])
}

///|
pub async fn[T : AsyncWriter] async_write_enum(writer : T, v : Enum) -> Unit {
  writer |> async_write_varint(v.0.to_uint64())
}

///|
pub async fn[T : AsyncWriter] async_write_bytes(writer : T, v : Bytes) -> Unit {
  let length = v.length()
  writer |> async_write_varint(length.to_uint64())
  writer.write(v)
}

///|
pub async fn[T : AsyncWriter] async_write_string(
  writer : T,
  v : String,
) -> Unit {
  let utf8_string = encode_utf8_string(v)
  writer
  |> async_write_varint(utf8_string.length().reinterpret_as_uint().to_uint64())
  writer.write(utf8_string)
}

///|
/// Writes a length-delimited embedded message with a size prefix.
pub async fn[M : AsyncWrite + Sized, T : AsyncWriter] async_write_message(
  writer : T,
  message : M,
) -> Unit {
  writer |> async_write_uint32(size_of(message))
  AsyncWrite::write(message, writer)
}

///|
/// Writes a delimited message field envelope for `field_number`.
///
/// The encoded form is start-group tag, message payload, then the matching
/// end-group tag. Generated code uses this for proto2 groups and Editions
/// `features.message_encoding = DELIMITED`.
pub async fn[M : AsyncWrite, T : AsyncWriter] async_write_delimited_message(
  writer : T,
  field_number : UInt,
  message : M,
) -> Unit {
  writer |> async_write_tag((field_number, WIRE_TYPE_START_GROUP))
  AsyncWrite::write(message, writer)
  writer |> async_write_tag((field_number, WIRE_TYPE_END_GROUP))
}