///|
/// Append-only compressed metadata stream. References are fixed when a record
/// starts, including records that span blocks. Child-first inode emission avoids
/// circular dependencies between compressed inode and directory layouts.
priv struct MetadataWriter {
  compression : Int
  encode : ((Int, Bytes) -> Bytes raise SquashError)?
  stored : Array[Byte]
  mut pending : Array[Byte]
  starts : Array[Int]
}

///|
fn metadata_writer(
  compression : Int,
  encode : ((Int, Bytes) -> Bytes raise SquashError)?,
) -> MetadataWriter {
  { compression, encode, stored: [], pending: [], starts: [], }
}

///|
fn MetadataWriter::position(self : MetadataWriter) -> UInt64 {
  (self.stored.length().to_uint64() << 16) | self.pending.length().to_uint64()
}

///|
fn MetadataWriter::flush(self : MetadataWriter) -> Unit raise SquashError {
  if self.pending.length() == 0 {
    return
  }
  let raw = Bytes::from_array(self.pending)
  let packed = match self.encode {
    Some(encode) => encode(self.compression, raw)
    None => raw
  }
  self.starts.push(self.stored.length())
  if packed.length() > 0 && packed.length() < raw.length() {
    put16(self.stored, packed.length())
    append_bytes(self.stored, packed)
  } else {
    put16(self.stored, raw.length() | 0x8000)
    append_bytes(self.stored, raw)
  }
  self.pending = []
}

///|
fn MetadataWriter::append(
  self : MetadataWriter,
  bytes : Array[Byte],
) -> Unit raise SquashError {
  for b in bytes {
    self.pending.push(b)
    if self.pending.length() == 8192 {
      self.flush()
    }
  }
}

///|
fn MetadataWriter::mapped_reference(
  self : MetadataWriter,
  raw_offset : Int,
) -> UInt64 {
  (self.starts[raw_offset / 8192].to_uint64() << 16) |
  (raw_offset % 8192).to_uint64()
}

///|
fn compressed_metadata(
  raw : Array[Byte],
  compression : Int,
  encode : ((Int, Bytes) -> Bytes raise SquashError)?,
) -> MetadataWriter raise SquashError {
  let writer = metadata_writer(compression, encode)
  writer.append(raw)
  writer.flush()
  writer
}

///|
fn write_compressed_index(
  output : BuildOutput,
  raw : Array[Byte],
  compression : Int,
  encode : ((Int, Bytes) -> Bytes raise SquashError)?,
) -> Int raise SquashError {
  let writer = compressed_metadata(raw, compression, encode)
  let start = output.length()
  output.append(Bytes::from_array(writer.stored))
  let index = output.length()
  for offset in writer.starts {
    output.uint64((start + offset).to_uint64())
  }
  index
}