///|
fn conversion_decimal(
  cs : Array[Char],
  cursor : Ref[Int],
) -> Int raise TclError {
  let mut value = 0
  while cursor.val < cs.length() &&
        cs[cursor.val] >= '0' &&
        cs[cursor.val] <= '9' {
    value = value * 10 + cs[cursor.val].to_int() - 48
    if value > 1000000 {
      raise Invalid("conversion size limit")
    }
    cursor.val += 1
  }
  value
}

///|
fn conversion_argument(
  args : Array[TclValue],
  cursor : Ref[Int],
) -> TclValue raise TclError {
  if cursor.val < 2 || cursor.val >= args.length() {
    raise Invalid("not enough arguments for format")
  }
  let value = args[cursor.val]
  cursor.val += 1
  value
}

///|
fn conversion_trim(text : String) -> String {
  let cs = utf16_units(text)
  let mut a = 0
  let mut b = cs.length()
  while a < b && list_space(cs[a]) {
    a += 1
  }
  while b > a && list_space(cs[b - 1]) {
    b -= 1
  }
  unit_slice(text, a, b)
}

///|
fn conversion_integer(text : String) -> @bigint.BigInt raise TclError {
  let trimmed = conversion_trim(text)
  if trimmed.is_empty() || numeric_prefix(trimmed, false) != trimmed.length() {
    raise Invalid("expected integer")
  }
  whole(trimmed)
}

///|
fn conversion_wrap(
  value : @bigint.BigInt,
  bits : Int,
  signed : Bool,
) -> @bigint.BigInt {
  let modulus = 1N << bits
  let raw = value & (modulus - 1N)
  if signed && raw >= modulus >> 1 {
    raw - modulus
  } else {
    raw
  }
}

///|
fn conversion_int32(text : String) -> Int raise TclError {
  let value = conversion_integer(text)
  if value < -4294967295N || value > 4294967295N {
    raise Invalid("integer value too large to represent")
  }
  conversion_wrap(value, 32, true).to_int()
}

///|
fn format_command(args : Array[TclValue]) -> String raise TclError {
  if args.length() < 2 {
    raise Invalid("format arity")
  }
  let cs = utf16_units(args[1].text)
  let cursor = Ref(0)
  let next = Ref(2)
  let mut positional = None
  let out = StringBuilder()
  let mut total = 0
  while cursor.val < cs.length() {
    if cs[cursor.val] != '%' {
      out.write_char(cs[cursor.val])
      cursor.val += 1
      total += 1
      if total > 1000000 {
        raise Invalid("format output size limit")
      }
      continue
    }
    cursor.val += 1
    if cursor.val < cs.length() && cs[cursor.val] == '%' {
      out.write_char('%')
      cursor.val += 1
      total += 1
      if total > 1000000 {
        raise Invalid("format output size limit")
      }
      continue
    }
    let saved = cursor.val
    let position = conversion_decimal(cs, cursor)
    let uses_position = cursor.val < cs.length() && cs[cursor.val] == '$'
    if positional is Some(previous) && previous != uses_position {
      raise Invalid("cannot mix positional and sequential format specifiers")
    }
    positional = Some(uses_position)
    if uses_position {
      if position < 1 || position + 1 >= args.length() {
        raise Invalid("format argument index out of range")
      }
      next.val = position + 1
      cursor.val += 1
    } else {
      cursor.val = saved
    }
    let mut left = false
    let mut plus = false
    let mut space = false
    let mut zero = false
    let mut alternate = false
    while cursor.val < cs.length() {
      match cs[cursor.val] {
        '-' => left = true
        '+' => plus = true
        ' ' => space = true
        '0' => zero = true
        '#' => alternate = true
        _ => break
      }
      cursor.val += 1
    }
    let mut width = if cursor.val < cs.length() && cs[cursor.val] == '*' {
      cursor.val += 1
      conversion_int32(conversion_argument(args, next).text)
    } else {
      conversion_decimal(cs, cursor)
    }
    if width < -1000000 || width > 1000000 {
      raise Invalid("format width limit")
    }
    if width < 0 {
      width = -width
      left = true
    }
    let precision = if cursor.val < cs.length() && cs[cursor.val] == '.' {
      cursor.val += 1
      let p = if cursor.val < cs.length() && cs[cursor.val] == '*' {
        cursor.val += 1
        conversion_int32(conversion_argument(args, next).text).max(0)
      } else {
        conversion_decimal(cs, cursor)
      }
      if p > 1000000 {
        raise Invalid("format precision limit")
      }
      Some(p)
    } else {
      None
    }
    let mut bits = 32
    if cursor.val < cs.length() && cs[cursor.val] == 'h' {
      bits = 16
      cursor.val += 1
    } else if cursor.val < cs.length() && cs[cursor.val] == 'l' {
      bits = 64
      cursor.val += 1
      if cursor.val < cs.length() && cs[cursor.val] == 'l' {
        bits = 0
        cursor.val += 1
      }
    }
    if cursor.val >= cs.length() {
      raise Invalid("incomplete format conversion")
    }
    let kind = cs[cursor.val]
    cursor.val += 1
    if !['d', 'i', 'u', 'o', 'x', 'X', 'b', 'c', 's', 'f', 'e', 'E', 'g', 'G'].contains(
        kind,
      ) {
      raise Invalid("invalid format conversion")
    }
    let value = conversion_argument(args, next)
    let mut prefix = ""
    let body = if kind == 's' {
      match precision {
        Some(p) => unit_slice(value.text, 0, p.min(value.text.length()))
        None => value.text
      }
    } else if kind == 'c' {
      let raw = conversion_integer(value.text)
      if raw < -4294967295N || raw > 4294967295N {
        raise Invalid("character integer out of range")
      }
      let cp = conversion_wrap(raw, 32, true).to_int()
      unit_text(if cp >= 0 && cp <= 65535 { cp } else { 65533 })
    } else if ['f', 'e', 'E', 'g', 'G'].contains(kind) {
      let text = conversion_trim(value.text)
      if text.is_empty() || numeric_prefix(text, true) != text.length() {
        raise Invalid("expected floating-point number")
      }
      let x = match value.as_number() {
        Some(n) => n.double()
        None => {
          let (length, value, _) = scan_real_prefix(text)
          if length != text.length() {
            raise Invalid("expected floating-point number")
          }
          value
        }
      }
      if x.is_nan() {
        raise Invalid("floating point value is Not a Number")
      }
      prefix = if x.reinterpret_as_uint64() >> 63 == 1UL {
        "-"
      } else if plus {
        "+"
      } else if space {
        " "
      } else {
        ""
      }
      if x.is_inf() {
        zero = false
        if kind == 'E' || kind == 'G' {
          "INF"
        } else {
          "inf"
        }
      } else {
        format_real(x.abs(), kind, precision.unwrap_or(6), alternate)
      }
    } else {
      if kind == 'u' && bits == 0 {
        raise Invalid("unsigned bignum format is invalid")
      }
      let signed = kind == 'd' || kind == 'i' || bits == 0
      let raw = conversion_integer(value.text)
      let value = if bits == 0 {
        raw
      } else {
        conversion_wrap(raw, bits, signed)
      }
      let magnitude = if value < 0N { -value } else { value }
      prefix = if value < 0N {
        "-"
      } else if signed && plus {
        "+"
      } else if signed && space {
        " "
      } else {
        ""
      }
      let radix = if kind == 'x' || kind == 'X' {
        16
      } else if kind == 'o' {
        8
      } else if kind == 'b' {
        2
      } else {
        10
      }
      let raw_digits = magnitude.to_string(radix~)
      let raw_digits = if kind == 'X' {
        unicode_case(raw_digits, 1)
      } else {
        raw_digits
      }
      let digits = "0".repeat(
          precision.unwrap_or(0).max(raw_digits.length()) - raw_digits.length(),
        ) +
        raw_digits
      if alternate {
        if kind == 'x' {
          prefix += "0x"
        } else if kind == 'X' {
          prefix += "0X"
        } else if kind == 'b' {
          prefix += "0b"
        } else if kind == 'o' && !digits.has_prefix("0") {
          prefix += "0"
        }
      }
      if precision is Some(_) {
        zero = false
      }
      digits
    }
    if ['f', 'e', 'E', 'g', 'G'].contains(kind) && left {
      zero = false
    }
    if !['s', 'c', 'f', 'e', 'E', 'g', 'G'].contains(kind) && zero {
      left = false
    }
    let padding = (width - prefix.length() - body.length()).max(0)
    let piece = if left {
      prefix + body + (if zero { "0" } else { " " }).repeat(padding)
    } else if zero {
      prefix + "0".repeat(padding) + body
    } else {
      " ".repeat(padding) + prefix + body
    }
    total += piece.length()
    if total > 1000000 {
      raise Invalid("format output size limit")
    }
    out.write_string(piece)
  }
  out.to_string()
}