///|
// Continuation opcodes retain their exact type/tag payload in imm0. The imm1
// branch table holds stable handler label identities, in on-label order; its
// default entry is unused. Raw binary label depths are reconstructed at lowering.
fn hot_continuation_handlers(instr : Instruction) -> Array[@lib.ResumeHandler] {
  match instr {
    Instruction::Resume(_, handlers)
    | Instruction::ResumeThrow(_, _, handlers)
    | Instruction::ResumeThrowRef(_, handlers) => handlers
    _ => []
  }
}

///|
fn hot_continuation_with_handler_labels(
  instr : Instruction,
  labels : Array[LabelIdx],
) -> Instruction {
  let mut cursor = 0
  let handlers = hot_continuation_handlers(instr).map(fn(handler) {
    match handler {
      @lib.ResumeOnLabel(tag, _) => {
        let label = labels[cursor]
        cursor += 1
        @lib.ResumeHandler::on_label(tag, label)
      }
      @lib.ResumeOnSwitch(_) => handler
    }
  })
  match instr {
    Instruction::Resume(type_idx, _) => Instruction::resume_(type_idx, handlers)
    Instruction::ResumeThrow(type_idx, tag, _) =>
      Instruction::resume_throw(type_idx, tag, handlers)
    Instruction::ResumeThrowRef(type_idx, _) =>
      Instruction::resume_throw_ref(type_idx, handlers)
    _ => instr
  }
}

///|
pub fn hot_continuation_targets(
  func : HotFunc,
  node_id : NodeId,
) -> Array[LabelId] {
  let node = hot_node_get(func, node_id)
  if node.op != HotOp::Continuation {
    return []
  }
  hot_branch_table_targets(func, node.imm1)
}

///|
pub fn hot_continuation_targets_set(
  func : HotFunc,
  node_id : NodeId,
  targets : Array[LabelId],
) -> Unit {
  let node = hot_node_get(func, node_id)
  if node.op != HotOp::Continuation {
    abort("continuation target mutation requires a continuation node")
  }
  for target in targets {
    if target != HOT_IMPLICIT_FUNCTION_LABEL && !hot_label_is_live(func, target) {
      abort("continuation handler target must be live")
    }
  }
  let table = hot_alloc_branch_table(
    func,
    HotBranchTable::new(targets, HOT_IMPLICIT_FUNCTION_LABEL),
  )
  hot_node_set(func, node_id, { ..node, imm1: table, })
}

///|
// Existence-only callers can avoid constructing an owned empty array when
// the builder's arena scan proved there are no continuation nodes.
#inline
fn cfg_expression_has_continuation_targets(
  func : HotFunc,
  root : NodeId,
  cache : Array[Array[LabelId]?],
) -> Bool {
  !cache.is_empty() &&
  !cfg_expression_continuation_targets(func, root, cache).is_empty()
}

///|
fn cfg_expression_continuation_targets(
  func : HotFunc,
  root : NodeId,
  cache : Array[Array[LabelId]?],
) -> Array[LabelId] {
  if cache.is_empty() {
    return []
  }
  let pending = [(root, false)]
  while !pending.is_empty() {
    let (node, finish) = pending.pop().unwrap()
    if node < 0 || cache[node] is Some(_) {
      continue
    }
    let children = if hot_is_control_node(func, node) {
      if hot_node_get(func, node).op == HotOp::If {
        [hot_child_get(func, node, 0)]
      } else {
        []
      }
    } else {
      hot_child_span(func, node)
    }
    if !finish {
      pending.push((node, true))
      for child in children {
        if child >= 0 && cache[child] is None {
          pending.push((child, false))
        }
      }
    } else {
      let targets = hot_continuation_targets(func, node)
      for child in children {
        if child < 0 {
          continue
        }
        for target in cache[child].unwrap() {
          if !targets.contains(target) {
            targets.push(target)
          }
        }
      }
      cache[node] = Some(targets)
    }
  }
  cache[root].unwrap()
}