///|
fn column_name_from_number(col : Int) -> String raise XlsxError {
  column_number_to_name(col)
}

///|
fn parse_column_ref(text : StringView) -> (Int, Bool)? {
  let mut idx = 0
  let mut abs_col = false
  if idx < text.length() && text[idx] == '$' {
    abs_col = true
    idx = idx + 1
  }
  match try_parse_column_name(text[idx:]) {
    Some(col) => Some((col, abs_col))
    None => None
  }
}

///|
fn parse_row_ref(text : StringView) -> (Int, Bool)? {
  let mut idx = 0
  let mut abs_row = false
  if idx < text.length() && text[idx] == '$' {
    abs_row = true
    idx = idx + 1
  }
  match try_parse_row_number(text[idx:]) {
    Some(row) => Some((row, abs_row))
    None => None
  }
}

///|
fn parse_cell_ref(text : StringView) -> (Int, Int, Bool, Bool)? {
  match try_parse_cell_ref_parts(text) {
    Some((row, col, abs_col, abs_row)) => Some((row, col, abs_row, abs_col))
    None => None
  }
}

///|
fn format_column_ref(col : Int, abs_col : Bool) -> String raise XlsxError {
  if col < 1 || col > cell_ref_max_cols {
    raise InvalidCellRef(value="\{col}")
  }
  let prefix = if abs_col { "$" } else { "" }
  let name = column_name_from_number(col)
  "\{prefix}\{name}"
}

///|
// Raises when a shift pushes the row past the grid, so an out-of-grid defined
// name (e.g. `Sheet1!$A$1048577`) is rejected instead of silently serialized.
fn format_row_ref(row : Int, abs_row : Bool) -> String raise XlsxError {
  if row < 1 || row > cell_ref_max_rows {
    raise InvalidCellRef(value="\{row}")
  }
  let prefix = if abs_row { "$" } else { "" }
  "\{prefix}\{row}"
}

///|
fn format_cell_ref(
  row : Int,
  col : Int,
  abs_row : Bool,
  abs_col : Bool,
) -> String raise XlsxError {
  if row < 1 || row > cell_ref_max_rows || col < 1 || col > cell_ref_max_cols {
    raise InvalidCellRef(value="\{col}:\{row}")
  }
  let col_prefix = if abs_col { "$" } else { "" }
  let row_prefix = if abs_row { "$" } else { "" }
  let col_name = column_name_from_number(col)
  "\{col_prefix}\{col_name}\{row_prefix}\{row}"
}

///|
fn is_ref_char(ch : Char) -> Bool {
  ch.is_ascii_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == ':'
}

///|
fn adjust_range_rows_after_insert(
  min_row : Int,
  max_row : Int,
  row : Int,
  count : Int,
) -> (Int, Int) {
  adjust_bounds_for_insert(min_row, max_row, row, count)
}

///|
fn adjust_range_rows_after_remove(
  min_row : Int,
  max_row : Int,
  row : Int,
  count : Int,
) -> (Int, Int)? {
  adjust_bounds_for_remove(min_row, max_row, row, count)
}

///|
fn adjust_range_cols_after_insert(
  min_col : Int,
  max_col : Int,
  col : Int,
  count : Int,
) -> (Int, Int) {
  adjust_bounds_for_insert(min_col, max_col, col, count)
}

///|
fn adjust_range_cols_after_remove(
  min_col : Int,
  max_col : Int,
  col : Int,
  count : Int,
) -> (Int, Int)? {
  adjust_bounds_for_remove(min_col, max_col, col, count)
}

///|
fn adjust_ref_after_row_insert(
  ref_text : StringView,
  row : Int,
  count : Int,
) -> String? raise XlsxError {
  let text = ref_text.to_owned()
  match text.find(":") {
    Some(pos) => {
      let left = text[:pos]
      let right = text[pos + 1:]
      match (parse_cell_ref(left), parse_cell_ref(right)) {
        (Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) => {
          let (new_min, new_max) = adjust_range_rows_after_insert(
            r1, r2, row, count,
          )
          let start = format_cell_ref(new_min, c1, ar1, ac1)
          let end = format_cell_ref(new_max, c2, ar2, ac2)
          Some("\{start}:\{end}")
        }
        _ =>
          match (parse_row_ref(left), parse_row_ref(right)) {
            (Some((r1, ar1)), Some((r2, ar2))) => {
              let (new_min, new_max) = adjust_range_rows_after_insert(
                r1, r2, row, count,
              )
              let start = format_row_ref(new_min, ar1)
              let end = format_row_ref(new_max, ar2)
              Some("\{start}:\{end}")
            }
            _ => Some(text)
          }
      }
    }
    None =>
      match parse_cell_ref(text) {
        Some((r1, c1, ar1, ac1)) => {
          let new_row = if r1 >= row { r1 + count } else { r1 }
          Some(format_cell_ref(new_row, c1, ar1, ac1))
        }
        None =>
          match parse_row_ref(text) {
            Some((r1, ar1)) => {
              let new_row = if r1 >= row { r1 + count } else { r1 }
              Some(format_row_ref(new_row, ar1))
            }
            None => Some(text)
          }
      }
  }
}

///|
fn adjust_ref_after_row_remove(
  ref_text : StringView,
  row : Int,
  count : Int,
) -> String? raise XlsxError {
  let text = ref_text.to_owned()
  match text.find(":") {
    Some(pos) => {
      let left = text[:pos]
      let right = text[pos + 1:]
      match (parse_cell_ref(left), parse_cell_ref(right)) {
        (Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) =>
          match adjust_range_rows_after_remove(r1, r2, row, count) {
            Some((new_min, new_max)) => {
              let start = format_cell_ref(new_min, c1, ar1, ac1)
              let end = format_cell_ref(new_max, c2, ar2, ac2)
              Some("\{start}:\{end}")
            }
            None => None
          }
        _ =>
          match (parse_row_ref(left), parse_row_ref(right)) {
            (Some((r1, ar1)), Some((r2, ar2))) =>
              match adjust_range_rows_after_remove(r1, r2, row, count) {
                Some((new_min, new_max)) => {
                  let start = format_row_ref(new_min, ar1)
                  let end = format_row_ref(new_max, ar2)
                  Some("\{start}:\{end}")
                }
                None => None
              }
            _ => Some(text)
          }
      }
    }
    None =>
      match parse_cell_ref(text) {
        Some((r1, c1, ar1, ac1)) =>
          match adjust_range_rows_after_remove(r1, r1, row, count) {
            Some((new_row, _)) => Some(format_cell_ref(new_row, c1, ar1, ac1))
            None => None
          }
        None =>
          match parse_row_ref(text) {
            Some((r1, ar1)) =>
              match adjust_range_rows_after_remove(r1, r1, row, count) {
                Some((new_row, _)) => Some(format_row_ref(new_row, ar1))
                None => None
              }
            None => Some(text)
          }
      }
  }
}

///|
fn adjust_ref_after_col_insert(
  ref_text : StringView,
  col : Int,
  count : Int,
) -> String? raise XlsxError {
  let text = ref_text.to_owned()
  match text.find(":") {
    Some(pos) => {
      let left = text[:pos]
      let right = text[pos + 1:]
      match (parse_cell_ref(left), parse_cell_ref(right)) {
        (Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) => {
          let (new_min, new_max) = adjust_range_cols_after_insert(
            c1, c2, col, count,
          )
          let start = format_cell_ref(r1, new_min, ar1, ac1)
          let end = format_cell_ref(r2, new_max, ar2, ac2)
          Some("\{start}:\{end}")
        }
        _ =>
          match (parse_column_ref(left), parse_column_ref(right)) {
            (Some((c1, ac1)), Some((c2, ac2))) => {
              let (new_min, new_max) = adjust_range_cols_after_insert(
                c1, c2, col, count,
              )
              let start = format_column_ref(new_min, ac1)
              let end = format_column_ref(new_max, ac2)
              Some("\{start}:\{end}")
            }
            _ => Some(text)
          }
      }
    }
    None =>
      match parse_cell_ref(text) {
        Some((r1, c1, ar1, ac1)) => {
          let new_col = if c1 >= col { c1 + count } else { c1 }
          Some(format_cell_ref(r1, new_col, ar1, ac1))
        }
        None =>
          match parse_column_ref(text) {
            Some((c1, ac1)) => {
              let new_col = if c1 >= col { c1 + count } else { c1 }
              Some(format_column_ref(new_col, ac1))
            }
            None => Some(text)
          }
      }
  }
}

///|
fn adjust_ref_after_col_remove(
  ref_text : StringView,
  col : Int,
  count : Int,
) -> String? raise XlsxError {
  let text = ref_text.to_owned()
  match text.find(":") {
    Some(pos) => {
      let left = text[:pos]
      let right = text[pos + 1:]
      match (parse_cell_ref(left), parse_cell_ref(right)) {
        (Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) =>
          match adjust_range_cols_after_remove(c1, c2, col, count) {
            Some((new_min, new_max)) => {
              let start = format_cell_ref(r1, new_min, ar1, ac1)
              let end = format_cell_ref(r2, new_max, ar2, ac2)
              Some("\{start}:\{end}")
            }
            None => None
          }
        _ =>
          match (parse_column_ref(left), parse_column_ref(right)) {
            (Some((c1, ac1)), Some((c2, ac2))) =>
              match adjust_range_cols_after_remove(c1, c2, col, count) {
                Some((new_min, new_max)) => {
                  let start = format_column_ref(new_min, ac1)
                  let end = format_column_ref(new_max, ac2)
                  Some("\{start}:\{end}")
                }
                None => None
              }
            _ => Some(text)
          }
      }
    }
    None =>
      match parse_cell_ref(text) {
        Some((r1, c1, ar1, ac1)) =>
          match adjust_range_cols_after_remove(c1, c1, col, count) {
            Some((new_col, _)) => Some(format_cell_ref(r1, new_col, ar1, ac1))
            None => None
          }
        None =>
          match parse_column_ref(text) {
            Some((c1, ac1)) =>
              match adjust_range_cols_after_remove(c1, c1, col, count) {
                Some((new_col, _)) => Some(format_column_ref(new_col, ac1))
                None => None
              }
            None => Some(text)
          }
      }
  }
}

///|
fn adjust_defined_name_refers_to_for_sheet(
  refers_to : StringView,
  sheet_name : StringView,
  adjust_fn : (StringView) -> String? raise XlsxError,
  cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
  check_read_cancelled(cancelled)
  let raw_chars = refers_to.to_array()
  let plain_prefix = "\{sheet_name.to_owned()}!"
  let quoted_prefix = "'\{escape_sheet_name(sheet_name)}'!"
  let plain_chars = plain_prefix.to_array()
  let quoted_chars = quoted_prefix.to_array()
  let sb = StringBuilder::new()
  let mut idx = 0
  let find_prefix = fn(
    text : Array[Char],
    prefix : Array[Char],
    start : Int,
  ) -> Int? raise XlsxError {
    if prefix.length() == 0 {
      return None
    }
    let mut pos = start
    while pos + prefix.length() <= text.length() {
      if (pos & 4095) == 0 {
        check_read_cancelled(cancelled)
      }
      let mut matched = true
      for i in 0..
        if p < q {
          Some((p, plain_prefix, plain_chars.length()))
        } else {
          Some((q, quoted_prefix, quoted_chars.length()))
        }
      (Some(p), None) => Some((p, plain_prefix, plain_chars.length()))
      (None, Some(q)) => Some((q, quoted_prefix, quoted_chars.length()))
      (None, None) => None
    }
    match next {
      Some((pos, prefix, prefix_len)) => {
        for i in idx.. sb.write_view(value)
          None => sb.write_view("#REF!")
        }
        idx = end
      }
      None => {
        for i in idx..