///|
fn html_escape(text : String) -> String {
  text
  .replace_all(old="&", new="&")
  .replace_all(old="<", new="<")
  .replace_all(old=">", new=">")
  .replace_all(old="\"", new=""")
  .replace_all(old="'", new="'")
}

///|
fn change_kind_name(kind : ChangeKind) -> String {
  match kind {
    Added => "added"
    Removed => "removed"
    Grew => "grew"
    Shrank => "shrank"
    Modified => "modified"
    KindChanged => "kind changed"
  }
}

///|
pub fn render_summary(snapshot : Snapshot, limit? : Int = 10) -> String {
  let lines : Array[String] = [
    "MoonDisk scan summary",
    "Root: " + snapshot.root,
    "Files: " + snapshot.file_count().to_string(),
    "Directories: " + snapshot.directory_count().to_string(),
    "Total size: " + format_bytes(snapshot.total_size()),
    "Errors: " + snapshot.errors.length().to_string(),
    "",
    "Largest files",
  ]
  let total = snapshot.total_size()
  for entry in largest_files(snapshot, limit) {
    lines.push(
      pad_left(format_bytes(entry.size), 12) +
      "  " +
      pad_left(format_percent(entry.size, total), 7) +
      "  " +
      entry.path,
    )
  }
  lines.push("")
  lines.push("Largest directories")
  for stat in largest_directories(snapshot, limit) {
    let name = if stat.path == "" { "." } else { stat.path }
    lines.push(
      pad_left(format_bytes(stat.recursive_size), 12) +
      "  " +
      pad_left(format_percent(stat.recursive_size, total), 7) +
      "  " +
      name,
    )
  }
  lines.push("")
  lines.push("File categories")
  for stat in category_stats(snapshot) {
    lines.push(
      pad_left(format_bytes(stat.size), 12) +
      "  " +
      pad_left(format_percent(stat.size, total), 7) +
      "  " +
      pad_left(stat.files.to_string(), 7) +
      " files  " +
      stat.name,
    )
  }
  lines.join("\n")
}

///|
pub fn render_diff_summary(diff : SnapshotDiff, limit? : Int = 20) -> String {
  let lines : Array[String] = [
    "MoonDisk snapshot comparison",
    "Added files: " +
    diff.added_count().to_string() +
    " (" +
    format_bytes(diff.added_size) +
    ")",
    "Removed files: " +
    diff.removed_count().to_string() +
    " (" +
    format_bytes(diff.removed_size) +
    ")",
    "Changed entries: " + diff.changed_count().to_string(),
    "Net change: " + format_bytes(diff.net_size),
    "",
    "Largest changes",
  ]
  let count = if limit < diff.changes.length() {
    limit
  } else {
    diff.changes.length()
  }
  for i = 0; i < count; i = i + 1 {
    let change = diff.changes[i]
    lines.push(
      pad_right(change_kind_name(change.kind), 12) +
      " " +
      pad_left(format_bytes(change.size_delta), 12) +
      "  " +
      change.path,
    )
  }
  lines.join("\n")
}

///|
pub fn render_plan(plan : CleanupPlan) -> String {
  let lines : Array[String] = [
    "MoonDisk cleanup plan",
    "Root: " + plan.root,
    "Quarantine: " + plan.quarantine_root,
    "Candidates: " + plan.items.length().to_string(),
    "Potentially reclaimable: " + format_bytes(plan.reclaimable_size),
    "Plan issues: " + plan.issues.length().to_string(),
    "",
  ]
  for item in plan.items {
    lines.push(
      "[" +
      risk_name(item.risk) +
      "] " +
      pad_left(format_bytes(item.expected_size), 12) +
      "  " +
      item.source +
      " — " +
      item.reason,
    )
  }
  if !plan.issues.is_empty() {
    lines.push("")
    lines.push("Issues requiring review")
    for issue in plan.issues {
      lines.push("- " + issue.path + ": " + issue.message)
    }
  }
  lines.join("\n")
}

///|
pub fn render_tree(
  snapshot : Snapshot,
  max_depth? : Int = 4,
  minimum_size? : Int64 = 0L,
) -> String {
  let total = snapshot.total_size()
  let lines : Array[String] = [".  " + format_bytes(total) + "  100.0%"]
  let stats = directory_stats(snapshot)
  stats.sort_by(fn(a, b) {
    if a.path < b.path {
      -1
    } else if a.path > b.path {
      1
    } else {
      0
    }
  })
  for stat in stats {
    if stat.path == "" ||
      path_depth(stat.path) > max_depth ||
      stat.recursive_size < minimum_size {
      continue
    }
    let depth = path_depth(stat.path)
    let branch = "  ".repeat(depth - 1) + "└─ "
    lines.push(
      branch +
      basename(stat.path) +
      "/  " +
      format_bytes(stat.recursive_size) +
      "  " +
      format_percent(stat.recursive_size, total),
    )
  }
  lines.join("\n")
}

///|
pub fn render_html_report(
  snapshot : Snapshot,
  title? : String = "MoonDisk report",
) -> String {
  let total = snapshot.total_size()
  let rows : Array[String] = []
  for entry in largest_files(snapshot, 100) {
    rows.push(
      "" +
      html_escape(entry.path) +
      "" +
      html_escape(format_bytes(entry.size)) +
      "" +
      html_escape(format_percent(entry.size, total)) +
      "",
    )
  }
  let directory_rows : Array[String] = []
  for stat in largest_directories(snapshot, 100) {
    let path = if stat.path == "" { "." } else { stat.path }
    directory_rows.push(
      "" +
      html_escape(path) +
      "" +
      html_escape(format_bytes(stat.recursive_size)) +
      "" +
      stat.recursive_files.to_string() +
      "",
    )
  }
  let category_rows : Array[String] = []
  for stat in category_stats(snapshot) {
    category_rows.push(
      "" +
      html_escape(stat.name) +
      "" +
      html_escape(format_bytes(stat.size)) +
      "" +
      html_escape(format_percent(stat.size, total)) +
      "" +
      stat.files.to_string() +
      "",
    )
  }
  let insight_rows : Array[String] = []
  for insight in storage_insights(snapshot, snapshot.created_at) {
    insight_rows.push(
      "
[" + html_escape(severity_name(insight.severity)) + "] " + html_escape(insight.title) + "

" + html_escape(insight.detail) + "

", ) } let insight_section = if insight_rows.is_empty() { "

No notable storage patterns were detected.

" } else { insight_rows.join("") } "\n" + "" + "" + html_escape(title) + "

" + html_escape(title) + "

" + "

Scan root: " + html_escape(snapshot.root) + "

" + "
Total size
" + html_escape(format_bytes(total)) + "
Files
" + snapshot.file_count().to_string() + "
Scan errors
" + snapshot.errors.length().to_string() + "
" + "

Largest files

" + rows.join("") + "
PathSizeShare
" + "

Largest directories

" + directory_rows.join("") + "
PathSizeFiles
" + "

File categories

" + category_rows.join("") + "
CategorySizeShareFiles
" + "

Storage insights

" + insight_section + "
" }