///|
fn pdf_text_tounicode_bfrange_inline_entries(
  line : BytesView,
) -> Array[(Int, @core.PdfBytes)]? {
  let entries : Array[(Int, @core.PdfBytes)] = Array(capacity=line.length())
  let mut index = 0
  while index < line.length() {
    match pdf_text_parse_cmap_hex_group_at(line, index) {
      Some((source, after_source)) =>
        match pdf_text_parse_cmap_hex_group_at(line, after_source) {
          Some((stop, after_stop)) =>
            match (pdf_text_bytes_to_int(source), pdf_text_bytes_to_int(stop)) {
              (Some(first), Some(last)) if first <= last => {
                index = pdf_text_skip_cmap_whitespace(line, after_stop)
                if index < line.length() && line[index].to_int() == 91 {
                  match pdf_text_index_of_byte(line, 93, index + 1) {
                    Some(close) => {
                      match
                        pdf_text_parse_cmap_hex_groups(line[index + 1:close]) {
                        Some(unicodes) =>
                          match
                            pdf_text_tounicode_bfrange_array_entries(
                              first, last, unicodes,
                            ) {
                            Some(line_entries) =>
                              for entry in line_entries {
                                entries.push(entry)
                              }
                            None => return None
                          }
                        None => return None
                      }
                      index = close + 1
                    }
                    None => return None
                  }
                } else {
                  match pdf_text_parse_cmap_hex_group_at(line, index) {
                    Some((unicode, after_unicode)) => {
                      for code in first..<=last {
                        match
                          pdf_text_add_to_big_endian_bytes(
                            unicode,
                            code - first,
                          ) {
                          Some(bytes) => entries.push((code, bytes))
                          None => return None
                        }
                      }
                      index = after_unicode
                    }
                    None => return None
                  }
                }
              }
              _ => return None
            }
          None => return None
        }
      None => break
    }
  }
  if entries.length() == 0 {
    None
  } else {
    Some(entries)
  }
}

///|
fn pdf_text_add_to_big_endian_bytes(
  bytes : @core.PdfBytes,
  increment : Int,
) -> @core.PdfBytes? {
  guard increment >= 0 else { return None }
  let output = bytes.to_array()
  let mut carry = increment
  let mut index = output.length()
  while index > 0 && carry > 0 {
    index -= 1
    let sum = output[index].to_int() + carry % 256
    output[index] = (sum % 256).to_byte()
    carry = carry / 256 + sum / 256
  }
  if carry == 0 {
    Some(Bytes::from_array(output))
  } else {
    None
  }
}

///|
fn pdf_text_tounicode_bfrange_array_entries(
  first : Int,
  last : Int,
  unicodes : ArrayView[@core.PdfBytes],
) -> Array[(Int, @core.PdfBytes)]? {
  guard first <= last && unicodes.length() == last - first + 1 else {
    return None
  }
  let entries : Array[(Int, @core.PdfBytes)] = Array(capacity=unicodes.length())
  for index in 0.. Array[(Int, @core.PdfBytes)]? {
  pdf_text_tounicode_bfrange_inline_entries(line)
}

///|
fn pdf_text_push_tounicode_bfrange_entries(
  entries : Array[(Int, @core.PdfBytes)],
  line_entries : ArrayView[(Int, @core.PdfBytes)],
) -> Unit {
  for entry in line_entries {
    entries.push(entry)
  }
}

///|
priv struct PdfTextCMapBFRangeArray {
  first : Int
  last : Int
  unicodes : Array[@core.PdfBytes]
}

///|
fn pdf_text_extend_cmap_hex_groups(
  output : Array[@core.PdfBytes],
  line : BytesView,
) -> Unit {
  match pdf_text_parse_cmap_hex_groups(line) {
    Some(groups) =>
      for group in groups {
        output.push(group)
      }
    None => ()
  }
}

///|
fn pdf_text_cmap_bfrange_array_line(line : BytesView) -> BytesView {
  match pdf_text_index_of_byte(line, 91, 0) {
    Some(open) => line[open + 1:]
    None => line
  }
}

///|
priv enum PdfTextCMapBFRangeArrayAppend {
  PdfTextCMapBFRangeArrayPending(PdfTextCMapBFRangeArray)
  PdfTextCMapBFRangeArrayClosed(BytesView)
}

///|
fn pdf_text_append_pending_bfrange_array(
  entries : Array[(Int, @core.PdfBytes)],
  pending : PdfTextCMapBFRangeArray,
  line : BytesView,
) -> PdfTextCMapBFRangeArrayAppend {
  let content = pdf_text_cmap_bfrange_array_line(line)
  match pdf_text_index_of_byte(content, 93, 0) {
    Some(close) => {
      pdf_text_extend_cmap_hex_groups(pending.unicodes, content[0:close])
      match
        pdf_text_tounicode_bfrange_array_entries(
          pending.first,
          pending.last,
          pending.unicodes,
        ) {
        Some(line_entries) =>
          pdf_text_push_tounicode_bfrange_entries(entries, line_entries)
        None => ()
      }
      PdfTextCMapBFRangeArrayClosed(content[close + 1:])
    }
    None => {
      pdf_text_extend_cmap_hex_groups(pending.unicodes, content)
      PdfTextCMapBFRangeArrayPending(pending)
    }
  }
}

///|
priv enum PdfTextCMapBFRangeLineAppend {
  PdfTextCMapBFRangeLineDone
  PdfTextCMapBFRangeLinePending(PdfTextCMapBFRangeArray)
  PdfTextCMapBFRangeLineContinue(BytesView)
}

///|
fn pdf_text_append_tounicode_bfrange_entry(
  entries : Array[(Int, @core.PdfBytes)],
  line : BytesView,
) -> PdfTextCMapBFRangeLineAppend {
  match pdf_text_parse_cmap_hex_group_at(line, 0) {
    Some((source, after_source)) =>
      match pdf_text_parse_cmap_hex_group_at(line, after_source) {
        Some((stop, after_stop)) =>
          match (pdf_text_bytes_to_int(source), pdf_text_bytes_to_int(stop)) {
            (Some(first), Some(last)) if first <= last => {
              let value_start = pdf_text_skip_cmap_whitespace(line, after_stop)
              let pending = PdfTextCMapBFRangeArray::{
                first,
                last,
                unicodes: [],
              }
              if value_start >= line.length() {
                PdfTextCMapBFRangeLinePending(pending)
              } else if line[value_start].to_int() == 91 {
                match
                  pdf_text_append_pending_bfrange_array(
                    entries,
                    pending,
                    line[value_start + 1:],
                  ) {
                  PdfTextCMapBFRangeArrayClosed(rest) =>
                    PdfTextCMapBFRangeLineContinue(rest)
                  PdfTextCMapBFRangeArrayPending(pending) =>
                    PdfTextCMapBFRangeLinePending(pending)
                }
              } else {
                match pdf_text_parse_cmap_hex_group_at(line, value_start) {
                  Some((unicode, after_unicode)) => {
                    let line_entries : Array[(Int, @core.PdfBytes)] = Array(
                      capacity=last - first + 1,
                    )
                    for code in first..<=last {
                      match
                        pdf_text_add_to_big_endian_bytes(unicode, code - first) {
                        Some(bytes) => line_entries.push((code, bytes))
                        None => return PdfTextCMapBFRangeLineDone
                      }
                    }
                    pdf_text_push_tounicode_bfrange_entries(
                      entries, line_entries,
                    )
                    PdfTextCMapBFRangeLineContinue(line[after_unicode:])
                  }
                  None => PdfTextCMapBFRangeLineDone
                }
              }
            }
            _ => PdfTextCMapBFRangeLineDone
          }
        None => PdfTextCMapBFRangeLineDone
      }
    None => PdfTextCMapBFRangeLineDone
  }
}

///|
fn pdf_text_append_tounicode_bfrange_line(
  entries : Array[(Int, @core.PdfBytes)],
  line : BytesView,
) -> PdfTextCMapBFRangeArray? {
  match pdf_text_tounicode_bfrange_entries_of_line(line) {
    Some(line_entries) => {
      pdf_text_push_tounicode_bfrange_entries(entries, line_entries)
      return None
    }
    None => ()
  }
  let mut current = line
  while current.length() > 0 {
    match pdf_text_append_tounicode_bfrange_entry(entries, current) {
      PdfTextCMapBFRangeLineDone => return None
      PdfTextCMapBFRangeLinePending(pending) => return Some(pending)
      PdfTextCMapBFRangeLineContinue(rest) => current = rest
    }
  }
  None
}