///|
pub(all) struct Attachment {
  log_time : UInt64
  create_time : UInt64
  name : String
  media_type : String
  data : Bytes
} derive(Eq, @debug.Debug)

///|
pub extend Attachment with Eq::{equal, not_equal}

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

///|
pub fn encode_attachment(value : Attachment, checksums? : Bool = true) -> Bytes {
  let out = @buffer.Buffer::Buffer()
  out.write_uint64_le(value.log_time)
  out.write_uint64_le(value.create_time)
  put_text(out, value.name)
  put_text(out, value.media_type)
  out.write_uint64_le(value.data.length().to_uint64())
  out.write_bytes(value.data[:])
  out.write_uint_le(if checksums { crc32(out.to_bytes()) } else { 0U })
  out.to_bytes()
}

///|
pub fn decode_attachment(
  bytes : Bytes,
  limits? : Limits = default_limits(),
) -> Attachment raise McapError {
  let input = cursor(bytes, limits)
  let log_time = input.u64()
  let create_time = input.u64()
  let name = input.text()
  let media_type = input.text()
  let size = bounded_length(input.u64(), limits.max_record_bytes, "attachment")
  let data = input.take(size)
  let crc_end = input.pos
  let checksum = input.u32()
  verify_crc(bytes[0:crc_end].to_owned(), checksum, label="attachment")
  { log_time, create_time, name, media_type, data, }
}

///|
pub(all) struct AttachmentIndex {
  offset : UInt64
  length : UInt64
  log_time : UInt64
  create_time : UInt64
  data_size : UInt64
  name : String
  media_type : String
}

///|
pub fn encode_attachment_index(value : AttachmentIndex) -> Bytes {
  let out = @buffer.Buffer::Buffer()
  out.write_uint64_le(value.offset)
  out.write_uint64_le(value.length)
  out.write_uint64_le(value.log_time)
  out.write_uint64_le(value.create_time)
  out.write_uint64_le(value.data_size)
  put_text(out, value.name)
  put_text(out, value.media_type)
  out.to_bytes()
}

///|
pub fn decode_attachment_index(
  bytes : Bytes,
  limits? : Limits = default_limits(),
) -> AttachmentIndex raise McapError {
  let input = cursor(bytes, limits)
  let offset = input.u64()
  let length = input.u64()
  let log_time = input.u64()
  let create_time = input.u64()
  let data_size = input.u64()
  let name = input.text()
  let media_type = input.text()
  { offset, length, log_time, create_time, data_size, name, media_type, }
}

///|
pub fn find_attachments(
  log : Log,
  name? : String = "",
  media_type? : String = "",
) -> Array[Attachment] {
  log.attachments
  .filter(fn(a) {
    (name == "" || a.name == name) &&
    (media_type == "" || a.media_type == media_type)
  })
  .map(fn(a) { { ..a, data: a.data.copy(), } })
}

///|
pub fn validate_attachment_indexes(
  bytes : Bytes,
  limits? : Limits = default_limits(),
) -> Int raise McapError {
  let records = scan_file(bytes, limits~)
  let physical : Map[UInt64, Record] = {}
  for record in records {
    physical[record.offset.to_uint64()] = record
  }
  let seen : Map[UInt64, Bool] = {}
  let mut count = 0
  for record in records {
    if record.opcode != 0x0a {
      continue
    }
    let index = decode_attachment_index(record.body, limits~)
    if seen.contains(index.offset) {
      raise Invalid("duplicate AttachmentIndex")
    }
    seen[index.offset] = true
    let target = match physical.get(index.offset) {
      Some(value) => value
      None => raise Invalid("AttachmentIndex points outside record boundary")
    }
    if target.opcode != 0x09 ||
      index.length != (target.body.length() + 9).to_uint64() {
      raise Invalid("AttachmentIndex type or length mismatch")
    }
    let attachment = decode_attachment(target.body, limits~)
    if index.log_time != attachment.log_time ||
      index.create_time != attachment.create_time ||
      index.name != attachment.name ||
      index.media_type != attachment.media_type ||
      index.data_size != attachment.data.length().to_uint64() {
      raise Invalid("AttachmentIndex properties disagree with Attachment")
    }
    count += 1
  }
  if count != 0 && count != records.filter(fn(r) { r.opcode == 0x09 }).length() {
    raise Invalid("AttachmentIndex set does not cover attachments")
  }
  count
}