///|
let segment_magic : Bytes = b"MOONSEG\x00"

///|
let segment_format_version : UInt = 2U

///|
let legacy_segment_format_version : UInt = 1U

///|
fn encode_schema(buffer : @buffer.Buffer, schema : Schema?) -> Unit {
  match schema {
    None => write_bool(buffer, false)
    Some(schema) => {
      write_bool(buffer, true)
      buffer.write_int_be(schema.field_count())
      for field_index in 0.. name
          None => abort("schema field disappeared during encoding")
        }
        let options = match schema.options(field_id) {
          Some(options) => options
          None => abort("schema options disappeared during encoding")
        }
        write_string(buffer, name)
        write_bool(buffer, options.is_indexed())
        write_bool(buffer, options.is_stored())
        write_string(buffer, options.tokenizer_name())
      }
    }
  }
}

///|
fn encode_payload(segment : Segment) -> Bytes {
  let buffer = @buffer.Buffer()
  encode_schema(buffer, segment.schema)
  buffer.write_int_be(segment.document_count)
  buffer.write_int_be(segment.indexed_terms.length())
  for term_index in 0.. Bytes {
  let buffer = @buffer.Buffer(
    size_hint=segment_header_length + payload.length(),
  )
  buffer.write_bytes(segment_magic[:])
  buffer.write_uint_be(segment_format_version)
  buffer.write_int_be(payload.length())
  buffer.write_uint_be(segment_checksum(payload))
  buffer.write_bytes(payload[:])
  buffer.to_bytes()
}

///|
pub fn encode_segment(segment : Segment) -> Bytes {
  encode_container(encode_payload(segment))
}

///|
fn decode_schema(
  decoder : SegmentDecoder,
  version : UInt,
) -> Schema? raise PersistenceError {
  if !decoder.read_bool("schema marker") {
    return None
  }
  let field_count = decoder.read_count("schema field count")
  let builder = SchemaBuilder::new()
  for _ in 0.. Bool {
  if field_id.value < 0 {
    return false
  }
  match schema {
    Some(schema) => field_id.value < schema.field_count()
    None => true
  }
}

///|
fn decode_postings(
  decoder : SegmentDecoder,
  document_count : Int,
) -> ReadOnlyArray[Posting] raise PersistenceError {
  let posting_count = decoder.read_count("posting count")
  let postings : Array[Posting] = []
  let mut previous_doc_id = -1
  for _ in 0..= 0 && doc_id < document_count && doc_id > previous_doc_id else {
      raise PersistenceError::InvalidFormat("invalid posting doc id: \{doc_id}")
    }
    guard term_freq > 0 && position_count == term_freq else {
      raise PersistenceError::InvalidFormat(
        "term frequency and positions disagree",
      )
    }
    let positions : Array[Int] = []
    let mut previous_position = -1
    for _ in 0..= 0 && position >= previous_position else {
        raise PersistenceError::InvalidFormat("invalid term position")
      }
      positions.push(position)
      previous_position = position
    }
    postings.push({
      doc_id: DocId::new(doc_id),
      term_freq,
      positions: ReadOnlyArray::from_array(positions),
    })
    previous_doc_id = doc_id
  }
  ReadOnlyArray::from_array(postings)
}

///|
fn decode_stored_document(
  decoder : SegmentDecoder,
  schema : Schema?,
) -> StoredDocument raise PersistenceError {
  let value_count = decoder.read_count("stored value count")
  let field_ids : Array[FieldId] = []
  let texts : Array[String] = []
  for _ in 0.. ReadOnlyArray[FieldLength] raise PersistenceError {
  let field_count = decoder.read_count("document field length count")
  let lengths : Array[FieldLength] = []
  for _ in 0.. entry.field_id == field_id) is None else {
      raise PersistenceError::InvalidFormat("invalid field length entry")
    }
    lengths.push({ field_id, length })
  }
  ReadOnlyArray::from_array(lengths)
}

///|
fn decode_field_stats(
  decoder : SegmentDecoder,
  schema : Schema?,
  document_count : Int,
) -> ReadOnlyArray[FieldStats] raise PersistenceError {
  let field_count = decoder.read_count("field stats count")
  let stats_entries : Array[FieldStats] = []
  for _ in 0.. entry.field_id == field_id) is None else {
      raise PersistenceError::InvalidFormat("invalid field statistics")
    }
    stats_entries.push({
      field_id,
      document_count: field_document_count,
      total_length,
    })
  }
  ReadOnlyArray::from_array(stats_entries)
}

///|
fn decode_payload(
  payload : Bytes,
  version : UInt,
) -> Segment raise PersistenceError {
  let decoder = SegmentDecoder::new(payload)
  let schema = decode_schema(decoder, version)
  let document_count = decoder.read_count("document count")
  let term_count = decoder.read_count("term count")
  let terms : Array[Term] = []
  let posting_lists : Array[ReadOnlyArray[Posting]] = []
  for _ in 0.. existing == term) is None else {
      raise PersistenceError::InvalidFormat("duplicate term")
    }
    terms.push(term)
    posting_lists.push(decode_postings(decoder, document_count))
  }
  let stored_count = decoder.read_count("stored document count")
  guard stored_count == document_count else {
    raise PersistenceError::InvalidFormat("stored document count mismatch")
  }
  let stored_documents : Array[StoredDocument] = []
  for _ in 0.. Segment raise PersistenceError {
  guard bytes.length() >= segment_header_length else {
    raise PersistenceError::InvalidFormat("truncated segment header")
  }
  for index in 0..