///|
pub fn render_json(result : DiffResult) -> String {
  @json.to_json(result).stringify(indent=2) + "\n"
}

///|
/// Escapes control characters so user data cannot inject terminal sequences.
pub fn quote_text(value : String) -> String {
  let b = StringBuilder()
  b.write_char('"')
  for ch in value {
    match ch {
      '"' => b.write_string("\\\"")
      '\\' => b.write_string("\\\\")
      '\n' => b.write_string("\\n")
      '\r' => b.write_string("\\r")
      '\t' => b.write_string("\\t")
      _ => {
        let n = ch.to_int()
        if n < 32 ||
          (n >= 127 && n <= 159) ||
          (n >= 0x202A && n <= 0x202E) ||
          (n >= 0x2066 && n <= 0x2069) {
          b.write_string("\\u{\{n.to_string(radix=16)}}")
        } else {
          b.write_char(ch)
        }
      }
    }
  }
  b.write_char('"')
  b.to_string()
}

///|
fn key_label(key : Array[String]) -> String {
  key.map(quote_text).join(", ")
}

///|
fn field_label(fields : Array[FieldValue]) -> String {
  fields
  .map(fn(f) { "\{quote_text(f.column)}=\{quote_text(f.value)}" })
  .join(", ")
}

///|
pub fn render_text(result : DiffResult) -> String {
  let s = result.summary
  let b = StringBuilder()
  b.write_string("MoonRow \{version()}\n")
  b.write_string(
    "before=\{s.before_rows} after=\{s.after_rows} added=\{s.added} removed=\{s.removed} changed=\{s.changed} unchanged=\{s.unchanged} changed_cells=\{s.changed_cells}\n",
  )
  for row in result.added {
    b.write_string("+ [\{key_label(row.key)}] \{field_label(row.fields)}\n")
  }
  for row in result.removed {
    b.write_string("- [\{key_label(row.key)}] \{field_label(row.fields)}\n")
  }
  for row in result.changed {
    b.write_string("~ [\{key_label(row.key)}]\n")
    for c in row.changes {
      b.write_string(
        "  \{quote_text(c.column)}: \{quote_text(c.before)} -> \{quote_text(c.after)}\n",
      )
    }
  }
  b.to_string()
}

///|
// Numeric character references preserve visible text without introducing Markdown or HTML.
fn markdown(value : String) -> String {
  let b = StringBuilder()
  for ch in quote_text(value) {
    let n = ch.to_int()
    if (n >= 33 && n <= 47) ||
      (n >= 58 && n <= 64) ||
      (n >= 91 && n <= 96) ||
      (n >= 123 && n <= 126) {
      b.write_string("&#\{n};")
    } else {
      b.write_char(ch)
    }
  }
  b.to_string()
}

///|
pub fn render_markdown(result : DiffResult) -> String {
  let s = result.summary
  let b = StringBuilder()
  b.write_string("# MoonRow comparison\n\n")
  b.write_string(
    "| Before | After | Added | Removed | Changed | Unchanged | Changed cells |\n| --- | --- | --- | --- | --- | --- | --- |\n",
  )
  b.write_string(
    "| \{s.before_rows} | \{s.after_rows} | \{s.added} | \{s.removed} | \{s.changed} | \{s.unchanged} | \{s.changed_cells} |\n\n",
  )
  b.write_string(
    "| Status | Key | Column | Before | After |\n| --- | --- | --- | --- | --- |\n",
  )
  for row in result.added {
    for f in row.fields {
      b.write_string(
        "| added | \{markdown(key_label(row.key))} | \{markdown(f.column)} | — | \{markdown(f.value)} |\n",
      )
    }
  }
  for row in result.removed {
    for f in row.fields {
      b.write_string(
        "| removed | \{markdown(key_label(row.key))} | \{markdown(f.column)} | \{markdown(f.value)} | — |\n",
      )
    }
  }
  for row in result.changed {
    for c in row.changes {
      b.write_string(
        "| changed | \{markdown(key_label(row.key))} | \{markdown(c.column)} | \{markdown(c.before)} | \{markdown(c.after)} |\n",
      )
    }
  }
  b.to_string()
}

///|
pub fn render_diagnostic(d : Diagnostic) -> String {
  "[\{d.code}] \{d.side} record=\{d.record} line=\{d.line} column=\{d.column}: \{quote_text(d.message)}\n"
}