///|
/// if (test) { effects } else return x; return y
/// becomes return test ? (effects, y) : x. Only expression-only arms can be
/// moved: declarations and other control flow keep their original statements.
fn Program::compact_value_branch_return(
  self : Program,
  branch_id : Int32,
  return_id : Int32,
) -> Bool {
  let branch = self.nodes[branch_id]
  let following = self.nodes[return_id]
  let consequent = self.nodes[self.single_statement(branch.b)]
  let alternative = self.nodes[self.single_statement(branch.c)]
  let (yes, no) = if consequent.kind is Return &&
    consequent.a >= 0 &&
    self.is_expression_prefix(branch.c) {
    (consequent.a, self.prefix_return_value(branch.c, following.a))
  } else if alternative.kind is Return &&
    alternative.a >= 0 &&
    self.is_expression_prefix(branch.b) {
    (self.prefix_return_value(branch.b, following.a), alternative.a)
  } else {
    return false
  }
  let value = self.compact_choice(branch.a, yes, no)
  self.nodes[return_id] = { ..following, a: value, }
  true
}

///|
fn Program::is_expression_prefix(self : Program, id : Int32) -> Bool {
  let statement = self.nodes[self.single_statement(id)]
  match statement.kind {
    Empty | ExpressionStatement => true
    Block => {
      for index in 0.. false
  }
}

///|
/// Called only after the prefix is known to contain no bindings or exits.
fn Program::prefix_return_value(
  self : Program,
  id : Int32,
  result : Int32,
) -> Int32 {
  let statement = self.nodes[self.single_statement(id)]
  match statement.kind {
    ExpressionStatement => self.sequence_pair(statement.a, result)
    Block if statement.b > 0 => {
      let offset = self.edges.length()
      for index in 0.. result
  }
}

///|
/// Share the bare return while retaining the effect arm's lexical block:
/// if (test) return; else { effects; return } becomes a guarded effect block
/// followed by return. The enclosing statement need not be a function tail.
fn Program::compact_unit_branch(self : Program, id : Int32) -> Unit {
  let branch = self.nodes[id]
  guard branch.kind is If && branch.c >= 0 else { return }
  let (effects_id, invert_condition) = if self.is_bare_return_statement(
      branch.b,
    ) &&
    self.nodes[branch.c].kind is Block {
    (branch.c, true)
  } else if self.is_bare_return_statement(branch.c) &&
    self.nodes[branch.b].kind is Block {
    (branch.b, false)
  } else {
    return
  }
  guard self.has_bare_return_tail(effects_id) else { return }
  let original_effects = self.nodes[effects_id]
  if original_effects.b == 2 {
    let first = self.nodes[self.edges[original_effects.a]]
    let tail = self.nodes[self.edges[original_effects.a + 1]]
    // A child If may already have become this synthetic shape. Its scope is
    // inherited, so it must not be treated as another owned block scope.
    // Conservatively leave matching source blocks alone as well.
    if first.kind is If && tail.kind is Return && tail.a < 0 {
      return
    }
  }
  let effects = self.without_bare_return_tail(effects_id)
  let condition = if invert_condition {
    self.negated_value(branch.a)
  } else {
    branch.a
  }
  let guarded_effects = self.node(If, condition, effects, -1, 0)
  let shared_return = self.node(Return, -1, 0, 0, 0)
  let offset = self.edges.length()
  self.edges.push(guarded_effects)
  self.edges.push(shared_return)
  let scope = self.scopes[original_effects.c].parent
  // This block adds no bindings; preserve the original effect block below it.
  // A positive literal summary keeps the moved condition visible to pooling.
  self.nodes[id] = Node::new(Block, offset, 2, scope, 1)
}

///|
fn Program::is_bare_return_statement(self : Program, id : Int32) -> Bool {
  let statement = self.nodes[self.single_statement(id)]
  statement.kind is Return && statement.a < 0
}

///|
/// if (test) return; { effects; return }
/// becomes if (!test) { effects } return. The shared return remains in place:
/// this statement list need not be at the end of its function.
fn Program::compact_guarded_unit_return(
  self : Program,
  guard_id : Int32,
  following_id : Int32,
) -> Bool {
  let branch = self.nodes[guard_id]
  guard branch.kind is If && branch.c < 0 else { return false }
  let early_return = self.nodes[self.single_statement(branch.b)]
  guard early_return.kind is Return && early_return.a < 0 else { return false }
  guard self.has_bare_return_tail(following_id) else { return false }
  let effects = self.without_bare_return_tail(following_id)
  let condition = self.negated_value(branch.a)
  self.nodes[guard_id] = Node::new(If, condition, effects, -1, 0)
  self.nodes[following_id] = Node::new(Return, -1, 0, 0, 0)
  true
}

///|
/// Follow existing lexical blocks only. A finally, loop, switch or conditional
/// can alter completion and is deliberately outside this rule.
fn Program::has_bare_return_tail(self : Program, id : Int32) -> Bool {
  for block_id = id {
    let block = self.nodes[block_id]
    guard block.kind is Block && block.b > 0 else { break false }
    let tail_id = self.edges[block.a + block.b - 1]
    let tail = self.nodes[tail_id]
    match tail.kind {
      Return => break tail.a < 0
      Block => continue tail_id
      _ => break false
    }
  }
}

///|
/// Called only after has_bare_return_tail accepted the chain. Copy each block
/// on that path so other AST owners retain their original statement lists.
fn Program::without_bare_return_tail(self : Program, id : Int32) -> Int32 {
  let block = self.nodes[id]
  let prefix_count = block.b - 1
  let tail_id = self.edges[block.a + prefix_count]
  let tail = self.nodes[tail_id]
  if tail.kind is Return {
    return self.node(Block, block.a, prefix_count, block.c, block.d)
  }
  let trimmed_tail = self.without_bare_return_tail(tail_id)
  if self.nodes[trimmed_tail].b == 0 {
    // This nested block contained only the removed return and empty blocks.
    return self.node(Block, block.a, prefix_count, block.c, block.d)
  }
  let offset = self.edges.length()
  for index in 0..