///|
pub fn render_imports_diff_json(report : ImportsComparison) -> String {
  let root : Map[String, Json] = Map([])
  root.set("format", Json::string("moonsize-imports-diff"))
  root.set("schema_version", int_json(1))
  root.set(
    "comparison_key",
    Json::array([
      Json::string("module_name"),
      Json::string("field"),
      Json::string("kind"),
    ]),
  )
  root.set("comparison_scope", Json::string(report.comparison_scope))
  root.set("type_compatibility", Json::string("not_evaluated"))
  root.set("inventory_changed", Json::boolean(report.inventory_changed))
  root.set("delta_direction", Json::string("current - baseline"))
  root.set("baseline", imports_json(report.baseline))
  root.set("current", imports_json(report.current))
  let c = report.counts
  root.set(
    "counts",
    Json::object({
      "baseline_entry_count": int_json(c.baseline_entry_count),
      "current_entry_count": int_json(c.current_entry_count),
      "overlap_count": int_json(c.overlap_count),
      "removed_count": int_json(c.removed_count),
      "added_count": int_json(c.added_count),
      "count_delta": int_json(c.count_delta),
    }),
  )
  root.set("delta", imports_ledger_json(report.delta))
  root.set(
    "groups",
    Json::array(
      report.groups.map(fn(group) {
        Json::object({
          "module": Json::string(group.module_name),
          "field": Json::string(group.field),
          "kind": Json::string(group.kind),
          "status": Json::string(group.status),
          "baseline_ordinals": Json::array(
            group.baseline_ordinals.map(int_json),
          ),
          "current_ordinals": Json::array(group.current_ordinals.map(int_json)),
          "baseline_count": int_json(group.baseline_count),
          "current_count": int_json(group.current_count),
          "overlap_count": int_json(group.overlap_count),
          "removed_count": int_json(group.removed_count),
          "added_count": int_json(group.added_count),
          "count_delta": int_json(group.count_delta),
          "baseline_entry_bytes": int_json(group.baseline_entry_bytes),
          "current_entry_bytes": int_json(group.current_entry_bytes),
          "entry_bytes_delta": int_json(group.entry_bytes_delta),
        })
      }),
    ),
  )
  Json::object(root).stringify(indent=2) + "\n"
}

///|
fn imports_diff_ledger_text(
  out : StringBuilder,
  label : String,
  s : ImportsLedger,
) -> Unit {
  out.write_string(
    label +
    " import section bytes: \{s.import_section_total_bytes} B = header \{s.section_header_bytes} B + count prefix \{s.count_prefix_bytes} B + declarations \{s.entry_bytes} B\n",
  )
  out.write_string(
    label +
    " declaration bytes: \{s.entry_bytes} B = module names \{s.module_name_encoding_bytes} B + field names \{s.field_name_encoding_bytes} B + descriptors \{s.descriptor_bytes} B\n",
  )
  out.write_string(
    label +
    " module-name bytes: \{s.module_name_encoding_bytes} B = prefixes \{s.module_length_prefix_bytes} B + UTF-8 \{s.module_utf8_bytes} B\n",
  )
  out.write_string(
    label +
    " field-name bytes: \{s.field_name_encoding_bytes} B = prefixes \{s.field_length_prefix_bytes} B + UTF-8 \{s.field_utf8_bytes} B\n",
  )
}

///|
pub fn render_imports_diff(report : ImportsComparison, top : Int) -> String {
  let out = StringBuilder()
  let c = report.counts
  out.write_string("HOST IMPORT INVENTORY COMPARISON\n\n")
  out.write_string(
    "Baseline: \{report.baseline.file_size} B; SHA-256 \{report.baseline.sha256}\n",
  )
  out.write_string(
    "Current: \{report.current.file_size} B; SHA-256 \{report.current.sha256}\n",
  )
  out.write_string(
    "Declarations: baseline \{c.baseline_entry_count}, current \{c.current_entry_count}; overlap \{c.overlap_count}, removed \{c.removed_count}, added \{c.added_count}; delta \{c.count_delta}\n",
  )
  if !report.inventory_changed {
    out.write_string(
      "No changes to names, kinds or declaration counts; type compatibility is not evaluated.\n",
    )
  } else {
    out.write_string(
      "Inventory counts changed; type compatibility is not evaluated.\n",
    )
  }
  out.write_string(
    "\nENCODING LEDGERS (delta = current - baseline; not file ranges)\n",
  )
  imports_diff_ledger_text(out, "Baseline", report.baseline.ledger)
  imports_diff_ledger_text(out, "Current", report.current.ledger)
  imports_diff_ledger_text(out, "Delta", report.delta)
  for
    (label, side) in [
      ("Baseline", report.baseline),
      ("Current", report.current),
    ] {
    out.write_string(
      label +
      " import section: " +
      side.section.map(imports_range_text).unwrap_or("absent") +
      "\n",
    )
  }
  let shown = top.max(0).min(report.groups.length())
  out.write_string(
    "\nIMPORT GROUPS: showing \{shown} of \{report.groups.length()}\n",
  )
  out.write_string(
    "MODULE  FIELD  KIND  STATUS  BEFORE  AFTER  OVERLAP  REMOVED  ADDED  COUNT DELTA  ENCODED DELTA B\n",
  )
  for i in 0..