///|
fn classify_batch_failure(error : RomanReportError) -> BatchFailureKind {
  match error {
    EmptyRomanInput => EmptyInputFailure
    UnsupportedRomanCharacter(_, _) => UnsupportedCharacterFailure
    LowercaseRomanNotAllowed(_, _) => LowercaseNotAllowedFailure
    InvalidRomanGrammar(_, code) => GrammarFailure(code)
    NonCanonicalRoman(_) => NonCanonicalFailure
    RomanReportOutOfRange(_) => OutOfRangeFailure
    InvalidRomanConfiguration(_) => InvalidConfigurationFailure
  }
}

///|
fn is_blank_batch_id(id : String) -> Bool {
  let chars = id.to_array()
  if chars.length() == 0 {
    return true
  }
  for character in chars {
    if !is_outer_whitespace(character) {
      return false
    }
  }
  true
}

///|
fn validate_batch_items(
  items : Array[RomanBatchItem],
) -> Result[Unit, BatchValidationError] {
  for index = 0; index < items.length(); index = index + 1 {
    if is_blank_batch_id(items[index].id) {
      return Err(EmptyBatchItemId(index))
    }
    for previous = 0; previous < index; previous = previous + 1 {
      if items[previous].id == items[index].id {
        return Err(DuplicateBatchItemId(items[index].id))
      }
    }
  }
  Ok(())
}

///|
fn increment_failure_count(
  counts : Array[BatchFailureCount],
  kind : BatchFailureKind,
) -> Unit {
  for index = 0; index < counts.length(); index = index + 1 {
    if counts[index].kind == kind {
      counts[index] = { kind, count: counts[index].count + 1 }
      return
    }
  }
  counts.push({ kind, count: 1 })
}

///|
/// Parse an ID-addressed batch while retaining stable input order.
pub fn process_roman_batch(
  items : Array[RomanBatchItem],
) -> Result[RomanBatchReport, BatchValidationError] {
  match validate_batch_items(items) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let results : Array[RomanBatchResult] = []
  let failure_counts : Array[BatchFailureCount] = []
  let mut succeeded = 0
  let mut failed = 0
  let mut used_unicode_compatibility = 0
  let mut trimmed_outer_whitespace = 0
  for item in items {
    match parse_with_config(item.input, item.config) {
      Ok(report) => {
        succeeded = succeeded + 1
        if report.used_unicode_compatibility {
          used_unicode_compatibility = used_unicode_compatibility + 1
        }
        if report.trimmed_outer_whitespace {
          trimmed_outer_whitespace = trimmed_outer_whitespace + 1
        }
        results.push({
          id: item.id,
          input: item.input,
          config: item.config,
          outcome: BatchParsed(report),
        })
      }
      Err(error) => {
        failed = failed + 1
        increment_failure_count(failure_counts, classify_batch_failure(error))
        results.push({
          id: item.id,
          input: item.input,
          config: item.config,
          outcome: BatchFailed(error),
        })
      }
    }
  }
  Ok({
    results,
    statistics: {
      total: items.length(),
      succeeded,
      failed,
      used_unicode_compatibility,
      trimmed_outer_whitespace,
      failure_counts,
    },
  })
}