///|
priv struct BraceState {
  arguments : Array[FormatArgument]
  kinds : Map[String, String]
  mut automatic : Bool
  mut numbered : Bool
  mut next : Int32
}

///|
fn brace_name(state : BraceState, raw : String) -> Result[String, String] {
  if raw == "" {
    state.automatic = true
    if state.numbered {
      return Err("Cannot mix automatic and numbered brace fields")
    }
    let index = state.next
    state.next += 1
    return Ok(index.to_string())
  }
  let chars = raw.to_array()
  let mut i = 0
  while i < chars.length() && chars[i] != '.' && chars[i] != '[' {
    i += 1
  }
  let root = char_slice(chars, 0, i)
  let mut canonical = if root == "" {
    match brace_name(state, "") {
      Ok(name) => name
      Err(error) => return Err(error)
    }
  } else {
    root
  }
  if root != "" && root.to_array().all(c => format_decimal_digit(c) is Some(_)) {
    canonical = match format_decimal_text(root, 10000) {
      None => return Err("Brace argument index exceeds 10000")
      Some(index) => index.to_string()
    }
    state.numbered = true
    if state.automatic {
      return Err("Cannot mix automatic and numbered brace fields")
    }
  }
  while i < chars.length() {
    if chars[i] == '.' {
      i += 1
      let start = i
      while i < chars.length() && chars[i] != '.' && chars[i] != '[' {
        i += 1
      }
      if i == start {
        return Err("Invalid attribute in brace field")
      }
      canonical += "." + char_slice(chars, start, i)
    } else if chars[i] == '[' {
      i += 1
      let start = i
      while i < chars.length() && chars[i] != ']' {
        i += 1
      }
      if i == start || i == chars.length() {
        return Err("Brace index requires content and a closing bracket")
      }
      let index_text = char_slice(chars, start, i)
      let index = if index_text
        .to_array()
        .all(c => format_decimal_digit(c) is Some(_)) {
        match format_decimal_text(index_text, 2147483647) {
          Some(index) => index.to_string()
          None =>
            return Err("Brace element index exceeds the signed 32-bit range")
        }
      } else {
        index_text
      }
      canonical += "[" + index + "]"
      i += 1
    } else {
      return Err("Unexpected character in brace field")
    }
  }
  Ok(canonical)
}

///|
fn brace_presentation(spec : String) -> Result[String, String] {
  let chars = spec.to_array()
  if chars.is_empty() {
    return Ok("default")
  }
  let mut i = 0
  let mut numeric_align = false
  if chars.length() >= 2 && ['<', '>', '=', '^'].contains(chars[1]) {
    numeric_align = chars[1] == '='
    i = 2
  } else if ['<', '>', '=', '^'].contains(chars[0]) {
    numeric_align = chars[0] == '='
    i = 1
  }
  let mut sign = false
  if i < chars.length() && ['+', '-', ' '].contains(chars[i]) {
    sign = true
    i += 1
  }
  let mut negative_zero = false
  if i < chars.length() && chars[i] == 'z' {
    negative_zero = true
    i += 1
  }
  let mut alternate = false
  if i < chars.length() && chars[i] == '#' {
    alternate = true
    i += 1
  }
  if i < chars.length() && chars[i] == '0' {
    i += 1
  }
  while i < chars.length() && chars[i].is_ascii_digit() {
    i += 1
  }
  let mut grouping = false
  let mut comma = false
  if i < chars.length() && [',', '_'].contains(chars[i]) {
    grouping = true
    comma = chars[i] == ','
    i += 1
  }
  let mut precision = false
  if i < chars.length() && chars[i] == '.' {
    precision = true
    i += 1
    let start = i
    while i < chars.length() && chars[i].is_ascii_digit() {
      i += 1
    }
    if i == start {
      return Err("Brace precision requires a number or a nested field")
    }
  }
  if i == chars.length() {
    return Ok(
      if numeric_align || sign || negative_zero || alternate || grouping {
        "numeric"
      } else {
        "default"
      },
    )
  }
  if i + 1 != chars.length() {
    return Err("Unsupported brace format specification: " + spec)
  }
  let kind = match chars[i] {
    'b' | 'd' | 'o' | 'x' | 'X' => "integer"
    'e' | 'E' | 'f' | 'F' | 'g' | 'G' | '%' => "numeric"
    'n' => "locale-numeric"
    'c' => "character"
    's' => "string"
    _ =>
      return Err("Unsupported brace presentation type: " + chars[i].to_string())
  }
  if kind == "string" &&
    (numeric_align || sign || negative_zero || alternate || grouping) {
    return Err("String presentation does not accept numeric format options")
  }
  if (kind == "integer" || kind == "character") && precision {
    return Err("Integer and character presentation do not accept precision")
  }
  if negative_zero && kind != "numeric" {
    return Err("Negative zero coercion requires floating-point presentation")
  }
  if kind == "locale-numeric" && grouping {
    return Err("Locale n presentation does not accept a grouping option")
  }
  if kind == "character" && (sign || alternate || grouping) {
    return Err(
      "Character presentation does not accept sign, alternate form, or grouping",
    )
  }
  if comma && ['b', 'o', 'x', 'X'].contains(chars[i]) {
    return Err("Non-decimal integer presentation requires underscore grouping")
  }
  Ok(kind)
}

///|
fn brace_field(
  chars : Array[Char],
  start : Int32,
  state : BraceState,
  depth : Int32,
) -> Result[Int32, String] {
  if depth > 1 {
    return Err(
      "Python str.format permits only one nested replacement-field level",
    )
  }
  let mut i = start + 1
  let name_start = i
  let mut bracket = false
  while i < chars.length() {
    let c = chars[i]
    if c == '[' {
      bracket = true
    } else if c == ']' {
      bracket = false
    }
    if !bracket && (c == '!' || c == ':' || c == '}') {
      break
    }
    if c == '{' && !bracket {
      return Err("Unexpected opening brace inside a field name")
    }
    i += 1
  }
  if i == chars.length() {
    return Err("Brace field has no closing brace")
  }
  let name = match brace_name(state, char_slice(chars, name_start, i)) {
    Ok(name) => name
    Err(error) => return Err(error)
  }
  let mut conversion = ""
  if chars[i] == '!' {
    i += 1
    if i >= chars.length() || !['s', 'r', 'a'].contains(chars[i]) {
      return Err("Brace conversion must be !s, !r, or !a")
    }
    conversion = chars[i].to_string()
    i += 1
    if i >= chars.length() || (chars[i] != ':' && chars[i] != '}') {
      return Err("Unexpected content after brace conversion")
    }
  }
  let mut presentation = "default"
  if chars[i] == ':' {
    i += 1
    let spec : Array[String] = []
    while i < chars.length() && chars[i] != '}' {
      if chars[i] == '{' {
        match brace_field(chars, i, state, depth + 1) {
          Ok(end) => {
            // A dynamic width/precision is represented by a numeric sentinel.
            spec.push("1")
            i = end
          }
          Err(error) => return Err(error)
        }
      } else {
        spec.push(chars[i].to_string())
        i += 1
      }
    }
    match brace_presentation(join_strings(spec, "")) {
      Ok(kind) => presentation = kind
      Err(error) => return Err(error)
    }
  }
  if i >= chars.length() || chars[i] != '}' {
    return Err("Brace field has no closing brace")
  }
  if conversion != "" && presentation != "default" && presentation != "string" {
    return Err(
      "!s, !r, and !a produce a string and cannot use numeric presentation",
    )
  }
  match
    add_python_argument(
      state.arguments,
      state.kinds,
      name,
      conversion + ":" + presentation,
      start,
    ) {
    Err(error) => Err(error)
    Ok(_) => Ok(i + 1)
  }
}

///|
/// Check Python str.format brace syntax; escaped {{ and }} do not consume args.
pub fn brace_contract(text : String) -> Result[FormatContract, String] {
  let chars = text.to_array()
  let state : BraceState = {
    arguments: [],
    kinds: Map([]),
    automatic: false,
    numbered: false,
    next: 0,
  }
  let mut i = 0
  while i < chars.length() {
    if chars[i] == '{' || chars[i] == '}' {
      if i + 1 < chars.length() && chars[i] == chars[i + 1] {
        i += 2
        continue
      }
      if chars[i] == '}' {
        return Err(
          "Unmatched closing brace at character " + (i + 1).to_string(),
        )
      }
      match brace_field(chars, i, state, 0) {
        Ok(end) => i = end
        Err(error) => return Err(error + " at character " + (i + 1).to_string())
      }
    } else {
      i += 1
    }
  }
  Ok({ arguments: state.arguments, style: "python-brace-format", })
}