///|
fn eval_len(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) => Number(Double::from_int(text.length()))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_lenb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) => Number(Double::from_int(dbcs_byte_length(text)))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_lower(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) => String(text.to_lower())
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_upper(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) => String(text.to_upper())
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_proper(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) => String(proper_case(text))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_dbcs(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) =>
          if dbcs_enabled(call.workbook) {
            String(dbcs_convert(text))
          } else {
            String(text)
          }
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_trim(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_string(v0) {
        Ok(text) => String(trim_excel_text(text))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_left(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let count = if call.values is [_, v1] {
        match value_as_int(v1) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      if count < 0 {
        Error(formula_error_value)
      } else {
        let len = text.length()
        let end = if count > len { len } else { count }
        let start = 0
        String(text.unsafe_substring(start~, end~))
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_leftb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let count = if call.values is [_, v1] {
        match value_as_int(v1) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      if count < 0 {
        Error(formula_error_value)
      } else {
        let len = dbcs_byte_length(text)
        if count >= len {
          String(text)
        } else {
          String(dbcs_byte_range(text, 1, count))
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_right(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let count = if call.values is [_, v1] {
        match value_as_int(v1) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      if count < 0 {
        Error(formula_error_value)
      } else {
        let len = text.length()
        let start = if count >= len { 0 } else { len - count }
        let end = len
        String(text.unsafe_substring(start~, end~))
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_rightb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let count = if call.values is [_, v1] {
        match value_as_int(v1) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      if count < 0 {
        Error(formula_error_value)
      } else {
        let len = dbcs_byte_length(text)
        if count >= len {
          String(text)
        } else {
          String(dbcs_byte_range(text, len - count + 1, count))
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_mid(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = match value_as_int(v1) {
        Ok(num) => num
        Err(err) => return err
      }
      let count = match value_as_int(v2) {
        Ok(num) => num
        Err(err) => return err
      }
      if start_num <= 0 || count < 0 {
        Error(formula_error_value)
      } else {
        let len = text.length()
        if start_num > len {
          String("")
        } else {
          let start = start_num - 1
          let mut end = start + count
          if end > len {
            end = len
          }
          String(text.unsafe_substring(start~, end~))
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_midb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = match value_as_int(v1) {
        Ok(num) => num
        Err(err) => return err
      }
      let count = match value_as_int(v2) {
        Ok(num) => num
        Err(err) => return err
      }
      if start_num <= 0 || count < 0 {
        Error(formula_error_value)
      } else if count == 0 {
        String("")
      } else if start_num > dbcs_byte_length(text) {
        String("")
      } else {
        String(dbcs_byte_range(text, start_num, count))
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_rept(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let times = match value_as_int(v1) {
        Ok(num) => num
        Err(err) => return err
      }
      if times < 0 {
        Error(formula_error_value)
      } else if times == 0 || text.length() == 0 {
        String("")
      } else if text.length() > max_cell_chars / times {
        Error(formula_error_value)
      } else {
        String(text.repeat(times))
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_replace(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2, v3] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = match value_as_int(v1) {
        Ok(num) => num
        Err(err) => return err
      }
      let count = match value_as_int(v2) {
        Ok(num) => num
        Err(err) => return err
      }
      let new_text = match value_as_string(v3) {
        Ok(text) => text
        Err(err) => return err
      }
      if start_num <= 0 || count < 0 {
        Error(formula_error_value)
      } else {
        let len = text.length()
        let mut start = start_num - 1
        if start > len {
          start = len
        }
        let mut end = start + count
        if end > len {
          end = len
        }
        let sb = StringBuilder()
        sb.write_view(text.unsafe_substring(start=0, end=start))
        sb.write_view(new_text)
        sb.write_view(text.unsafe_substring(start=end, end=len))
        String(sb.to_string())
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_replaceb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2, v3] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = match value_as_int(v1) {
        Ok(num) => num
        Err(err) => return err
      }
      let count = match value_as_int(v2) {
        Ok(num) => num
        Err(err) => return err
      }
      let new_text = match value_as_string(v3) {
        Ok(text) => text
        Err(err) => return err
      }
      if start_num <= 0 || count < 0 {
        Error(formula_error_value)
      } else {
        let len = dbcs_byte_length(text)
        let prefix_count = if start_num - 1 < len { start_num - 1 } else { len }
        let removed_end = start_num + count - 1
        let suffix_start = if removed_end < len {
          removed_end + 1
        } else {
          len + 1
        }
        let sb = StringBuilder()
        sb.write_view(dbcs_byte_range(text, 1, prefix_count))
        sb.write_view(new_text)
        // suffix runs from just past the replaced range to end of string
        sb.write_view(dbcs_byte_range(text, suffix_start, len))
        String(sb.to_string())
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_substitute(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2] | [v0, v1, v2, _] => {
      let text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let old_text = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      let new_text = match value_as_string(v2) {
        Ok(text) => text
        Err(err) => return err
      }
      if old_text.length() == 0 {
        Error(formula_error_value)
      } else if call.values is [_, _, _, v3] {
        let instance = match value_as_int(v3) {
          Ok(num) => num
          Err(err) => return err
        }
        if instance <= 0 {
          Error(formula_error_value)
        } else {
          let mut index = 0
          let mut count = 0
          let len = text.length()
          let pattern_len = old_text.length()
          while true {
            match find_substring_from(text, old_text, index) {
              Some(found) => {
                count = count + 1
                if count == instance {
                  let sb = StringBuilder()
                  sb.write_view(text.unsafe_substring(start=0, end=found))
                  sb.write_view(new_text)
                  let tail_start = found + pattern_len
                  sb.write_view(
                    text.unsafe_substring(start=tail_start, end=len),
                  )
                  return String(sb.to_string())
                }
                index = found + pattern_len
              }
              None => break
            }
          }
          String(text)
        }
      } else {
        let mut index = 0
        let len = text.length()
        let pattern_len = old_text.length()
        let sb = StringBuilder()
        while true {
          match find_substring_from(text, old_text, index) {
            Some(found) => {
              sb.write_view(text.unsafe_substring(start=index, end=found))
              sb.write_view(new_text)
              index = found + pattern_len
            }
            None => {
              sb.write_view(text.unsafe_substring(start=index, end=len))
              break
            }
          }
        }
        String(sb.to_string())
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_find(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let find_text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let within_text = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = if call.values is [_, _, v2] {
        match value_as_int(v2) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      if start_num <= 0 {
        Error(formula_error_value)
      } else {
        let start = start_num - 1
        let len = within_text.length()
        if start > len {
          Error(formula_error_value)
        } else if find_text.length() == 0 {
          Number(Double::from_int(start_num))
        } else {
          match find_substring_from(within_text, find_text, start) {
            Some(idx) => Number(Double::from_int(idx + 1))
            None => Error(formula_error_value)
          }
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_findb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let find_text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let within_text = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = if call.values is [_, _, v2] {
        match value_as_int(v2) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      match find_dbcs(within_text, find_text, start_num, false) {
        Some(value) => Number(Double::from_int(value))
        None => Error(formula_error_value)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_search(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let find_text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let within_text = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = if call.values is [_, _, v2] {
        match value_as_int(v2) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      if start_num <= 0 {
        Error(formula_error_value)
      } else {
        let start = start_num - 1
        let lower_text = within_text.to_lower()
        let lower_find = find_text.to_lower()
        let len = lower_text.length()
        if start > len {
          Error(formula_error_value)
        } else if lower_find.length() == 0 {
          Number(Double::from_int(start_num))
        } else {
          match wildcard_find(lower_text, lower_find, start) {
            Some(idx) => Number(Double::from_int(idx + 1))
            None => Error(formula_error_value)
          }
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_searchb(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let find_text = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let within_text = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      let start_num = if call.values is [_, _, v2] {
        match value_as_int(v2) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        1
      }
      let lower_find = find_text.to_lower()
      let lower_text = within_text.to_lower()
      match find_dbcs(lower_text, lower_find, start_num, true) {
        Some(value) => Number(Double::from_int(value))
        None => Error(formula_error_value)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_exact(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] => {
      let left = match value_as_string(v0) {
        Ok(text) => text
        Err(err) => return err
      }
      let right = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      Bool(left == right)
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_fixed(call : EvalCall) -> FormulaValue {
  if call.values.length() >= 1 && call.values.length() <= 3 {
    let number = match value_as_number(call.values[0]) {
      Ok(num) => num
      Err(err) => return err
    }
    let mut decimals = if call.values.length() == 1 {
      decimal_places_from_text(formula_value_string(call.values[0]))
    } else {
      match value_as_int(call.values[1]) {
        Ok(num) => num
        Err(err) => return err
      }
    }
    if decimals < 0 {
      decimals = 0
    }
    let no_commas = if call.values.length() == 3 {
      match value_as_bool(call.values[2]) {
        Ok(flag) => flag
        Err(err) => return err
      }
    } else {
      false
    }
    String(format_fixed_number(number, decimals, !no_commas, 1))
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_value(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_number_text(v0) {
        Ok(num) => Number(num)
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_valuetotext(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let format = if call.values is [_, v1] {
        match value_as_int(v1) {
          Ok(num) => num
          Err(err) => return err
        }
      } else {
        0
      }
      if format != 0 && format != 1 {
        Error(formula_error_value)
      } else {
        let text = formula_value_string(v0)
        match value_as_number(v0) {
          Ok(_) => String(text)
          Err(_) =>
            if format == 1 {
              String("\"" + text + "\"")
            } else {
              String(text)
            }
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_text(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] => {
      let format_text = match value_as_string(v1) {
        Ok(text) => text
        Err(err) => return err
      }
      match value_as_number_text(v0) {
        Ok(num) => {
          let formatted = format_number_code(
            format_number(num),
            format_text,
            Options::new(),
            use_1904_format=call.ctx.use_1904_dates,
          ) catch {
            _ => return Error(formula_error_value)
          }
          String(formatted)
        }
        Err(err) => err
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_arraytotext(call : EvalCall) -> FormulaValue raise XlsxError {
  if call.values.length() == 1 || call.values.length() == 2 {
    let format = if call.values.length() == 2 {
      match value_as_int(call.values[1]) {
        Ok(num) => num
        Err(err) => return err
      }
    } else {
      0
    }
    if format != 0 && format != 1 {
      return Error(formula_error_value)
    }
    let range = match call.args[0] {
      Range(_, _, _) | Cell(_, _) =>
        eval_range_expr(call.workbook, call.sheet_name, call.args[0], call.ctx)
      _ => { rows: 1, cols: 1, values: [call.values[0]], }
    }
    let rows : Array[String] = []
    for row in 0.. return Error(err)
          _ => {
            let text = formula_value_string(value)
            match value_as_number(value) {
              Ok(_) => cells.push(text)
              Err(_) =>
                if format == 1 {
                  cells.push("\"" + text + "\"")
                } else {
                  cells.push(text)
                }
            }
          }
        }
      }
      let joined = if format == 1 { cells.join(",") } else { cells.join(", ") }
      rows.push(joined)
    }
    if format == 1 {
      String("{" + rows.join(";") + "}")
    } else {
      String(rows.join(", "))
    }
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_textafter(call : EvalCall) -> FormulaValue {
  if call.values.length() >= 2 && call.values.length() <= 6 {
    let text = match value_as_string(call.values[0]) {
      Ok(value) => value
      Err(err) => return err
    }
    let delimiter = match value_as_string(call.values[1]) {
      Ok(value) => value
      Err(err) => return err
    }
    if text == "" {
      return Error(formula_error_na)
    }
    let instance_num = if call.values.length() >= 3 {
      match value_as_int(call.values[2]) {
        Ok(num) => num
        Err(err) => return err
      }
    } else {
      1
    }
    if instance_num == 0 ||
      instance_num > text.length() ||
      -instance_num > text.length() {
      return Error(formula_error_value)
    }
    let match_mode = if call.values.length() >= 4 {
      match value_as_int(call.values[3]) {
        Ok(num) => num
        Err(err) => return err
      }
    } else {
      0
    }
    if match_mode != 0 && match_mode != 1 {
      return Error(formula_error_value)
    }
    let match_end = if call.values.length() >= 5 {
      match value_as_bool(call.values[4]) {
        Ok(flag) => flag
        Err(err) => return err
      }
    } else {
      false
    }
    let if_not_found = if call.values.length() == 6 {
      call.values[5]
    } else {
      Empty
    }
    let idx_opt = text_after_before_index(
      text, delimiter, instance_num, match_mode, match_end,
    )
    let idx = match idx_opt {
      Some(value) => value
      None => return if_not_found
    }
    if call.name == "TEXTAFTER" || call.name == "_XLFN.TEXTAFTER" {
      let start = idx + delimiter.length()
      if start >= text.length() {
        Empty
      } else {
        String(text.unsafe_substring(start~, end=text.length()))
      }
    } else if idx <= 0 {
      String("")
    } else {
      String(text.unsafe_substring(start=0, end=idx))
    }
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_textsplit(call : EvalCall) -> FormulaValue {
  if call.values.length() >= 2 && call.values.length() <= 6 {
    textsplit_values(call.values)
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_textjoin(call : EvalCall) -> FormulaValue {
  if call.values.length() >= 3 {
    let delimiter = match value_as_string(call.values[0]) {
      Ok(text) => text
      Err(err) => return err
    }
    let ignore_empty = match value_as_bool(call.values[1]) {
      Ok(flag) => flag
      Err(err) => return err
    }
    let parts : Array[String] = []
    for i in 2.. ()
        Err(err) => return err
      }
    }
    let result = parts.join(delimiter)
    if result.length() > max_cell_chars {
      Error(formula_error_value)
    } else {
      String(result)
    }
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_char(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_number(v0) {
        Ok(num) => {
          let code = Double::to_int(trunc_double(num))
          if code < 0 || code > max_field_length {
            Error(formula_error_value)
          } else {
            String(char_from_code(code))
          }
        }
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_unichar(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match value_as_number(v0) {
        Ok(num) =>
          if num <= 0.0 || num > 55295.0 {
            Error(formula_error_value)
          } else {
            let code = Double::to_int(trunc_double(num))
            String(char_from_code(code))
          }
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_clean(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => clean_text_value(v0)
    _ => Error(formula_error_value)
  }
}

///|
fn eval_encodeurl(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => encode_url_value(v0)
    _ => Error(formula_error_value)
  }
}

///|
fn eval_bahttext(call : EvalCall) -> FormulaValue {
  match call.values {
    [_] => bahttext_values(call.values)
    _ => Error(formula_error_value)
  }
}

///|
fn eval_code(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => code_value("CODE", v0)
    _ => Error(formula_error_value)
  }
}

///|
fn eval_unicode(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => code_value("UNICODE", v0)
    _ => Error(formula_error_value)
  }
}

///|
fn eval_t(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match normalize_scalar(v0) {
        Error(err) => Error(err)
        Number(_) | Empty | List(_) => String("")
        String(text) => String(text)
        Bool(flag) => String(if flag { "TRUE" } else { "FALSE" })
      }
    _ => Error(formula_error_value)
  }
}