///|
/// Renderers: the same facts as text, JSON and Markdown.
///
/// The JSON lines are the machine readable contract used by the command line and
/// by the external verification script, so they are written by hand here rather
/// than through a generic serializer: the field order is fixed, the numbers are
/// integers or fixed precision text, and nothing depends on a hash order. A
/// document always ends with exactly one newline.

///|
/// Quote a JSON string, escaping the characters that must be escaped.
pub fn json_string(text : String) -> String {
  let builder = StringBuilder(size_hint=text.length() + 2)
  builder.write_char('"')
  for ch in text {
    if ch == '"' {
      builder.write_string("\\\"")
    } else if ch == '\\' {
      builder.write_string("\\\\")
    } else if ch == '\n' {
      builder.write_string("\\n")
    } else if ch == '\r' {
      builder.write_string("\\r")
    } else if ch == '\t' {
      builder.write_string("\\t")
    } else if ch.to_int() < 32 {
      builder.write_string("\\u00")
      builder.write_string(two_hex(ch.to_int()))
    } else {
      builder.write_char(ch)
    }
  }
  builder.write_char('"')
  builder.to_string()
}

///|
fn two_hex(value : Int32) -> String {
  let digits : Array[Char] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
  ]
  let safe = if value < 0 { 0 } else { value }
  digits[safe / 16 % 16].to_string() + digits[safe % 16].to_string()
}

///|
/// A ratio expressed in parts per million as fixed four decimal text, e.g.
/// `0.9992`. Integer arithmetic only, so the same input prints the same text on
/// every backend.
pub fn ppm_text(ppm : Int32) -> String {
  let clamped = if ppm < 0 {
    0
  } else if ppm > 1_000_000 {
    1_000_000
  } else {
    ppm
  }
  let whole = clamped / 1_000_000
  let fraction = clamped % 1_000_000
  let four = (fraction + 50) / 100
  if four >= 10_000 {
    whole.to_string() + ".0000"
  } else {
    let text = four.to_string()
    let padding = if text.length() >= 4 {
      ""
    } else if text.length() == 3 {
      "0"
    } else if text.length() == 2 {
      "00"
    } else if text.length() == 1 {
      "000"
    } else {
      "0000"
    }
    whole.to_string() + "." + padding + text
  }
}

///|
/// One JSON line describing a signature.
pub fn render_signature_json(
  signature : FileSignature,
  path : String,
  content_sha256_hex : String,
  content_xxh64_hex : String,
) -> String {
  "{\"kind\":\"sign\",\"path\":" +
  json_string(path) +
  ",\"total_length\":" +
  signature.total_length.to_string() +
  ",\"block_size\":" +
  signature.block_size.to_string() +
  ",\"strong_len\":" +
  signature.strong_len.to_string() +
  ",\"block_count\":" +
  signature.blocks.length().to_string() +
  ",\"signature_bytes\":" +
  signature.encode().length().to_string() +
  ",\"signature_sha256_hex\":" +
  json_string(signature.digest_hex()) +
  ",\"sha256_hex\":" +
  json_string(content_sha256_hex) +
  ",\"xxh64_hex\":" +
  json_string(content_xxh64_hex) +
  "}"
}

///|
/// One JSON line describing the plan for one transfer.
pub fn render_delta_json(
  plan : TransferPlan,
  old_path : String,
  new_path : String,
) -> String {
  "{\"kind\":\"delta\",\"old\":" +
  json_string(old_path) +
  ",\"new\":" +
  json_string(new_path) +
  ",\"new_length\":" +
  plan.new_length().to_string() +
  ",\"op_count\":" +
  plan.delta().op_count().to_string() +
  ",\"copy_ops\":" +
  plan.copy_ops().to_string() +
  ",\"copy_blocks\":" +
  plan.delta().copied_blocks().to_string() +
  ",\"copied_bytes\":" +
  plan.copied_bytes().to_string() +
  ",\"literal_bytes\":" +
  plan.literal_bytes().to_string() +
  ",\"delta_bytes\":" +
  plan.delta_bytes().length().to_string() +
  ",\"delta_sha256_hex\":" +
  json_string(plan.delta().digest_hex()) +
  ",\"savings_ratio\":" +
  ppm_text(plan.savings_ppm()) +
  "}"
}

///|
/// One JSON line describing an applied delta.
pub fn render_apply_json(result : ApplyResult) -> String {
  "{\"kind\":\"apply\",\"ok\":true,\"bytes_written\":" +
  result.bytes_written.to_string() +
  ",\"blocks_verified\":" +
  result.blocks_verified.to_string() +
  ",\"ops_applied\":" +
  result.ops_applied.to_string() +
  ",\"sha256_hex\":" +
  json_string(result.digest_hex) +
  "}"
}

///|
/// One JSON line describing one snapshot ingested into a chunk store.
pub fn render_chunk_json(report : SnapshotReport, path : String) -> String {
  "{\"kind\":\"chunk\",\"path\":" +
  json_string(path) +
  ",\"length\":" +
  report.total_bytes.to_string() +
  ",\"chunk_count\":" +
  report.chunk_count.to_string() +
  ",\"unique_chunks\":" +
  report.unique_chunks.to_string() +
  ",\"new_chunks\":" +
  report.new_chunks.to_string() +
  ",\"reused_chunks\":" +
  report.reused_chunks.to_string() +
  ",\"stored_bytes\":" +
  report.stored_bytes.to_string() +
  ",\"saved_bytes\":" +
  report.saved_bytes.to_string() +
  ",\"dedup_ratio\":" +
  ppm_text(report.dedup_ppm) +
  ",\"manifest_bytes\":" +
  report.manifest_bytes.to_string() +
  ",\"root_hex\":" +
  json_string(report.root_hex) +
  "}"
}

///|
/// One JSON line describing a failure. The command line prints this instead of a
/// stack trace, and exits non zero.
pub fn render_error_json(code : String, message : String) -> String {
  "{\"kind\":\"error\",\"code\":" +
  json_string(code) +
  ",\"message\":" +
  json_string(message) +
  "}"
}

///|
/// A human readable summary of one transfer, for a terminal or a log line.
pub fn render_plan_text(plan : TransferPlan) -> String {
  "new_length: " +
  plan.new_length().to_string() +
  "\ncopy_ops: " +
  plan.copy_ops().to_string() +
  "\ncopied_bytes: " +
  plan.copied_bytes().to_string() +
  "\nliteral_bytes: " +
  plan.literal_bytes().to_string() +
  "\ndelta_bytes: " +
  plan.delta_bytes().length().to_string() +
  "\nsavings_ratio: " +
  ppm_text(plan.savings_ppm()) +
  "\n"
}

///|
/// The same summary as a Markdown table, for a pull request comment or a report.
pub fn render_plan_markdown(plan : TransferPlan) -> String {
  let builder = StringBuilder()
  builder.write_string("| 指标 | 值 |\n| --- | --- |\n")
  builder.write_string(
    "| 新数据长度 | " + plan.new_length().to_string() + " |\n",
  )
  builder.write_string(
    "| 复制操作数 | " + plan.copy_ops().to_string() + " |\n",
  )
  builder.write_string(
    "| 复制字节 | " + plan.copied_bytes().to_string() + " |\n",
  )
  builder.write_string(
    "| 字面量字节 | " + plan.literal_bytes().to_string() + " |\n",
  )
  builder.write_string(
    "| delta 字节 | " + plan.delta_bytes().length().to_string() + " |\n",
  )
  builder.write_string(
    "| 节省比例 | " + ppm_text(plan.savings_ppm()) + " |\n",
  )
  builder.to_string()
}