///|
pub fn Diagnostic::to_text(self : Diagnostic) -> String {
  diagnostic_prefix(self.severity, self.code.name(), self.offset) + self.message
}

///|
pub fn Recommendation::to_text(self : Recommendation) -> String {
  let buf = StringBuilder(size_hint=self.message.length() + 48)
  buf.write_string(self.severity.name())
  buf.write_string(": ")
  buf.write_string(self.code.name())
  match self.parameter {
    Some(name) => {
      buf.write_string(" [")
      buf.write_string(name)
      buf.write_string("]")
    }
    None => ()
  }
  match self.offset {
    Some(offset) => {
      buf.write_string(" @")
      buf.write_string(offset.to_string())
    }
    None => ()
  }
  buf.write_string(" - ")
  buf.write_string(self.message)
  match self.value {
    Some(value) => {
      buf.write_string(" => ")
      buf.write_string(value)
    }
    None => ()
  }
  buf.to_string()
}

///|
pub fn FilenameReport::to_text(self : FilenameReport) -> String {
  let buf = StringBuilder(
    size_hint=self.original.length() + self.value.length() + 64,
  )
  buf.write_string("filename: ")
  buf.write_string(self.original)
  buf.write_string(" -> ")
  buf.write_string(self.value)
  if self.changed {
    buf.write_string(" (changed)")
  } else {
    buf.write_string(" (unchanged)")
  }
  for d in self.diagnostics {
    buf.write_char('\n')
    buf.write_string("  ")
    buf.write_string(d.to_text())
  }
  buf.to_string()
}

///|
pub fn HeaderReview::to_text(self : HeaderReview) -> String {
  let buf = StringBuilder(size_hint=self.header.length() + 160)
  buf.write_string("header: ")
  buf.write_string(self.header)
  buf.write_char('\n')
  buf.write_string("status: ")
  buf.write_string(self.status.name())
  match self.disposition_kind() {
    Some(kind) => {
      buf.write_char('\n')
      buf.write_string("kind: ")
      buf.write_string(kind)
    }
    None => ()
  }
  match self.filename {
    Some(report) => {
      buf.write_char('\n')
      buf.write_string(report.to_text())
    }
    None => ()
  }
  if self.recommendations.length() > 0 {
    buf.write_char('\n')
    buf.write_string("recommendations:")
    for r in self.recommendations {
      buf.write_char('\n')
      buf.write_string("- ")
      buf.write_string(r.to_text())
    }
  }
  buf.to_string()
}

///|
pub fn AuditSummary::to_text(self : AuditSummary) -> String {
  "total=" +
  self.total.to_string() +
  ", accepted=" +
  self.accepted.to_string() +
  ", needs_attention=" +
  self.needs_attention.to_string() +
  ", rejected=" +
  self.rejected.to_string() +
  ", warnings=" +
  self.warnings.to_string() +
  ", errors=" +
  self.errors.to_string() +
  ", filenames_changed=" +
  self.filenames_changed.to_string()
}

///|
pub fn reviews_to_text(reviews : Array[HeaderReview]) -> String {
  let buf = StringBuilder(size_hint=256)
  buf.write_string(summarize_reviews(reviews).to_text())
  for review in reviews {
    buf.write_string("\n\n")
    buf.write_string(review.to_text())
  }
  buf.to_string()
}

///|
pub fn HeaderReview::to_markdown_row(self : HeaderReview) -> String {
  let kind = match self.disposition_kind() {
    Some(value) => value
    None => ""
  }
  let filename = match self.filename_value() {
    Some(value) => value
    None => ""
  }
  "| " +
  markdown_escape(self.status.name()) +
  " | " +
  markdown_escape(kind) +
  " | " +
  markdown_escape(filename) +
  " | " +
  self.warning_count().to_string() +
  " | " +
  self.error_count().to_string() +
  " |"
}

///|
pub fn reviews_to_markdown(reviews : Array[HeaderReview]) -> String {
  let buf = StringBuilder(size_hint=256)
  buf.write_string("| Status | Kind | Safe filename | Warnings | Errors |\n")
  buf.write_string("| --- | --- | --- | ---: | ---: |")
  for review in reviews {
    buf.write_char('\n')
    buf.write_string(review.to_markdown_row())
  }
  buf.to_string()
}

///|
pub fn Diagnostic::to_json(self : Diagnostic) -> String {
  let buf = StringBuilder(size_hint=self.message.length() + 80)
  buf.write_string("{\"code\":")
  write_json_string(buf, self.code.name())
  buf.write_string(",\"severity\":")
  write_json_string(buf, self.severity.name())
  buf.write_string(",\"message\":")
  write_json_string(buf, self.message)
  buf.write_string(",\"offset\":")
  write_json_int_option(buf, self.offset)
  buf.write_char('}')
  buf.to_string()
}

///|
pub fn Recommendation::to_json(self : Recommendation) -> String {
  let buf = StringBuilder(size_hint=self.message.length() + 128)
  buf.write_string("{\"code\":")
  write_json_string(buf, self.code.name())
  buf.write_string(",\"severity\":")
  write_json_string(buf, self.severity.name())
  buf.write_string(",\"message\":")
  write_json_string(buf, self.message)
  buf.write_string(",\"parameter\":")
  write_json_string_option(buf, self.parameter)
  buf.write_string(",\"value\":")
  write_json_string_option(buf, self.value)
  buf.write_string(",\"offset\":")
  write_json_int_option(buf, self.offset)
  buf.write_char('}')
  buf.to_string()
}

///|
pub fn FilenameReport::to_json(self : FilenameReport) -> String {
  let buf = StringBuilder(
    size_hint=self.original.length() + self.value.length() + 128,
  )
  buf.write_string("{\"original\":")
  write_json_string(buf, self.original)
  buf.write_string(",\"value\":")
  write_json_string(buf, self.value)
  buf.write_string(",\"changed\":")
  write_json_bool(buf, self.changed)
  buf.write_string(",\"diagnostics\":")
  write_diagnostics_json(buf, self.diagnostics)
  buf.write_char('}')
  buf.to_string()
}

///|
pub fn HeaderReview::to_json(self : HeaderReview) -> String {
  let buf = StringBuilder(size_hint=self.header.length() + 256)
  buf.write_string("{\"header\":")
  write_json_string(buf, self.header)
  buf.write_string(",\"status\":")
  write_json_string(buf, self.status.name())
  buf.write_string(",\"kind\":")
  write_json_string_option(buf, self.disposition_kind())
  buf.write_string(",\"filename\":")
  match self.filename {
    Some(report) => buf.write_string(report.to_json())
    None => buf.write_string("null")
  }
  buf.write_string(",\"recommendations\":")
  write_recommendations_json(buf, self.recommendations)
  buf.write_char('}')
  buf.to_string()
}

///|
pub fn reviews_to_json(reviews : Array[HeaderReview]) -> String {
  let buf = StringBuilder(size_hint=256)
  buf.write_char('[')
  let mut first = true
  for review in reviews {
    if first {
      first = false
    } else {
      buf.write_char(',')
    }
    buf.write_string(review.to_json())
  }
  buf.write_char(']')
  buf.to_string()
}

///|
fn diagnostic_prefix(
  severity : Severity,
  code : String,
  offset : Int?,
) -> String {
  let buf = StringBuilder(size_hint=32)
  buf.write_string(severity.name())
  buf.write_string(": ")
  buf.write_string(code)
  match offset {
    Some(value) => {
      buf.write_string(" @")
      buf.write_string(value.to_string())
    }
    None => ()
  }
  buf.write_string(" - ")
  buf.to_string()
}

///|
fn markdown_escape(value : String) -> String {
  let buf = StringBuilder(size_hint=value.length())
  for c in value.iter() {
    if c == '|' {
      buf.write_string("\\|")
    } else if c == '\n' || c == '\r' {
      buf.write_char(' ')
    } else {
      buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
fn write_json_bool(buf : StringBuilder, value : Bool) -> Unit {
  if value {
    buf.write_string("true")
  } else {
    buf.write_string("false")
  }
}

///|
fn write_json_int_option(buf : StringBuilder, value : Int?) -> Unit {
  match value {
    Some(n) => buf.write_string(n.to_string())
    None => buf.write_string("null")
  }
}

///|
fn write_json_string_option(buf : StringBuilder, value : String?) -> Unit {
  match value {
    Some(s) => write_json_string(buf, s)
    None => buf.write_string("null")
  }
}

///|
fn write_diagnostics_json(
  buf : StringBuilder,
  diagnostics : Array[Diagnostic],
) -> Unit {
  buf.write_char('[')
  let mut first = true
  for d in diagnostics {
    if first {
      first = false
    } else {
      buf.write_char(',')
    }
    buf.write_string(d.to_json())
  }
  buf.write_char(']')
}

///|
fn write_recommendations_json(
  buf : StringBuilder,
  recommendations : Array[Recommendation],
) -> Unit {
  buf.write_char('[')
  let mut first = true
  for r in recommendations {
    if first {
      first = false
    } else {
      buf.write_char(',')
    }
    buf.write_string(r.to_json())
  }
  buf.write_char(']')
}

///|
fn write_json_string(buf : StringBuilder, value : String) -> Unit {
  buf.write_char('"')
  for c in value.iter() {
    if c == '"' {
      buf.write_string("\\\"")
    } else if c == '\\' {
      buf.write_string("\\\\")
    } else if c == '\n' {
      buf.write_string("\\n")
    } else if c == '\r' {
      buf.write_string("\\r")
    } else if c == '\t' {
      buf.write_string("\\t")
    } else if is_ctl(c) {
      buf.write_string("\\u")
      append_json_hex4(buf, c.to_int())
    } else {
      buf.write_char(c)
    }
  }
  buf.write_char('"')
}

///|
fn append_json_hex4(buf : StringBuilder, value : Int) -> Unit {
  let upper = "0123456789ABCDEF"
  buf.write_char(char_at(upper, (value >> 12) & 15))
  buf.write_char(char_at(upper, (value >> 8) & 15))
  buf.write_char(char_at(upper, (value >> 4) & 15))
  buf.write_char(char_at(upper, value & 15))
}