///|
fn normalized_substring(text : String, start : Int, end : Int) -> String {
  let chars = text.to_array()
  let selected : Array[Char] = []
  for index = start; index < end; index = index + 1 {
    selected.push(chars[index])
  }
  String::from_array(selected)
}

///|
fn extended_structure_error(
  input : NormalizedRomanInput,
  index : Int,
  code : DiagnosticCode,
) -> RomanReportError {
  InvalidRomanGrammar(normalized_index_span(input, index, 1), code)
}

///|
fn find_extended_group_close(
  input : NormalizedRomanInput,
) -> Result[Int, RomanReportError] {
  let chars = input.normalized.to_array()
  if chars.length() == 0 || chars[0] != '(' {
    return Err(
      InvalidRomanGrammar(input.content_span, MalformedParenthesesCode),
    )
  }
  for index = 1; index < chars.length(); index = index + 1 {
    if chars[index] == '(' {
      return Err(extended_structure_error(input, index, NestedParenthesesCode))
    }
    if chars[index] == ')' {
      if index == 1 {
        return Err(
          InvalidRomanGrammar(
            normalized_index_span(input, 0, 2),
            EmptyParenthesizedGroupCode,
          ),
        )
      }
      return Ok(index)
    }
  }
  Err(InvalidRomanGrammar(input.content_span, MalformedParenthesesCode))
}

///|
fn map_extended_segment_error(
  input : NormalizedRomanInput,
  error : ParseError,
  offset : Int,
) -> RomanReportError {
  match error {
    EmptyInput => EmptyRomanInput
    InvalidCharacter(index, character) =>
      UnsupportedRomanCharacter(
        normalized_index_span(input, offset + index, 1),
        character,
      )
    InvalidRepetition(index, _) =>
      InvalidRomanGrammar(
        normalized_index_span(input, offset + index, 1),
        InvalidRepetitionCode,
      )
    InvalidSubtractivePair(index, _, _) =>
      InvalidRomanGrammar(
        normalized_index_span(input, offset + index, 2),
        InvalidSubtractionCode,
      )
    InvalidOrder(index) =>
      InvalidRomanGrammar(
        normalized_index_span(input, offset + index, 1),
        InvalidOrderCode,
      )
    NonCanonical(expected) => NonCanonicalRoman(expected)
    ParseOutOfRange(value) => RomanReportOutOfRange(value)
  }
}

///|
fn parse_extended_segment(
  input : NormalizedRomanInput,
  start : Int,
  end : Int,
) -> Result[Int, RomanReportError] {
  let text = normalized_substring(input.normalized, start, end)
  match parse(text) {
    Ok(value) => Ok(value)
    Err(error) => Err(map_extended_segment_error(input, error, start))
  }
}

///|
fn reject_unexpected_parenthesis(
  input : NormalizedRomanInput,
) -> RomanReportError? {
  let chars = input.normalized.to_array()
  for index = 0; index < chars.length(); index = index + 1 {
    if chars[index] == '(' || chars[index] == ')' {
      return Some(
        extended_structure_error(input, index, MalformedParenthesesCode),
      )
    }
  }
  None
}

///|
fn parse_extended_candidate(
  input : NormalizedRomanInput,
) -> Result[Int, RomanReportError] {
  let chars = input.normalized.to_array()
  if chars[0] != '(' {
    match reject_unexpected_parenthesis(input) {
      Some(error) => return Err(error)
      None => ()
    }
    return match parse(input.normalized) {
      Ok(value) => Ok(value)
      Err(error) => Err(map_legacy_parse_error(input, error))
    }
  }
  let close = match find_extended_group_close(input) {
    Ok(index) => index
    Err(error) => return Err(error)
  }
  for index = close + 1; index < chars.length(); index = index + 1 {
    if chars[index] == '(' || chars[index] == ')' {
      return Err(
        extended_structure_error(input, index, MalformedParenthesesCode),
      )
    }
  }
  let group_text = normalized_substring(input.normalized, 1, close)
  match interpret_normalized_value(group_text) {
    Some(value) if value > 3999 => return Err(RomanReportOutOfRange(value))
    _ => ()
  }
  let group = match parse_extended_segment(input, 1, close) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let remainder = if close + 1 == chars.length() {
    0
  } else {
    match parse_extended_segment(input, close + 1, chars.length()) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
  }
  let total = group * 1000 + remainder
  if total > 3999999 {
    Err(RomanReportOutOfRange(total))
  } else {
    Ok(total)
  }
}