///|
fn pdf_javascript_name(text : String) -> @core.PdfName {
  @core.pdf_name_of_bytes(@ascii.encode(text))
}

///|
let pdf_javascript_js_key_name : @core.PdfName = pdf_javascript_name("/JS")

///|
fn pdf_javascript_js_key() -> @core.PdfName {
  pdf_javascript_js_key_name
}

///|
let pdf_javascript_uri_key_name : @core.PdfName = pdf_javascript_name("/URI")

///|
fn pdf_javascript_uri_key() -> @core.PdfName {
  pdf_javascript_uri_key_name
}

///|
let pdf_javascript_s_key_name : @core.PdfName = pdf_javascript_name("/S")

///|
fn pdf_javascript_s_key() -> @core.PdfName {
  pdf_javascript_s_key_name
}

///|
let pdf_javascript_root_key_name : @core.PdfName = pdf_javascript_name("/Root")

///|
fn pdf_javascript_root_key() -> @core.PdfName {
  pdf_javascript_root_key_name
}

///|
let pdf_javascript_names_key_name : @core.PdfName = pdf_javascript_name(
  "/Names",
)

///|
fn pdf_javascript_names_key() -> @core.PdfName {
  pdf_javascript_names_key_name
}

///|
let pdf_javascript_javascript_key_name : @core.PdfName = pdf_javascript_name(
  "/JavaScript",
)

///|
fn pdf_javascript_javascript_key() -> @core.PdfName {
  pdf_javascript_javascript_key_name
}

///|
let pdf_javascript_empty_bytes : @core.PdfBytes = b""

///|
fn pdf_javascript_empty_string() -> @syntax.PdfObject {
  PdfString(pdf_javascript_empty_bytes)
}

///|
let pdf_javascript_action_bytes : @core.PdfBytes = b"/JavaScript"

///|
let pdf_javascript_uri_prefix_bytes : @core.PdfBytes = @ascii.encode(
  "javascript:",
)

///|
fn pdf_javascript_bytes_equal(left : BytesView, right : BytesView) -> Bool {
  if left.length() != right.length() {
    false
  } else {
    for index in 0.. Bool {
  if bytes.length() < prefix.length() {
    false
  } else {
    for index in 0.. Bool {
  match self.lookup_direct(pdf_javascript_uri_key(), object) {
    Some(PdfString(bytes)) =>
      pdf_javascript_bytes_has_prefix(bytes, pdf_javascript_uri_prefix_bytes)
    _ => false
  }
}

///|
fn PdfDocument::pdf_javascript_object_has_action(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> Bool {
  match self.lookup_direct(pdf_javascript_s_key(), object) {
    Some(PdfString(bytes)) =>
      pdf_javascript_bytes_equal(bytes, pdf_javascript_action_bytes)
    _ => false
  }
}

///|
fn PdfDocument::pdf_javascript_object_has_javascript(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> Bool {
  self.pdf_javascript_object_has_action(object) ||
  self.pdf_javascript_object_has_uri(object)
}

///|
fn PdfDocument::pdf_javascript_remove_from_dictionary(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> @syntax.PdfObject {
  let mut output = object
  match self.lookup_direct(pdf_javascript_js_key(), output) {
    Some(_) =>
      output = try! output.add_dict_entry(
        pdf_javascript_js_key(),
        pdf_javascript_empty_string(),
      )
    None => ()
  }
  match self.lookup_direct(pdf_javascript_uri_key(), output) {
    Some(PdfString(bytes)) if pdf_javascript_bytes_has_prefix(
        bytes, pdf_javascript_uri_prefix_bytes,
      ) =>
      output = try! output.add_dict_entry(
        pdf_javascript_uri_key(),
        pdf_javascript_empty_string(),
      )
    _ => ()
  }
  output
}

///|
fn PdfDocument::pdf_javascript_remove_from_object(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> @syntax.PdfObject {
  match object {
    PdfDictionary(entries) => {
      let rewritten = @syntax.pdf_recurse_dict(
        fn(child) { self.pdf_javascript_remove_from_object(child) },
        entries,
      )
      self.pdf_javascript_remove_from_dictionary(rewritten)
    }
    PdfStreamObject(stream) =>
      match stream.dictionary {
        PdfDictionary(entries) => {
          stream.dictionary = @syntax.pdf_recurse_dict(
            fn(child) { self.pdf_javascript_remove_from_object(child) },
            entries,
          )
          self.pdf_javascript_remove_from_dictionary(PdfStreamObject(stream))
        }
        _ => object
      }
    PdfArray(values) =>
      @syntax.pdf_recurse_array(
        fn(child) { self.pdf_javascript_remove_from_object(child) },
        values,
      )
    _ => object
  }
}

///|
fn PdfDocument::pdf_javascript_object_contains(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> Bool {
  if self.pdf_javascript_object_has_javascript(object) {
    true
  } else {
    match object {
      PdfDictionary(entries) =>
        for entry in entries {
          if self.pdf_javascript_object_contains(entry.1) {
            break true
          }
        } nobreak {
          false
        }
      PdfStreamObject(stream) =>
        self.pdf_javascript_object_contains(stream.dictionary)
      PdfArray(values) =>
        for value in values {
          if self.pdf_javascript_object_contains(value) {
            break true
          }
        } nobreak {
          false
        }
      _ => false
    }
  }
}

///|
fn PdfDocument::pdf_javascript_has_name_tree(self : PdfDocument) -> Bool {
  self.lookup_chain(self.trailer_dict(), [
    pdf_javascript_root_key(),
    pdf_javascript_names_key(),
    pdf_javascript_javascript_key(),
  ]) !=
  None
}

///|
/// Return whether parsed document objects or the root names tree contain
/// cpdf-style JavaScript markers.
///
/// This follows cpdf's historical checks: `/URI` strings are detected only when
/// they begin with lowercase `javascript:`, and `/S` is detected only when its
/// resolved value is the PDF string `/JavaScript`.
pub fn PdfDocument::contains_javascript(self : PdfDocument) -> Bool {
  for entry in self.parsed_object_entries() {
    if self.pdf_javascript_object_contains(entry.2) {
      break true
    }
  } nobreak {
    self.pdf_javascript_has_name_tree()
  }
}

///|
/// Empty `/JS` strings, empty lowercase `javascript:` URI strings, and remove
/// the `/Root /Names /JavaScript` chain.
pub fn PdfDocument::remove_javascript(
  self : PdfDocument,
) -> Unit raise @core.PdfError {
  self.map_objects_in_place(fn(object) {
    self.pdf_javascript_remove_from_object(object)
  })
  ignore(
    self.remove_chain([
      pdf_javascript_root_key(),
      pdf_javascript_names_key(),
      pdf_javascript_javascript_key(),
    ]),
  )
}

///|
/// Compatibility wrapper for `PdfDocument::contains_javascript`.
pub fn pdf_contains_javascript(document : PdfDocument) -> Bool {
  document.contains_javascript()
}

///|
/// Compatibility wrapper for `PdfDocument::remove_javascript`.
pub fn pdf_remove_javascript(
  document : PdfDocument,
) -> Unit raise @core.PdfError {
  document.remove_javascript()
}