///|
#valtype
priv struct LabelExitGuard {
  statement_index : Int32
  condition : Int32
  invert_condition : Bool
  failure_branch : Int32
  success_prefix : Int32
}

///|
/// L: { prefix; if (bad) { failure; break L } success }
/// becomes { prefix; if (!bad) { success } { failure } } when success always
/// exits beyond L. Keep both existing lexical environments; the new success
/// block contains no direct lexical declarations and needs no new scope.
fn Program::compact_label_exit(self : Program, id : Int32) -> Unit {
  guard !self.has_dynamic_scope else { return }
  let labeled = self.nodes[id]
  guard labeled.kind is Labeled else { return }
  let initial_body = self.nodes[labeled.c]
  guard initial_body.kind is Block else { return }
  let in_function = for scope = initial_body.c {
    guard scope >= 0 else { break false }
    let environment = self.scopes[scope]
    if environment.kind == SCOPE_FUNCTION {
      break true
    }
    continue environment.parent
  }
  // Script/eval observes statement completion values. These rules are for
  // function bodies, where ordinary statement values are discarded.
  guard in_function else { return }
  if self.trim_label_tail(labeled.c, labeled.d) &&
    !self.references_label(labeled.c, labeled.d) {
    self.nodes[id] = self.nodes[labeled.c]
    return
  }
  let body = self.nodes[labeled.c]
  guard body.kind is Block && body.b >= 2 else { return }
  if !self.is_forwarding_label_test(body, labeled.d) &&
    self.compact_simple_label_guards(id, labeled, body) {
    return
  }
  if self.compact_label_test(id, labeled, body) {
    return
  }
  let tail = self.edges[body.a + body.b - 1]
  guard self.exits_beyond_label(tail, labeled.d) else { return }
  guard self.label_exit_guard(body, labeled.d) is Some(exit_guard) else {
    return
  }
  for index in (exit_guard.statement_index + 1)..= 0 {
    let offset = self.edges.length()
    // Keep this branch as a separate statement: its lexical environment must
    // end before the original success tail starts executing.
    self.edges.push(exit_guard.success_prefix)
    for index in 0..= 0 { 1 } else { 0 })
  let success = self.node(Block, success_offset, success_count, body.c, body.d)
  let condition = if exit_guard.invert_condition {
    self.negated_value(exit_guard.condition)
  } else {
    exit_guard.condition
  }
  let success_guard = self.node(If, condition, success, -1, 0)
  let failure = self.nodes[exit_guard.failure_branch]
  let failure_body = if failure.kind is Block && failure.b > 1 {
    self.node(Block, failure.a, failure.b - 1, failure.c, failure.d)
  } else {
    -1
  }
  let offset = self.edges.length()
  for index in 0..= 0 {
    self.edges.push(failure_body)
  }
  self.nodes[id] = {
    ..body,
    a: offset,
    b: exit_guard.statement_index + 1 + (if failure_body >= 0 { 1 } else { 0 }),
  }
}

///|
/// A guard containing only break L skips the rest of this exact label block.
/// Put that suffix under the inverted guard without changing its evaluation
/// order. Other exits keep the label, including exits inside loops or finally.
fn Program::compact_simple_label_guards(
  self : Program,
  id : Int32,
  labeled : Node,
  original_body : Node,
) -> Bool {
  let mut body = original_body
  let mut changed = false
  for index = body.b - 2; index >= 0; index = index - 1 {
    let branch = self.nodes[self.edges[body.a + index]]
    guard branch.kind is If && branch.c < 0 else { continue }
    let consequent = self.nodes[self.single_statement(branch.b)]
    guard consequent.kind is Break && consequent.c == labeled.d else {
      continue
    }
    guard self.can_nest_label_suffix(body, index + 1) else { continue }
    // Only a new empty lexical environment is introduced. Existing suffix
    // blocks retain their own scopes, and direct lexical declarations reject.
    let suffix = self.node(
      Block,
      body.a + index + 1,
      body.b - index - 1,
      body.c,
      body.d,
    )
    let condition = self.negated_value(branch.a)
    let guarded_suffix = self.node(If, condition, suffix, -1, 0)
    self.compact_branch(guarded_suffix)
    let offset = self.edges.length()
    for prefix in 0.. Bool {
  for index in start.. Bool {
  let tail = self.nodes[self.edges[body.a + body.b - 1]]
  guard tail.kind is Break && tail.c >= 0 && tail.c != label else {
    return false
  }
  for index in 0..<(body.b - 1) {
    guard self.is_label_test(self.edges[body.a + index], label) else {
      return false
    }
  }
  true
}

///|
/// Only these statement tails fall directly out of the enclosing label.
/// Loops, switches and exception handlers keep their own completion behavior.
fn Program::trim_label_tail(self : Program, id : Int32, label : Int32) -> Bool {
  let node = self.nodes[id]
  match node.kind {
    Break if node.c == label => {
      self.nodes[id] = Node::new(Empty, 0, 0, 0, 0)
      true
    }
    Block => {
      let mut kept = node.b
      let mut removed_break = false
      while kept > 0 {
        let tail = self.edges[node.a + kept - 1]
        if self.trim_label_tail(tail, label) {
          removed_break = true
        }
        guard self.is_empty_statement(self.single_statement(tail)) else {
          break
        }
        kept -= 1
      }
      if removed_break && kept != node.b {
        self.nodes[id] = { ..node, b: kept, }
      }
      removed_break
    }
    If => {
      let consequent_changed = self.trim_label_tail(node.b, label)
      let alternative_changed = node.c >= 0 &&
        self.trim_label_tail(node.c, label)
      guard consequent_changed || alternative_changed else { return false }
      let consequent_empty = self.is_empty_statement(
        self.single_statement(node.b),
      )
      let alternative_empty = node.c < 0 ||
        self.is_empty_statement(self.single_statement(node.c))
      if alternative_empty {
        self.nodes[id] = { ..node, c: -1, }
      } else if consequent_empty &&
        !(self.nodes[node.c].kind is FunctionDeclaration) {
        let condition = self.negated_value(node.a)
        self.nodes[id] = Node::new(If, condition, node.c, -1, 0)
      }
      self.compact_branch(id)
      true
    }
    Labeled if node.d != label => self.trim_label_tail(node.c, label)
    _ => false
  }
}

///|
/// Called only after a tail break was removed. Unlike tail trimming, this
/// reference check visits loop/switch/try bodies, where earlier exits remain
/// necessary. Function and class bodies have independent label namespaces.
fn Program::references_label(self : Program, id : Int32, label : Int32) -> Bool {
  guard id >= 0 else { return false }
  let node = self.nodes[id]
  match node.kind {
    Break | Continue => node.c == label
    Block => self.statements_reference_label(node.a, node.b, label)
    If =>
      self.references_label(node.b, label) ||
      self.references_label(node.c, label)
    Labeled => node.d == label || self.references_label(node.c, label)
    While | With => self.references_label(node.b, label)
    DoWhile => self.references_label(node.a, label)
    For => self.references_label(node.d, label)
    ForIn | ForOf => self.references_label(node.c, label)
    Switch => {
      for index in 0..
      self.references_label(node.a, label) ||
      self.references_label(node.b, label) ||
      self.references_label(node.c, label)
    Catch => self.references_label(node.b, label)
    _ => false
  }
}

///|
fn Program::statements_reference_label(
  self : Program,
  offset : Int32,
  count : Int32,
  label : Int32,
) -> Bool {
  for index in 0.. Bool {
  guard body.b == 2 else { return false }
  let inner = self.nodes[self.edges[body.a]]
  let effect = self.edges[body.a + 1]
  guard inner.kind is Labeled &&
    inner.d != outer.d &&
    self.nodes[self.single_statement(effect)].kind is (Return | Throw) else {
    return false
  }
  let tests = self.nodes[inner.c]
  guard tests.kind is Block && tests.b >= 2 else { return false }
  let tail = self.nodes[self.edges[tests.a + tests.b - 1]]
  guard tail.kind is Break && tail.c == outer.d else { return false }
  for index in 0..<(tests.b - 1) {
    guard self.is_label_test(self.edges[tests.a + index], inner.d) else {
      return false
    }
  }
  let mut condition = self.label_test_condition(self.edges[tests.a], inner.d)
  for index in 1..<(tests.b - 1) {
    let next = self.label_test_condition(self.edges[tests.a + index], inner.d)
    condition = self.slot_logical(LogicalOr, condition, next)
  }
  self.nodes[id] = Node::new(If, condition, effect, -1, 0)
  true
}

///|
/// Every successful arm breaks to the same inner label. A false arm may only
/// fall through or test another condition; no other statement can be moved.
fn Program::is_label_test(self : Program, id : Int32, label : Int32) -> Bool {
  let branch = self.nodes[self.single_statement(id)]
  guard branch.kind is If else { return false }
  let consequent = self.nodes[self.single_statement(branch.b)]
  let then_breaks = consequent.kind is Break && consequent.c == label
  guard then_breaks || self.is_label_test(branch.b, label) else { return false }
  branch.c < 0 || self.is_label_test(branch.c, label)
}

///|
fn Program::label_test_condition(
  self : Program,
  id : Int32,
  label : Int32,
) -> Int32 {
  let branch = self.nodes[self.single_statement(id)]
  let consequent = self.nodes[self.single_statement(branch.b)]
  let then_breaks = consequent.kind is Break && consequent.c == label
  if then_breaks {
    guard branch.c >= 0 else { return branch.a }
    let alternative = self.label_test_condition(branch.c, label)
    return self.slot_logical(LogicalOr, branch.a, alternative)
  }
  let consequent_test = self.label_test_condition(branch.b, label)
  guard branch.c >= 0 else {
    return self.slot_logical(LogicalAnd, branch.a, consequent_test)
  }
  let alternative_test = self.label_test_condition(branch.c, label)
  let condition = self.slot_operand(branch.a, ConditionalTest)
  let consequent_value = self.slot_operand(consequent_test, ConditionalArm)
  let alternative_value = self.slot_operand(alternative_test, ConditionalArm)
  self.node(Conditional, condition, consequent_value, alternative_value, 0)
}

///|
fn Program::label_exit_guard(
  self : Program,
  body : Node,
  label : Int32,
) -> LabelExitGuard? {
  for index in 0..<(body.b - 1) {
    let branch = self.nodes[self.edges[body.a + index]]
    guard branch.kind is If else { continue }
    if self.has_direct_label_break_tail(branch.b, label) {
      return Some({
        statement_index: index,
        condition: branch.a,
        invert_condition: true,
        failure_branch: branch.b,
        success_prefix: branch.c,
      })
    }
    if branch.c >= 0 && self.has_direct_label_break_tail(branch.c, label) {
      return Some({
        statement_index: index,
        condition: branch.a,
        invert_condition: false,
        failure_branch: branch.c,
        success_prefix: branch.b,
      })
    }
  }
  None
}

///|
fn Program::has_direct_label_break_tail(
  self : Program,
  id : Int32,
  label : Int32,
) -> Bool {
  let branch = self.nodes[id]
  let tail = if branch.kind is Block && branch.b > 0 {
    self.nodes[self.edges[branch.a + branch.b - 1]]
  } else {
    branch
  }
  tail.kind is Break && tail.c == label
}

///|
/// The terminal jump must leave the candidate label. A nested loop, switch,
/// label or try statement needs its own target/finally analysis and is skipped.
fn Program::exits_beyond_label(
  self : Program,
  id : Int32,
  label : Int32,
) -> Bool {
  let node = self.nodes[id]
  match node.kind {
    Return | Throw => true
    Break | Continue => node.c != label
    Block if node.b > 0 =>
      self.exits_beyond_label(self.edges[node.a + node.b - 1], label)
    If =>
      node.c >= 0 &&
      self.exits_beyond_label(node.b, label) &&
      self.exits_beyond_label(node.c, label)
    _ => false
  }
}

///|
/// Moving a lexical declaration into a new conditional block changes its TDZ
/// and visibility to the prefix. Nested blocks retain their existing scopes.
fn Program::can_nest_label_tail(self : Program, id : Int32) -> Bool {
  let node = self.nodes[id]
  match node.kind {
    VariableDeclaration => node.a == DECLARATION_VAR
    FunctionDeclaration | ClassDeclaration => false
    Labeled => self.can_nest_label_tail(node.c)
    _ => true
  }
}

///|
/// Count only statement-level jumps; function/class expressions introduce
/// independent label namespaces. Negative means an unsupported boundary.
fn Program::label_break_count(
  self : Program,
  id : Int32,
  label : Int32,
) -> Int32 {
  guard id >= 0 else { return 0 }
  let node = self.nodes[id]
  match node.kind {
    Break => if node.c == label { 1 } else { 0 }
    Continue => if node.c == label { -1 } else { 0 }
    Block => self.label_breaks_in_statements(node.a, node.b, label)
    If => {
      let consequent = self.label_break_count(node.b, label)
      guard consequent >= 0 && consequent <= 1 else { return consequent }
      let alternative = self.label_break_count(node.c, label)
      guard alternative >= 0 else { return alternative }
      consequent + alternative
    }
    Labeled => {
      guard node.d != label else { return -1 }
      self.label_break_count(node.c, label)
    }
    While => self.label_break_count(node.b, label)
    DoWhile => self.label_break_count(node.a, label)
    For => self.label_break_count(node.d, label)
    ForIn | ForOf => self.label_break_count(node.c, label)
    Switch => {
      let mut breaks = 0
      for index in 0..= 0 else { return case_breaks }
        breaks += case_breaks
        guard breaks <= 1 else { return breaks }
      }
      breaks
    }
    Try | Catch | With | FunctionDeclaration | ClassDeclaration => -1
    _ => 0
  }
}

///|
fn Program::label_breaks_in_statements(
  self : Program,
  offset : Int32,
  count : Int32,
  label : Int32,
) -> Int32 {
  let mut breaks = 0
  for index in 0..= 0 else { return statement_breaks }
    breaks += statement_breaks
    guard breaks <= 1 else { return breaks }
  }
  breaks
}