///|
/// Stable machine-readable report. JSON uses "method" for the HTTP method.
pub fn Report::to_json_report(self : Report) -> String {
  let findings : Array[Json] = self.findings.map(fn(f) {
    {
      "rule_id": f.rule_id.to_json(),
      "method": f.http_method.to_json(),
      "path": f.path.to_json(),
      "old_location": location_json(f.old_location),
      "new_location": location_json(f.new_location),
      "reason": f.reason.to_json(),
    }
  })
  let diagnostics : Array[Json] = self.diagnostics.map(fn(d) {
    {
      "code": d.code.to_json(),
      "severity": d.severity.to_json(),
      "side": d.side.to_json(),
      "method": d.http_method.to_json(),
      "path": d.path.to_json(),
      "location": ToJson::to_json(d.location),
      "reason": d.reason.to_json(),
    }
  })
  canonical({
    "tool": "MoonAPI Check",
    "version": version().to_json(),
    "scope": "openapi-3.0.3-request-v1",
    "status": self.status.to_json(),
    "exit_code": self.exit_code().to_json(),
    "findings": Json::array(findings),
    "diagnostics": Json::array(diagnostics),
  })
}

///|
fn location_json(location : Location?) -> Json {
  match location {
    Some(value) => ToJson::to_json(value)
    None => Json::null()
  }
}

///|
fn location_text(location : Location) -> String {
  let pointer = if location.pointer == "" {
    "(document root)"
  } else {
    location.pointer
  }
  if location.pointer == location.definition_pointer {
    pointer
  } else {
    pointer + " -> definition " + location.definition_pointer
  }
}

///|
/// Human-readable report containing all findings and diagnostics.
pub fn Report::to_text(self : Report) -> String {
  let lines : Array[String] = [
    "MoonAPI Check " + version(),
    "Scope: OpenAPI 3.0.3 request V1 (see support matrix)",
    "Status: " +
    self.status +
    " | breaking changes: " +
    self.findings.length().to_string() +
    " | diagnostics: " +
    self.diagnostics.length().to_string() +
    " | exit: " +
    self.exit_code().to_string(),
  ]
  for f in self.findings {
    lines.push("")
    lines.push("BREAK [" + f.rule_id + "] " + f.http_method + " " + f.path)
    lines.push("  " + f.reason)
    if f.old_location is Some(loc) {
      lines.push("  old: " + location_text(loc))
    }
    if f.new_location is Some(loc) {
      lines.push("  new: " + location_text(loc))
    }
  }
  for d in self.diagnostics {
    lines.push("")
    lines.push(
      "DIAGNOSTIC [" +
      d.code +
      "] " +
      d.side +
      " " +
      d.http_method +
      " " +
      d.path,
    )
    lines.push("  " + d.reason)
    lines.push("  at: " + location_text(d.location))
  }
  if self.exit_code() == 0 {
    lines.push("No breaking change found within the supported V1 profile.")
  }
  lines.join("\n") + "\n"
}

///|
/// Construct a host I/O or execution diagnostic without reimplementing reports in Node.
pub fn execution_error(message : String) -> Report {
  {
    status: "invalid",
    findings: [],
    diagnostics: [
      {
        code: "execution-error",
        severity: "error",
        side: "host",
        http_method: "",
        path: "",
        location: { pointer: "", definition_pointer: "", },
        reason: message,
      },
    ],
  }
}