///|
/// Top-N 热点报告。
///
/// 火焰图里同一个函数会出现在多条不同的调用路径下(例如 `array::Array::at`
/// 既在冒泡排序里被调用,也在插入排序里被调用)。因此热点榜要**按函数名聚合**
/// 自身耗时,而不是逐节点罗列——这与 `go tool pprof -top`、`moon-pprof summary`
/// 的口径一致。
///
/// 权重单位是上游决定的(moon-pprof 输出纳秒),本模块不对单位做任何假设:
/// 占比始终有意义,绝对值原样展示。

///|
/// 一个热点条目:按函数名聚合后的自身耗时。
pub struct Hotspot {
  name : String
  /// 该函数在整棵树中的自身耗时合计
  self_time : Double
  /// 占全部权重的比例
  ratio : Double
  /// 在树中出现的节点数(被多少条不同路径调用)
  occurrences : Int
} derive(Eq)

///|
/// 显式声明 `Eq` 的方法可被当作常规方法调用。
///
/// 新版编译器不再自动完成这个提升,不声明会报 `implicit_impl_as_method`。
pub extend Hotspot with Eq::{not_equal, equal}

///|
/// 聚合用的可变累加器。
priv struct Acc {
  mut self_time : Double
  mut occurrences : Int
}

///|
/// 深度优先遍历,按函数名累加自身耗时。
fn collect(node : Node, acc : Map[String, Acc]) -> Unit {
  let own = node.self_time()
  match acc.get(node.name) {
    Some(existing) => {
      existing.self_time = existing.self_time + own
      existing.occurrences = existing.occurrences + 1
    }
    None => acc.set(node.name, { self_time: own, occurrences: 1, })
  }
  for child in node.children {
    collect(child, acc)
  }
}

///|
/// 排序规则:自身耗时降序;同值时按名字升序,保证确定性。
fn compare_hotspots(a : Hotspot, b : Hotspot) -> Int {
  if a.self_time > b.self_time {
    -1
  } else if a.self_time < b.self_time {
    1
  } else {
    String::compare(a.name, b.name)
  }
}

///|
/// 取自身耗时最高的若干函数。
///
/// 自身耗时为 0 的函数是纯粹的「过路」节点,不构成热点,会被过滤掉
/// (与 `moon-pprof summary` 的口径一致)。
///
/// `top <= 0` 表示不截断,返回全部条目。
pub fn top_hotspots(tree : CallTree, top : Int) -> Array[Hotspot] {
  let acc : Map[String, Acc] = Map([])
  for root in tree.roots {
    collect(root, acc)
  }
  let all : Array[Hotspot] = []
  for pair in acc {
    let entry = pair.1
    if entry.self_time > 0.0 {
      all.push({
        name: pair.0,
        self_time: entry.self_time,
        ratio: if tree.total > 0.0 {
          entry.self_time / tree.total
        } else {
          0.0
        },
        occurrences: entry.occurrences,
      })
    }
  }
  all.sort_by(compare_hotspots)
  let count = if top > 0 && top < all.length() { top } else { all.length() }
  let kept : Array[Hotspot] = []
  for i = 0; i < count; i = i + 1 {
    kept.push(all[i])
  }
  kept
}

///|
/// 把比例格式化成一位小数的百分比。
///
/// 先放大成「千分比」再四舍五入,这样 99.97% 会正确进位成 100.0%,
/// 而不会出现 99.10% 这类结果。
fn format_percent(ratio : Double) -> String {
  let scaled = (ratio * 1000.0 + 0.5).to_int()
  let whole = scaled / 10
  let frac = scaled % 10
  "\{whole}.\{frac}%"
}

///|
/// 把热点列表渲染成纯文本报告。
///
/// `unit` 决定绝对值怎么显示(时间按数量级缩放,计数加千位分隔);
/// 占比不受单位影响。
pub fn render_hotspots(
  hotspots : Array[Hotspot],
  total : Double,
  unit : WeightUnit,
) -> String {
  let sb = StringBuilder()
  sb.write_string(
    "Top \{hotspots.length()} hotspots by self time (total \{format_weight(total, unit)})\n",
  )
  sb.write_string("\n")
  sb.write_string("  #  self%       self time  frame\n")
  let mut rank = 0
  for h in hotspots {
    rank = rank + 1
    let rank_text = rank.to_string()
    let percent_text = format_percent(h.ratio)
    let weight_text = format_weight(h.self_time, unit)
    sb.write_string(
      String::pad_start(rank_text, 3, ' ') +
      "  " +
      String::pad_start(percent_text, 6, ' ') +
      "  " +
      String::pad_start(weight_text, 13, ' ') +
      "  " +
      h.name +
      "\n",
    )
  }
  sb.to_string()
}