///|
fn percent_integer_text(
  value : ScalarNumber,
  code : Char,
  precision : Int32?,
  alternate : Bool,
  positive_sign : Bool,
  available : Int64,
) -> String raise @core.DataError {
  let integer = match value {
    Integer(number) => number
    Boolean(value) => @bigint.BigInt::from_int(if value { 1 } else { 0 })
    NumpyBool(value) => {
      if code == 'o' || code == 'x' || code == 'X' {
        return named_format_error(
          "TypeError",
          "%" +
          code.to_string() +
          " format: an integer is required, not numpy.bool",
        )
      }
      @bigint.BigInt::from_int(if value { 1 } else { 0 })
    }
    NumpyInt64(number) => @bigint.BigInt::from_int64(number)
    Number(number) | NumpyFloat64(number) => {
      if code == 'o' || code == 'x' || code == 'X' {
        return named_format_error(
          "TypeError",
          "%" +
          code.to_string() +
          " format: an integer is required, not " +
          (match value {
            NumpyFloat64(_) => "numpy.float64"
            _ => "float"
          }),
        )
      }
      if number.is_nan() {
        return named_format_error(
          "ValueError", "cannot convert float NaN to integer",
        )
      }
      if number.is_inf() {
        return named_format_error(
          "OverflowError", "cannot convert float infinity to integer",
        )
      }
      let (numerator, denominator) = decimal_ratio(number)
      let integer = numerator / denominator
      if number < 0.0 {
        integer.neg()
      } else {
        integer
      }
    }
  }
  let negative = integer < @bigint.BigInt::from_int(0)
  let magnitude = if negative { integer.neg() } else { integer }
  let radix = if code == 'o' {
    8
  } else if code == 'x' || code == 'X' {
    16
  } else {
    10
  }
  if radix == 10 &&
    magnitude.bit_length() >= 14285 &&
    magnitude >= decimal_power(4300) {
    return named_format_error(
      "ValueError", "Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit",
    )
  }
  let digits = magnitude.to_string(radix~)
  let digits = if code == 'X' { digits.to_upper() } else { digits }
  let prefix = if alternate && code == 'o' {
    "0o"
  } else if alternate && code == 'x' {
    "0x"
  } else if alternate && code == 'X' {
    "0X"
  } else {
    ""
  }
  let count = Int32::max(1, precision.unwrap_or(1))
  let length = Int64::from_int(Int32::max(count, digits.length())) +
    Int64::from_int(prefix.length()) +
    (if negative || positive_sign { 1L } else { 0L })
  if length > available {
    return named_format_error(
      "ResourceError", "percent integer exceeds String addressability",
    )
  }
  (if negative { "-" } else { "" }) + prefix + digits.pad_start(count, '0')
}

///|
fn percent_character_text(value : ScalarNumber) -> String raise @core.DataError {
  let number = match value {
    Integer(number) => {
      if number < @bigint.BigInt::from_int(0) ||
        number >= @bigint.BigInt::from_int(1114112) {
        return named_format_error(
          "OverflowError", "%c arg not in range(0x110000)",
        )
      }
      number.to_int64()
    }
    Boolean(value) => if value { 1L } else { 0L }
    NumpyInt64(number) => number
    _ => return named_format_error("TypeError", "%c requires int or char")
  }
  if number < 0L || number >= 1114112L {
    return named_format_error("OverflowError", "%c arg not in range(0x110000)")
  }
  if number >= 55296L && number <= 57343L {
    return named_format_error(
      "Unsupported", "percent character is an unpaired Unicode surrogate",
    )
  }
  match number.to_int().to_char() {
    Some(character) => character.to_string()
    None =>
      named_format_error(
        "Unsupported", "percent character cannot be represented",
      )
  }
}

///|
fn percent_field_text(
  value : ScalarNumber,
  code : Char,
  precision : Int32?,
  width : Int64,
  left : Bool,
  zero : Bool,
  plus : Bool,
  space : Bool,
  alternate : Bool,
  position : Int32,
  available : Int64,
) -> String raise @core.DataError {
  let numeric = match code {
    'd' | 'i' | 'u' | 'o' | 'x' | 'X' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' =>
      true
    _ => false
  }
  let mut rendered = match code {
    'd' | 'i' | 'u' | 'o' | 'x' | 'X' =>
      percent_integer_text(
        value,
        code,
        precision,
        alternate,
        plus || space,
        available,
      )
    'e' | 'E' | 'f' | 'F' | 'g' | 'G' => {
      let number = scalar_percent_double(value)
      let positive_sign = (plus || space) &&
        (number.is_nan() || numeric_sign(number) == "")
      named_format_float_digits(
        number,
        code,
        precision,
        alternate,
        available=available - (if positive_sign { 1L } else { 0L }),
      )
    }
    'c' => percent_character_text(value)
    's' | 'r' | 'a' => {
      let text = if code == 's' {
        scalar_percent_str(value)
      } else {
        // Repr for all three scalar kinds is ASCII, so ascii(value) is the same.
        scalar_percent_repr(value)
      }
      match precision {
        Some(count) if count < text.length() => text[:count].to_owned()
        _ => text
      }
    }
    _ =>
      return named_format_error(
        "ValueError",
        "unsupported format character '" +
        (if code.to_int() >= 32 && code.to_int() < 127 {
          code.to_string()
        } else {
          "?"
        }) +
        "' (0x" +
        code.to_int().to_string(radix=16) +
        ") at index " +
        position.to_string(),
      )
  }
  if numeric && !rendered.has_prefix("-") {
    if plus {
      rendered = "+" + rendered
    } else if space {
      rendered = " " + rendered
    }
  }
  if width > available {
    return named_format_error(
      "ResourceError", "percent field exceeds String addressability",
    )
  }
  let count = Int64::from_int(rendered.to_array().length())
  let padding = Int64::max(0L, width - count)
  if Int64::from_int(rendered.length()) + padding > available {
    return named_format_error(
      "ResourceError", "percent field exceeds String addressability",
    )
  }
  if padding == 0L {
    return rendered
  }
  if left {
    return rendered + String::make(padding.to_int(), ' ')
  }
  if zero && numeric {
    let sign = if rendered.has_prefix("-") ||
      rendered.has_prefix("+") ||
      rendered.has_prefix(" ") {
      1
    } else {
      0
    }
    let prefix = sign +
      (if alternate && (code == 'o' || code == 'x' || code == 'X') {
        2
      } else {
        0
      })
    return rendered[:prefix].to_owned() +
      String::make(padding.to_int(), '0') +
      rendered[prefix:].to_owned()
  }
  String::make(padding.to_int(), ' ') + rendered
}

///|
// A scalar is consumed once. Parsing still precedes fetching its conversion
// argument, so malformed later fields can win over missing-argument errors.
fn scalar_percent_format(
  format : String,
  value : ScalarNumber,
  tuple~ : Bool = false,
  available~ : Int64 = 2147483647L,
) -> String raise @core.DataError {
  let chars = format.to_array()
  let result = StringBuilder()
  let mut index = 0
  let mut consumed = false
  let mut units = 0L
  while index < chars.length() {
    if chars[index] != '%' {
      units += Int64::from_int(chars[index].utf16_len())
      if units > available {
        return named_format_error(
          "ResourceError", "percent result exceeds String addressability",
        )
      }
      result.write_char(chars[index])
      index += 1
      continue
    }
    index += 1
    if index >= chars.length() {
      return named_format_error("ValueError", "incomplete format")
    }
    if chars[index] == '%' {
      units += 1L
      if units > available {
        return named_format_error(
          "ResourceError", "percent result exceeds String addressability",
        )
      }
      result.write_char('%')
      index += 1
      continue
    }
    if chars[index] == '(' {
      if tuple || value is (Number(_) | Integer(_) | Boolean(_)) {
        return named_format_error("TypeError", "format requires a mapping")
      }
      // NumPy scalars expose the mapping slot. Parse the full nested key first,
      // then retain their actual scalar-subscript failure before field parsing.
      let mut nesting = 1
      index += 1
      while index < chars.length() && nesting > 0 {
        if chars[index] == '(' {
          nesting += 1
        } else if chars[index] == ')' {
          nesting -= 1
        }
        index += 1
      }
      if nesting > 0 {
        return named_format_error("ValueError", "incomplete format key")
      }
      return named_format_error(
        "IndexError", "invalid index to scalar variable.",
      )
    }
    let mut left = false
    let mut zero = false
    let mut plus = false
    let mut space = false
    let mut alternate = false
    while index < chars.length() {
      match chars[index] {
        '-' => left = true
        '0' => zero = true
        '+' => plus = true
        ' ' => space = true
        '#' => alternate = true
        _ => break
      }
      index += 1
    }
    let mut width = 0L
    if index < chars.length() && chars[index] == '*' {
      width = scalar_percent_star(value, consumed, false)
      consumed = true
      if width < 0L {
        left = true
        width = 0L - width
      }
      index += 1
    } else {
      while index < chars.length() && chars[index] >= '0' && chars[index] <= '9' {
        let digit = Int64::from_int(chars[index].to_int() - 48)
        if width > (9223372036854775807L - digit) / 10L {
          return named_format_error("ValueError", "width too big")
        }
        width = width * 10L + digit
        index += 1
      }
    }
    let mut precision : Int32? = None
    if index < chars.length() && chars[index] == '.' {
      index += 1
      if index < chars.length() && chars[index] == '*' {
        let count = scalar_percent_star(value, consumed, true)
        consumed = true
        precision = Some(if count < 0L { 0 } else { count.to_int() })
        index += 1
      } else {
        let mut count = 0
        while index < chars.length() &&
              chars[index] >= '0' &&
              chars[index] <= '9' {
          let digit = chars[index].to_int() - 48
          if count > (2147483647 - digit) / 10 {
            return named_format_error("ValueError", "precision too big")
          }
          count = count * 10 + digit
          index += 1
        }
        precision = Some(count)
      }
    }
    if index < chars.length() &&
      (chars[index] == 'h' || chars[index] == 'l' || chars[index] == 'L') {
      index += 1
    }
    if index >= chars.length() {
      return named_format_error("ValueError", "incomplete format")
    }
    let position = index
    let code = chars[index]
    index += 1
    if consumed {
      return named_format_error(
        "TypeError", "not enough arguments for format string",
      )
    }
    consumed = true
    let field = percent_field_text(
      value,
      code,
      precision,
      width,
      left,
      zero,
      plus,
      space,
      alternate,
      position,
      available - units,
    )
    units += Int64::from_int(field.length())
    if units > available {
      return named_format_error(
        "ResourceError", "percent result exceeds String addressability",
      )
    }
    result.write_string(field)
  }
  if !consumed && (tuple || value is (Number(_) | Integer(_) | Boolean(_))) {
    return named_format_error(
      "TypeError", "not all arguments converted during string formatting",
    )
  }
  result.to_string()
}

///|
// Preserve the D29 entry and its scalar origin without widening NamedFormatValue.
fn format_percent_checked(
  format : String,
  value : NamedFormatValue,
) -> String raise @core.DataError {
  scalar_percent_format(
    format,
    match value {
      PythonFloat(value) => ScalarNumber::Number(value)
      PythonInteger(value) => ScalarNumber::Integer(value)
      NumpyFloat64(value) => ScalarNumber::NumpyFloat64(value)
      NumpyInt64(value) => ScalarNumber::NumpyInt64(value)
    },
  )
}