///|
fn roman_rewrite_edit_id(index : Int) -> String {
  "rewrite-\{index}"
}

///|
fn roman_lint_edit_id(index : Int) -> String {
  "lint-\{index}"
}

///|
fn roman_lint_edit_reason(code : DiagnosticCode) -> RomanEditReason {
  match code {
    NonCanonicalCode => RomanCanonicalizationEdit
    UppercaseRequiredCode | LowercaseRequiredCode | LowercaseNotAllowedCode =>
      RomanCaseNormalizationEdit
    UnicodeSourceNotAllowedCode => RomanUnicodeExpansionEdit
    _ => RomanLintDiagnosticEdit(code)
  }
}

///|
fn first_roman_lint_diagnostic_code(lint : RomanLintReport) -> DiagnosticCode? {
  if lint.diagnostics.length() == 0 {
    None
  } else {
    Some(lint.diagnostics[0].code)
  }
}

///|
fn roman_lint_numeric_value(lint : RomanLintReport) -> Int? {
  match lint.analysis.status {
    AnalysisAccepted(report) => Some(report.value)
    AnalysisRejected(_) => None
  }
}

///|
fn parsed_roman_numeric_value(input : String, config : ParseConfig) -> Int? {
  match parse_with_config(input, config) {
    Ok(report) => Some(report.value)
    Err(NonCanonicalRoman(expected)) =>
      match parse_with_config(expected, config) {
        Ok(report) => Some(report.value)
        Err(_) => None
      }
    _ => None
  }
}

///|
fn recover_rejected_roman_numeric_value(
  rejection : RomanDocumentRejectedCandidate,
  policy : RomanLintPolicy,
) -> Int? {
  parsed_roman_numeric_value(rejection.source_text, {
    ..policy.parse_config,
    accept_lowercase: true,
    accept_unicode: true,
    trim_outer_whitespace: false,
  })
}

///|
fn format_complete_roman_lint_target(
  value : Int,
  policy : RomanLintPolicy,
) -> String? {
  match
    format_with_config(value, {
      mode: policy.parse_config.mode,
      letter_case: lint_output_case(policy.case_requirement),
    }) {
    Ok(replacement) => Some(replacement)
    Err(_) => None
  }
}

///|
fn conforming_roman_lint_replacement(
  source : String,
  replacement : String,
  policy : RomanLintPolicy,
) -> String? {
  if replacement == source {
    return None
  }
  match lint_roman_document(replacement, policy) {
    Ok(document) => {
      let replacement_length = replacement.to_array().length()
      if document.conforms &&
        document.candidates.length() == 1 &&
        document.rejected_candidates.length() == 0 &&
        document.candidates[0].span == { start: 0, end: replacement_length } &&
        document.candidates[0].source_text == replacement &&
        document.candidates[0].lint.conforms {
        Some(replacement)
      } else {
        None
      }
    }
    Err(_) => None
  }
}

///|
fn identify_roman_lint_edits(
  edits : Array[RomanTextEdit],
) -> Array[RomanTextEdit] {
  let identified : Array[RomanTextEdit] = []
  for edit in sort_roman_edits_by_source(edits) {
    identified.push({ ..edit, id: roman_lint_edit_id(identified.length()) })
  }
  identified
}

///|
/// Build a validated plan from replacements retained by a rewrite report.
pub fn roman_edit_plan_from_rewrite(
  report : RomanRewriteReport,
  rewrite_config : RewriteConfig,
  edit_config : RomanEditPlanConfig,
) -> Result[RomanEditPlan, RomanEditError] {
  let trusted = match rewrite_roman_text(report.original, rewrite_config) {
    Ok(trusted) => trusted
    Err(error) => return Err(RomanEditRewriteFailed(error))
  }
  let edits : Array[RomanTextEdit] = []
  for replacement in trusted.replacements {
    if replacement.source_text != replacement.replacement_text {
      edits.push({
        id: roman_rewrite_edit_id(edits.length()),
        span: replacement.source_span,
        expected_source: replacement.source_text,
        replacement: replacement.replacement_text,
        numeric_value: Some(replacement.value),
        reason: RomanCanonicalizationEdit,
        source_diagnostic_code: None,
      })
    }
  }
  validate_roman_edit_plan(trusted.original, edits, edit_config)
}

///|
/// Build a validated plan from deterministic document-lint replacements.
pub fn roman_edit_plan_from_document_lint(
  report : RomanDocumentLintReport,
  config : RomanEditPlanConfig,
) -> Result[RomanEditPlan, RomanEditError] {
  let trusted = match lint_roman_document(report.original, report.policy) {
    Ok(trusted) => trusted
    Err(error) => return Err(RomanEditDocumentLintFailed(error))
  }
  let edits : Array[RomanTextEdit] = []
  for candidate in trusted.candidates {
    match
      (
        candidate.lint.replacement,
        first_roman_lint_diagnostic_code(candidate.lint),
      ) {
      (Some(proposed), Some(diagnostic_code)) =>
        match
          conforming_roman_lint_replacement(
            candidate.source_text,
            proposed,
            trusted.policy,
          ) {
          Some(replacement) =>
            edits.push({
              id: "candidate-\{edits.length()}",
              span: candidate.span,
              expected_source: candidate.source_text,
              replacement,
              numeric_value: roman_lint_numeric_value(candidate.lint),
              reason: roman_lint_edit_reason(diagnostic_code),
              source_diagnostic_code: Some(diagnostic_code),
            })
          None => ()
        }
      _ => ()
    }
  }
  for rejection in trusted.rejected_candidates {
    match recover_rejected_roman_numeric_value(rejection, trusted.policy) {
      Some(value) =>
        match format_complete_roman_lint_target(value, trusted.policy) {
          Some(proposed) =>
            match
              conforming_roman_lint_replacement(
                rejection.source_text,
                proposed,
                trusted.policy,
              ) {
              Some(replacement) => {
                let code = rejection.diagnostic.code
                edits.push({
                  id: "rejected-\{edits.length()}",
                  span: rejection.span,
                  expected_source: rejection.source_text,
                  replacement,
                  numeric_value: Some(value),
                  reason: roman_lint_edit_reason(code),
                  source_diagnostic_code: Some(code),
                })
              }
              None => ()
            }
          None => ()
        }
      None => ()
    }
  }
  match
    validate_roman_edit_plan(
      trusted.original,
      identify_roman_lint_edits(edits),
      config,
    ) {
    Err(error) => Err(error)
    Ok(plan) => Ok({ ..plan, diagnostics: trusted.diagnostics })
  }
}