///|
fn pdf_bookmark_listing_escape_codepoints(
  codepoints : ArrayView[Int],
) -> Array[Int] {
  let output = Array::make(
    pdf_bookmark_listing_escape_codepoints_length(codepoints),
    0,
  )
  let mut position = 0
  for codepoint in codepoints {
    match codepoint {
      34 => {
        output[position] = 92
        output[position + 1] = 34
        position += 2
      }
      92 => {
        output[position] = 92
        output[position + 1] = 92
        position += 2
      }
      10 => {
        output[position] = 92
        output[position + 1] = 110
        position += 2
      }
      _ => {
        output[position] = codepoint
        position += 1
      }
    }
  }
  output
}

///|
fn pdf_bookmark_listing_escape_codepoints_length(
  codepoints : ArrayView[Int],
) -> Int {
  let mut length = 0
  for codepoint in codepoints {
    match codepoint {
      34 | 92 | 10 => length += 2
      _ => length += 1
    }
  }
  length
}

///|
fn pdf_bookmark_listing_stripped_codepoints_length(
  codepoints : ArrayView[Int],
) -> Int {
  let mut length = 0
  for codepoint in codepoints {
    if codepoint <= 127 {
      length += 1
    }
  }
  length
}

///|
fn pdf_bookmark_listing_stripped_codepoints(
  codepoints : ArrayView[Int],
) -> @core.PdfBytes raise @core.PdfError {
  let output = Array::make(
    pdf_bookmark_listing_stripped_codepoints_length(codepoints),
    b'\x00',
  )
  let mut position = 0
  for codepoint in codepoints {
    if codepoint <= 127 {
      output[position] = @core.pdf_byte_of_int(codepoint)
      position += 1
    }
  }
  Bytes::from_array(output)
}

///|
fn pdf_bookmark_listing_process_text(
  encoding : @metadata.PdfMetadataEncoding,
  text : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
  match encoding {
    PdfMetadataRaw => text.to_owned()
    PdfMetadataUTF8 => {
      let escaped = pdf_bookmark_listing_escape_codepoints(
        pdf_codepoints_of_pdfdocstring(text),
      )
      pdf_utf8_of_codepoints(escaped)
    }
    PdfMetadataStripped => {
      let escaped = pdf_bookmark_listing_escape_codepoints(
        pdf_codepoints_of_pdfdocstring(text),
      )
      pdf_bookmark_listing_stripped_codepoints(escaped)
    }
  }
}

///|
priv struct PdfBookmarkListingTextLine {
  level : @core.PdfBytes
  text : @core.PdfBytes
  page : @core.PdfBytes
  target : @core.PdfBytes
  is_open : Bool
}

///|
fn pdf_bookmark_copy_view(
  output : Array[Byte],
  position : Int,
  bytes : BytesView,
) -> Int {
  let mut position = position
  for byte in bytes {
    output[position] = byte
    position += 1
  }
  position
}

///|
fn pdf_bookmark_copy_ascii(
  output : Array[Byte],
  position : Int,
  text : String,
) -> Int {
  pdf_bookmark_copy_view(output, position, @ascii.encode(text))
}

///|
fn PdfBookmarkListingTextLine::byte_length(
  self : PdfBookmarkListingTextLine,
) -> Int {
  let open_length = if self.is_open { 5 } else { 0 }
  self.level.length() +
  self.text.length() +
  self.page.length() +
  self.target.length() +
  8 +
  open_length
}

///|
fn PdfBookmarkListingTextLine::write_to(
  self : PdfBookmarkListingTextLine,
  output : Array[Byte],
  position : Int,
) -> Int {
  let mut position = pdf_bookmark_copy_view(output, position, self.level)
  position = pdf_bookmark_copy_ascii(output, position, " \"")
  position = pdf_bookmark_copy_view(output, position, self.text)
  position = pdf_bookmark_copy_ascii(output, position, "\" ")
  position = pdf_bookmark_copy_view(output, position, self.page)
  if self.is_open {
    position = pdf_bookmark_copy_ascii(output, position, " open")
  }
  position = pdf_bookmark_copy_ascii(output, position, " \"")
  position = pdf_bookmark_copy_view(output, position, self.target)
  pdf_bookmark_copy_ascii(output, position, "\"\n")
}

///|
fn PdfDocument::pdf_bookmark_listing_text_line(
  self : PdfDocument,
  bookmark : @bookmark.PdfBookmark,
  encoding : @metadata.PdfMetadataEncoding,
) -> PdfBookmarkListingTextLine raise @core.PdfError {
  let page_number = self.page_number_of_target(bookmark.target)
  {
    level: @ascii.encode(bookmark.level.to_string()),
    text: pdf_bookmark_listing_process_text(encoding, bookmark.text),
    page: @ascii.encode(page_number.to_string()),
    target: pdf_write_object(
      pdf_bookmark_listing_target_object(bookmark.target, page_number),
    ),
    is_open: bookmark.is_open,
  }
}