///|
/// 权重的单位与格式化。
///
/// 折叠栈格式里第 2 列是**不透明的权重**,不同产出者含义完全不同:
///
/// | 产出者 | 权重含义 |
/// | --- | --- |
/// | `moon-pprof`(`pprof2folded`) | 纳秒时间 |
/// | `perf`、`py-spy`、`async-profiler` | 采样计数 |
///
/// 数值本身无法区分这两者(`42` 既可能是 42 纳秒也可能是 42 次采样),
/// 所以单位必须由使用者声明。默认纳秒——与本项目文档中的主链路一致。

///|
/// 权重单位。
///
/// 注意名字**不能**叫 `Unit`:那是 MoonBit 的内置类型,重名会遮蔽它,
/// 导致全仓库所有 `-> Unit` 的函数都报出「has type Unit, wanted Unit」这类怪错。
pub(all) enum WeightUnit {
  /// 采样计数(`perf` 等)。显示为带千位分隔的整数,不带单位后缀。
  Count
  /// 纳秒(默认)
  Nanoseconds
  Microseconds
  Milliseconds
  Seconds
} derive(Eq)

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

///|
/// 从命令行文本解析单位。无法识别时返回 `None`。
pub fn WeightUnit::parse(text : String) -> WeightUnit? {
  match text.to_lower() {
    "count" | "counts" | "samples" | "sample" => Some(Count)
    "ns" | "nanoseconds" | "nanosecond" => Some(Nanoseconds)
    "us" | "µs" | "microseconds" | "microsecond" => Some(Microseconds)
    "ms" | "milliseconds" | "millisecond" => Some(Milliseconds)
    "s" | "sec" | "secs" | "seconds" | "second" => Some(Seconds)
    _ => None
  }
}

///|
/// 单位的规范写法,用于帮助信息与报告表头。
pub fn WeightUnit::label(self : WeightUnit) -> String {
  match self {
    Count => "count"
    Nanoseconds => "ns"
    Microseconds => "us"
    Milliseconds => "ms"
    Seconds => "s"
  }
}

///|
/// 一个单位的权重折算成纳秒。`Count` 不适用,返回 1。
fn WeightUnit::to_nanoseconds(self : WeightUnit) -> Double {
  match self {
    Count => 1.0
    Nanoseconds => 1.0
    Microseconds => 1000.0
    Milliseconds => 1000000.0
    Seconds => 1000000000.0
  }
}

///|
/// 保留最多两位小数,去掉多余的尾零。
fn format_scaled(value : Double) -> String {
  let negative = value < 0.0
  let magnitude = if negative { -value } else { value }
  let scaled = (magnitude * 100.0 + 0.5).to_int()
  let whole = scaled / 100
  let frac = scaled % 100
  let sign = if negative { "-" } else { "" }
  if frac == 0 {
    sign + whole.to_string()
  } else if frac % 10 == 0 {
    sign + whole.to_string() + "." + (frac / 10).to_string()
  } else if frac < 10 {
    sign + whole.to_string() + ".0" + frac.to_string()
  } else {
    sign + whole.to_string() + "." + frac.to_string()
  }
}

///|
/// 按数量级自动选择易读的时间单位,与 `go tool pprof` 的做法一致。
fn format_duration(nanoseconds : Double) -> String {
  let magnitude = if nanoseconds < 0.0 { -nanoseconds } else { nanoseconds }
  if magnitude >= 1000000000.0 {
    format_scaled(nanoseconds / 1000000000.0) + " s"
  } else if magnitude >= 1000000.0 {
    format_scaled(nanoseconds / 1000000.0) + " ms"
  } else if magnitude >= 1000.0 {
    format_scaled(nanoseconds / 1000.0) + " µs"
  } else {
    format_scaled(nanoseconds) + " ns"
  }
}

///|
/// 给整数加千位分隔符。`perf` 的采样计数动辄六七位,不隔开很难读。
fn add_commas(digits : String) -> String {
  let negative = digits.has_prefix("-")
  let body = if negative { digits[1:] } else { digits }
  let sb = StringBuilder()
  if negative {
    sb.write_string("-")
  }
  let n = body.length()
  for i = 0; i < n; i = i + 1 {
    if i > 0 && (n - i) % 3 == 0 {
      sb.write_string(",")
    }
    sb.write_string(body[i:i + 1].to_owned())
  }
  sb.to_string()
}

///|
/// 把权重格式化成人能读的文本。
///
/// 时间单位按数量级自动缩放(`871808200` 纳秒 → `871.8 ms`);
/// 计数则原样显示并加千位分隔(`1234567` → `1,234,567`),不加后缀——
/// 加了反而会让人误以为它是时间。
pub fn format_weight(value : Double, unit : WeightUnit) -> String {
  match unit {
    Count => {
      // 负数朝绝对值更大的方向取整,否则 -1200000 会被写成 -1199999
      let rounded = (if value < 0.0 { value - 0.5 } else { value + 0.5 }).to_int()
      if rounded.to_double() == value {
        add_commas(rounded.to_string())
      } else {
        value.to_string()
      }
    }
    _ => format_duration(value * WeightUnit::to_nanoseconds(unit))
  }
}

///|
/// 带符号的变化量,正数带 `+` 便于识别。
pub fn format_signed_weights(value : Double, unit : WeightUnit) -> String {
  if value > 0.0 {
    "+" + format_weight(value, unit)
  } else {
    format_weight(value, unit)
  }
}