///|
fn append_document_slice(
  output : String,
  chars : Array[Char],
  start : Int,
  end : Int,
) -> String {
  if start >= end {
    output
  } else {
    output + scan_substring(chars, start, end)
  }
}

///|
/// Rewrite accepted scanner matches while preserving replacement evidence.
pub fn rewrite_roman_text(
  text : String,
  config : RewriteConfig,
) -> Result[RomanRewriteReport, RomanRewriteError] {
  let scan_report = match scan_roman_text(text, config.scan_config) {
    Ok(report) => report
    Err(error) => return Err(RewriteScanConfigurationFailed(error))
  }
  let chars = text.to_array()
  let replacements : Array[RomanReplacement] = []
  let skipped : Array[RomanRewriteSkipped] = []
  let mut output = ""
  let mut source_cursor = 0
  let mut output_cursor = 0
  for matched in scan_report.matches {
    output = append_document_slice(
      output,
      chars,
      source_cursor,
      matched.span.start,
    )
    output_cursor = output_cursor + matched.span.start - source_cursor
    match format_with_config(matched.report.value, config.target_format) {
      Ok(replacement_text) => {
        let replacement_length = replacement_text.to_array().length()
        let output_span : SourceSpan = {
          start: output_cursor,
          end: output_cursor + replacement_length,
        }
        output = output + replacement_text
        replacements.push({
          source_span: matched.span,
          output_span,
          source_text: matched.source_text,
          replacement_text,
          value: matched.report.value,
        })
        output_cursor = output_span.end
      }
      Err(error) =>
        match config.failure_policy {
          FailOnUnformattableCandidate =>
            return Err(RewriteFormatFailed(matched.span, error))
          KeepUnformattableCandidate => {
            output = output + matched.source_text
            output_cursor = output_cursor +
              matched.source_text.to_array().length()
            skipped.push({
              source_span: matched.span,
              source_text: matched.source_text,
              value: matched.report.value,
              error,
            })
          }
        }
    }
    source_cursor = matched.span.end
  }
  output = append_document_slice(output, chars, source_cursor, chars.length())
  Ok({ original: text, output, replacements, skipped, scan_report })
}