///|
/// 布局:把调用树算成一组矩形坐标。
///
/// 采用 icicle 方向(根在顶部、逐层向下)。算法本身很简单,要点有三个:
///
///   1. **按值比例切分**:子项宽度 = 父宽度 × (子值 / 父值)。因此不需要任何
///      矩形打包算法,天然不重叠、不留缝;
///   2. **最后一个子项直接取父节点右边界**,而不是用累加值。否则浮点误差会
///      一点点累积,几千个矩形之后就会出现肉眼可见的缝或重叠;
///   3. **自身耗时作为合成子项参与切分**,这样「父宽 = 全部子项宽之和」严格成立,
///      图面上不会出现空洞。
///
/// 布局不做最小宽度过滤:它产出的是**精确**的几何数据,是否绘制小于若干像素的
/// 矩形属于渲染层的取舍。

///|
/// 一个待绘制的矩形。
pub struct Rect {
  x : Double
  /// 纵向位置。由 `layout` 在排布完成后统一确定:
  /// 火焰图方向下根在底部,因此 y 依赖整棵树的最大深度。
  mut y : Double
  width : Double
  height : Double
  name : String
  /// 累计权重;合成项(自身耗时)则为该帧的自身耗时
  value : Double
  depth : Int
  /// 差异模式下的变化量;普通模式下为 `None`
  delta : Double?
} derive(Eq)

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

///|
/// 布局结果。渲染器拿到的布局自带完整几何信息,无需再传画布参数。
pub struct Layout {
  rects : Array[Rect]
  /// 画布宽度,与传入值一致
  width : Double
  /// 每行高度,与传入值一致
  row_height : Double
  /// 最深的深度下标(渲染高度为 `(max_depth + 1) * row_height`)
  max_depth : Int
  /// 全部权重之和,用于计算占比
  total : Double
  /// 差异模式下最大的变化量绝对值,用于颜色归一化;非差异模式为 0
  max_delta : Double
  /// 纵向方向:`false` 火焰图(根在底部),`true` 冰柱图(根在顶部)。
  /// 交互脚本判断「祖先在上还是在下」时需要它。
  inverted : Bool
} derive(Eq)

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

///|
/// 「自身耗时」合成帧的名字。
pub fn self_placeholder() -> String {
  "(self)"
}

///|
/// 参与切分的一个子项:要么是真实子节点,要么是自身耗时的合成项。
priv struct Item {
  name : String
  value : Double
  node : Node?
}

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

///|
/// 布局一个节点的子树。`x0`/`x1` 是它占据的水平区间。
fn layout_node(
  node : Node,
  x0 : Double,
  x1 : Double,
  depth : Int,
  row_height : Double,
  rects : Array[Rect],
) -> Unit {
  let pending : Array[(Node?, String, Double, Double, Double, Int, Double?)] = [
    (Some(node), node.name, node.value, x0, x1, depth, node.delta),
  ]
  while pending.length() > 0 {
    let (source, name, value, left, right, d, delta) = pending[pending.length() -
      1]
    ignore(pending.pop())
    rects.push({
      x: left,
      y: d.to_double() * row_height,
      width: right - left,
      height: row_height,
      name,
      value,
      depth: d,
      delta,
    })
    guard source is Some(current) else { continue }
    if right <= left || current.value <= 0.0 {
      continue
    }
    let items : Array[Item] = []
    for child in current.children {
      if child.value > 0.0 {
        items.push({ name: child.name, value: child.value, node: Some(child), })
      }
    }
    let own = current.self_time()
    if own > 0.0 && items.length() > 0 {
      items.push({ name: self_placeholder(), value: own, node: None, })
    }
    items.sort_by(compare_items)
    let tasks : Array[(Node?, String, Double, Double, Double, Int, Double?)] = []
    let mut cursor = left
    for i = 0; i < items.length(); i = i + 1 {
      let item = items[i]
      let next = if i + 1 == items.length() {
        right
      } else {
        smaller(
          right,
          larger(cursor, cursor + (right - left) * (item.value / current.value)),
        )
      }
      let change = match item.node {
        Some(child) => child.delta
        None => current.self_delta
      }
      tasks.push(
        (item.node, item.name, item.value, cursor, next, d + 1, change),
      )
      cursor = next
    }
    for i = tasks.length() - 1; i >= 0; i = i - 1 {
      pending.push(tasks[i])
    }
  }
}

///|
/// 把调用树布局成矩形列表(前序遍历顺序:父节点先于子节点)。
///
/// 多个根节点时按各自权重水平切分画布。
///
/// `inverted` 选择纵向方向:
///
///   - **默认(false)火焰图**:根在**底部**,逐层向上生长。底部密、顶部疏,
///     高耗时路径表现为向上伸出的「火苗」——这是 Brendan Gregg 定义的经典形态。
///   - `inverted = true` 冰柱图(icicle):根在顶部,逐层向下。
///
/// 两种方向都只影响 y 坐标,水平切分完全一致。
pub fn layout(
  tree : CallTree,
  width : Double,
  row_height : Double,
  inverted? : Bool,
) -> Layout raise ValidationError {
  validate_tree(tree)
  positive_geometry(width, "width")
  positive_geometry(row_height, "row_height")
  let flip = match inverted {
    Some(value) => value
    None => false
  }
  let rects : Array[Rect] = []
  if width > 0.0 &&
    row_height > 0.0 &&
    tree.total > 0.0 &&
    tree.roots.length() > 0 {
    let mut cursor = 0.0
    let mut index = 0
    for root in tree.roots {
      index = index + 1
      let next = if index == tree.roots.length() {
        width
      } else {
        smaller(
          width,
          larger(cursor, cursor + width * (root.value / tree.total)),
        )
      }
      layout_node(root, cursor, next, 0, row_height, rects)
      cursor = next
    }
  }
  let mut max_depth = 0
  for rect in rects {
    if rect.depth > max_depth {
      max_depth = rect.depth
    }
  }
  // 方向直到这里才能确定:火焰图的根在底部,y 取决于整棵树的最大深度
  if !flip {
    for rect in rects {
      rect.y = (max_depth - rect.depth).to_double() * row_height
    }
  }
  if !finite((max_depth.to_double() + 1.0) * row_height) {
    raise InvalidGeometry("height overflow")
  }
  // 差异配色需要一个统一的量级做归一化,否则一处剧变就会让其它差别看不出来
  let mut max_delta = 0.0
  for rect in rects {
    match rect.delta {
      Some(value) => {
        let magnitude = if value < 0.0 { -value } else { value }
        if magnitude > max_delta {
          max_delta = magnitude
        }
      }
      None => ()
    }
  }
  {
    rects,
    width,
    row_height,
    max_depth,
    total: tree.total,
    max_delta,
    inverted: flip,
  }
}

///|
/// 需要绘制的行数(= 最深深度 + 1)。
pub fn Layout::row_count(self : Layout) -> Int {
  if self.rects.length() == 0 {
    0
  } else {
    self.max_depth + 1
  }
}

///|
/// 画布所需高度。
pub fn Layout::height(self : Layout) -> Double {
  self.row_count().to_double() * self.row_height
}