// `dependents` and `deps` print trees the way tree(1) prints directories:
// the same rules, the same -L/--noreport/-J, a module where tree has a file.

///|
fn module_count(n : Int32) -> String {
  if n == 1 {
    "1 module"
  } else {
    "\{n} modules"
  }
}

///|
fn render_text(
  graph : @graph.Graph,
  forest : @graph.Forest,
  report : String?,
) -> String {
  let out = StringBuilder()
  fn line(prefix : String, branch : String, node : @graph.TreeNode) -> Unit {
    out.write_string(prefix)
    out.write_string(branch)
    out.write_string(graph.name(node.node))
    if node.duplicate {
      out.write_string(" (*)")
    }
    out.write_char('\n')
  }

  fn children(prefix : String, node : @graph.TreeNode) -> Unit {
    let n = node.children.length()
    for k, child in node.children {
      let last = k == n - 1
      line(prefix, if last { "└── " } else { "├── " }, child)
      children(prefix + (if last { "    " } else { "│   " }), child)
    }
  }

  for root in forest.roots {
    line("", "", root)
    children("", root)
  }
  if report is Some(r) {
    out.write_char('\n')
    out.write_string(r)
    out.write_char('\n')
  }
  out.to_string()
}

///|
/// tree's -J layout: a pretty-printed array of nodes, four spaces per level,
/// ending with a report object.
fn render_json(
  graph : @graph.Graph,
  forest : @graph.Forest,
  metrics : @graph.Metrics,
  report : Json?,
) -> String {
  let out = StringBuilder()
  fn node(level : Int32, n : @graph.TreeNode, last : Bool) -> Unit {
    out.write_string(" ".repeat(4 * level))
    let i = n.node
    let fields : Map[String, Json] = Map([
      ("type", Json::string("module")),
      ("name", Json::string(graph.name(i))),
      ("version", Json::string(graph.current(i).version)),
      ("owner", Json::string(graph.owner(i))),
      (
        "metrics",
        Json::object(
          Map([
            ("direct", Json::number(metrics.get(i, Direct).to_double())),
            ("transitive", Json::number(metrics.get(i, Transitive).to_double())),
            ("owners", Json::number(metrics.get(i, Owners).to_double())),
          ]),
        ),
      ),
    ])
    if n.duplicate {
      fields["duplicate"] = Json::boolean(true)
    }
    if n.truncated {
      fields["truncated"] = Json::boolean(true)
    }
    let head = Json::object(fields).stringify()
    if n.children.is_empty() {
      out.write_string(head)
    } else {
      // Reopen the object to append "contents", keeping tree's shape.
      out.write_string(head[:head.length() - 1].to_owned())
      out.write_string(",\"contents\":[\n")
      let k = n.children.length()
      for j, c in n.children {
        node(level + 1, c, j == k - 1)
      }
      out.write_string(" ".repeat(4 * level))
      out.write_string("]}")
    }
    out.write_string(if last { "\n" } else { ",\n" })
  }

  out.write_string("[\n")
  let k = forest.roots.length()
  for j, root in forest.roots {
    node(1, root, j == k - 1 && report is None)
  }
  if report is Some(r) {
    out.write_string("    ")
    out.write_string(r.stringify())
    out.write_char('\n')
  }
  out.write_string("]\n")
  out.to_string()
}