///|
fn status_label(status : Status) -> String {
  match status {
    Pass => "pass"
    Divergent => "divergent"
    Inconclusive => "inconclusive"
  }
}

///|
/// Process status contract for CI: 0 pass, 1 known divergence, 2 incomplete data.
pub fn report_exit_code(report : ParityReport) -> Int {
  match report.status {
    Pass => 0
    Divergent => 1
    Inconclusive => 2
  }
}

///|
pub fn report_to_json(report : ParityReport) -> String {
  let reference_target = match report.reference_target {
    Some(target) => Json::string(target)
    None => Json::null()
  }
  Json::object(
    Map::from_array([
      ("scenario", Json::string(report.scenario)),
      ("status", Json::string(status_label(report.status))),
      ("diagnostics", @json.to_json(report.diagnostics)),
      ("reference_target", reference_target),
      ("compared_targets", @json.to_json(report.compared_targets)),
      ("missing_targets", @json.to_json(report.missing_targets)),
      ("unexpected_targets", @json.to_json(report.unexpected_targets)),
      ("duplicate_targets", @json.to_json(report.duplicate_targets)),
      ("differences", @json.to_json(report.differences)),
    ]),
  ).stringify()
}

///|
pub fn report_to_markdown(report : ParityReport) -> String {
  let mut output = "# MoonBit target parity: " + report.scenario + "\n\n"
  output += "Status: **" + status_label(report.status) + "**\n\n"
  for diagnostic in report.diagnostics {
    output += "Diagnostic: " + diagnostic + "\n\n"
  }
  match report.reference_target {
    Some(target) => output += "Reference target: `" + target + "`\n\n"
    None => output += "Reference target: unavailable\n\n"
  }
  if report.missing_targets.length() > 0 {
    output += "Missing targets: " + report.missing_targets.join(", ") + "\n\n"
  }
  if report.unexpected_targets.length() > 0 {
    output += "Undeclared targets: " +
      report.unexpected_targets.join(", ") +
      "\n\n"
  }
  if report.duplicate_targets.length() > 0 {
    output += "Duplicate targets: " +
      report.duplicate_targets.join(", ") +
      "\n\n"
  }
  if report.differences.length() > 0 {
    output += "| Target | Check | Field | JSON path | Reference | Observed |\n"
    output += "| --- | --- | --- | --- | --- | --- |\n"
    for difference in report.differences {
      output += "| `" +
        difference.target +
        "` | `" +
        difference.kind +
        "` | `" +
        difference.field +
        "` | `" +
        difference.path.unwrap_or("") +
        "` | `" +
        escape_cell(difference.reference) +
        "` | `" +
        escape_cell(difference.observed) +
        "` |\n"
    }
    for difference in report.differences {
      if difference.field == "stdout" || difference.field == "stderr" {
        output += "\n
Text diff for `" + difference.target + "` / `" + difference.field + "`\n\n
"
        output += escape_html(
          unified_text_diff(difference.reference, difference.observed),
        )
        output += "
\n
\n" } } } else if report.status is Status::Pass { output += "All declared targets produced matching observable results.\n" } output } ///| pub fn suite_report_to_json(report : SuiteReport) -> String { let mut reports_json = "[" for i, item in report.reports { if i > 0 { reports_json += "," } reports_json += report_to_json(item) } reports_json += "]" "{\"status\":" + Json::string(status_label(report.status)).stringify() + ",\"scenario_count\":" + report.scenario_count.to_string() + ",\"passed\":" + report.passed.to_string() + ",\"divergent\":" + report.divergent.to_string() + ",\"inconclusive\":" + report.inconclusive.to_string() + ",\"reports\":" + reports_json + "}" } ///| /// Render a self-contained, dependency-free HTML summary for browser review. pub fn suite_report_to_html(report : SuiteReport) -> String { let mut output = "" output += "" output += "MoonBit Target Parity

MoonBit Target Parity

" output += "

" + status_label(report.status) + "

" output += "

Scenarios: " + report.scenario_count.to_string() + " · Passed: " + report.passed.to_string() + " · Divergent: " + report.divergent.to_string() + " · Inconclusive: " + report.inconclusive.to_string() + "

" for item in report.reports { output += "

" + escape_html(item.scenario) + "

" output += "

" + status_label(item.status) + "

" if item.reference_target is Some(target) { output += "

Reference target: " + escape_html(target) + "

" } if item.diagnostics.length() > 0 { output += "
    " for diagnostic in item.diagnostics { output += "
  • " + escape_html(diagnostic) + "
  • " } output += "
" } if item.missing_targets.length() > 0 { output += "

Missing targets: " + escape_html(item.missing_targets.join(", ")) + "

" } if item.differences.length() > 0 { output += "" for difference in item.differences { output += "" output += "" output += "" output += "" output += "" output += "" } output += "
TargetCheckFieldPathExpected/referenceObserved
" + escape_html(difference.target) + "" + escape_html(difference.kind) + "" + escape_html(difference.field) + "" + escape_html(difference.path.unwrap_or("")) + "
" +
          escape_html(difference.reference) +
          "
" +
          escape_html(difference.observed) +
          "
" } output += "
" } output + "" } ///| pub fn suite_report_exit_code(report : SuiteReport) -> Int { match report.status { Pass => 0 Divergent => 1 Inconclusive => 2 } } ///| fn escape_cell(value : String) -> String { value .replace_all(old="|", new="\\|") .replace_all(old="\n", new="
") .replace_all(old="\r", new="") } ///| fn escape_html(value : String) -> String { value .replace_all(old="&", new="&") .replace_all(old="<", new="<") .replace_all(old=">", new=">") .replace_all(old="\"", new=""") .replace_all(old="'", new="'") }