///|
fn Program::unwrapped_expression(self : Program, id : Int32) -> Int32 {
  for expression = id {
    guard expression >= 0 else { break expression }
    let node = self.nodes[expression]
    guard node.kind is Parenthesized else { break expression }
    continue node.a
  }
}

///|
/// These atoms keep their meaning without grouping. Strings can be directives;
/// let/async need grouping in some for headers. Optional chains are not atoms.
fn Program::compact_parenthesized(self : Program, id : Int32) -> Unit {
  let parenthesized = self.nodes[id]
  guard parenthesized.kind is Parenthesized else { return }
  let value = self.nodes[parenthesized.a]
  match value.kind {
    Identifier => {
      let name = self.names[self.references[value.a].name]
      guard name != "let" && name != "async" else { return }
    }
    NumberLiteral | BooleanLiteral | NullLiteral | This => ()
    _ => return
  }
  self.nodes[id] = value
}

///|
fn Program::is_undefined_value(self : Program, id : Int32) -> Bool {
  guard !self.has_dynamic_scope else { return false }
  let expression = self.unwrapped_expression(id)
  guard expression >= 0 else { return false }
  let node = self.nodes[expression]
  guard node.kind is Identifier else { return false }
  let reference = self.references[node.a]
  reference.symbol < 0 && reference.name == NAME_UNDEFINED
}

///|
fn Program::is_number_literal(self : Program, id : Int32) -> Bool {
  let expression = self.unwrapped_expression(id)
  let node = self.nodes[expression]
  node.kind is NumberLiteral && self.source[node.b - 1] != 'n'
}

///|
fn Program::is_zero_literal(self : Program, id : Int32) -> Bool {
  let expression = self.unwrapped_expression(id)
  let node = self.nodes[expression]
  node.kind is NumberLiteral &&
  node.b - node.a == 1 &&
  self.source[node.a] == '0'
}

///|
/// MoonBit emits decimal shift counts. Counts 1 through 31 make an unsigned
/// right shift fit in Int32; count zero must keep the outer signed conversion.
fn Program::is_positive_shift_count(self : Program, id : Int32) -> Bool {
  let expression = self.unwrapped_expression(id)
  let node = self.nodes[expression]
  guard node.kind is NumberLiteral else { return false }
  let length = node.b - node.a
  let first_digit = self.source[node.a]
  match length {
    1 => first_digit >= '1' && first_digit <= '9'
    2 => {
      let second_digit = self.source[node.a + 1]
      (
        (first_digit == '1' || first_digit == '2') &&
        second_digit >= '0' &&
        second_digit <= '9'
      ) ||
      (first_digit == '3' && (second_digit == '0' || second_digit == '1'))
    }
    _ => false
  }
}

///|
fn Program::has_signed_number_result(self : Program, id : Int32) -> Bool {
  let expression = self.unwrapped_expression(id)
  let node = self.nodes[expression]
  guard node.kind is Binary else { return false }
  match self.operators[node.a] {
    UnsignedShiftRight => self.is_positive_shift_count(node.c)
    ShiftLeft | ShiftRight | BitwiseAnd | BitwiseOr | BitwiseXor =>
      // A Number operand excludes a successful BigInt operation. Keeping the
      // inner operation also preserves coercions and mixed-type exceptions.
      self.is_number_literal(node.b) || self.is_number_literal(node.c)
    _ => false
  }
}

///|
/// Compare a plain decimal spelling with a decimal bound without parsing or
/// arithmetic overflow. Multi-digit leading zeroes may be legacy octal.
fn Program::decimal_integer_literal_within(
  self : Program,
  literal : Node,
  maximum : String,
) -> Bool {
  let length = literal.b - literal.a
  guard length > 0 && length <= maximum.length() else { return false }
  guard length == 1 || self.source[literal.a] != '0' else { return false }
  for index in literal.a.. limit_digit {
      return false
    }
  }
  true
}

///|
/// Run after reference resolution, as part of the shared postorder arena scan.
fn Program::compact_expression(self : Program, id : Int32) -> Unit {
  let node = self.nodes[id]
  match node.kind {
    Return if self.is_undefined_value(node.a) =>
      self.nodes[id] = { ..node, a: -1, }
    Unary if self.operators[node.a] is LogicalNot => {
      let value = self.simplified_negation(node.b)
      guard value >= 0 else { return }
      let result = self.nodes[value]
      self.nodes[id] = if result.kind
        is (Binary | Logical | Conditional | Sequence) {
        Node::new(Parenthesized, value, 0, 0, 0)
      } else {
        result
      }
    }
    Conditional => {
      let alternative = self.nodes[self.unwrapped_expression(node.c)]
      if alternative.kind is Conditional &&
        self.same_value_expression(node.b, alternative.b) {
        let condition = self.slot_logical(LogicalOr, node.a, alternative.a)
        let value = self.compact_choice(condition, node.b, alternative.c)
        self.nodes[id] = self.nodes[value]
        return
      }
      let consequent = self.nodes[self.unwrapped_expression(node.b)]
      guard consequent.kind is BooleanLiteral ||
        alternative.kind is BooleanLiteral else {
        return
      }
      let value = self.compact_choice(node.a, node.b, node.c)
      self.nodes[id] = self.nodes[value]
    }
    Binary if self.nodes[node.b].kind is NumberLiteral => {
      let maximum = match self.operators[node.a] {
        UnsignedShiftRight => "4294967295"
        BitwiseOr | ShiftRight => "2147483647"
        _ => return
      }
      guard self.is_zero_literal(node.c) &&
        self.decimal_integer_literal_within(self.nodes[node.b], maximum) else {
        return
      }
      self.nodes[id] = self.nodes[node.b]
    }
    Binary if self.operators[node.a]
      is (BitwiseOr | ShiftRight | UnsignedShiftRight) &&
      self.is_zero_literal(node.c) => {
      let inner = self.nodes[self.unwrapped_expression(node.b)]
      if inner.kind is Binary &&
        self.operators[inner.a] is (BitwiseOr | ShiftRight | UnsignedShiftRight) &&
        self.is_zero_literal(inner.c) {
        let operand = self.integer_coercion_operand(
          inner.b,
          self.operators[node.a],
        )
        self.nodes[id] = { ..node, b: operand, }
      } else if self.operators[node.a] is BitwiseOr &&
        self.has_signed_number_result(node.b) {
        // Preserve explicit parentheses when dropping the outer operator.
        self.nodes[id] = self.nodes[node.b]
      }
    }
    While => {
      let condition = self.nodes[self.unwrapped_expression(node.a)]
      guard condition.kind is BooleanLiteral && condition.a == 1 else { return }
      self.nodes[id] = Node::new(For, -1, -1, -1, node.b)
    }
    _ => ()
  }
}

///|
/// Keep the inner operand's grouping. A shift binds more tightly than a
/// bitwise operand, so (x | y | 0) >>> 0 must become (x | y) >>> 0.
fn Program::integer_coercion_operand(
  self : Program,
  id : Int32,
  operator : Operator,
) -> Int32 {
  let value = self.nodes[id]
  let needs_parentheses = match value.kind {
    Assignment | Conditional | Logical | Sequence | ArrowExpression => true
    Unary => self.operators[value.a] is (Yield | YieldStar)
    Binary if !(operator is BitwiseOr) =>
      !(self.operators[value.a]
      is (Add
      | Subtract
      | Multiply
      | Divide
      | Modulo
      | Exponent
      | ShiftLeft
      | ShiftRight
      | UnsignedShiftRight))
    _ => false
  }
  if needs_parentheses {
    self.node(Parenthesized, id, 0, 0, 0)
  } else {
    id
  }
}

///|
fn Program::negated_value(self : Program, id : Int32) -> Int32 {
  let simplified = self.simplified_negation(id)
  guard simplified < 0 else { return simplified }
  let node = self.nodes[self.unwrapped_expression(id)]
  let operand = if self.is_primary_value(id) ||
    node.kind is (NumberLiteral | NullLiteral) ||
    (node.kind is Unary && !(self.operators[node.a] is (Yield | YieldStar))) {
    id
  } else {
    self.node(Parenthesized, id, 0, 0, 0)
  }
  self.node(Unary, self.add_operator(LogicalNot), operand, 0, 0)
}

///|
fn Program::simplified_negation(self : Program, id : Int32) -> Int32 {
  let expression = self.unwrapped_expression(id)
  let node = self.nodes[expression]
  match node.kind {
    BooleanLiteral => return self.node(BooleanLiteral, 1 - node.a, 0, 0, 0)
    Unary if self.operators[node.a] is LogicalNot &&
      self.is_boolean_value(node.b) => return node.b
    Binary => {
      let opposite : Operator? = match self.operators[node.a] {
        Equal => Some(NotEqual)
        NotEqual => Some(Equal)
        StrictlyEqual => Some(StrictlyNotEqual)
        StrictlyNotEqual => Some(StrictlyEqual)
        _ => None
      }
      if opposite is Some(operator) {
        return self.node(Binary, self.add_operator(operator), node.b, node.c, 0)
      }
    }
    _ => ()
  }
  -1
}

///|
fn Program::remove_final_unit_return(
  self : Program,
  function_id : Int32,
) -> Unit {
  let function = self.functions[function_id]
  guard (function.flags & FUNCTION_EXPRESSION_BODY) == 0 else { return }
  self.trim_function_tail(function.body)
  let body = self.nodes[function.body]
  guard (function.flags & FUNCTION_ARROW) != 0 &&
    body.kind is Block &&
    body.b == 1 else {
    return
  }
  let result = self.nodes[self.edges[body.a]]
  guard result.kind is Return && result.a >= 0 else { return }
  let value = self.nodes[result.a]
  let expression = if value.kind is (Sequence | ObjectLiteral) {
    self.node(Parenthesized, result.a, 0, 0, 0)
  } else {
    result.a
  }
  self.functions[function_id] = {
    ..function,
    body: expression,
    flags: function.flags | FUNCTION_EXPRESSION_BODY,
  }
}

///|
/// Falling off a function returns undefined. Only follow tails that execute
/// once; a return in a loop, switch or finally has different control flow.
fn Program::trim_function_tail(self : Program, id : Int32) -> Unit {
  let node = self.nodes[id]
  match node.kind {
    Return if node.a < 0 => self.nodes[id] = Node::new(Empty, 0, 0, 0, 0)
    Block if node.b > 0 => {
      let tail = self.edges[node.a + node.b - 1]
      self.trim_function_tail(tail)
      if self.is_empty_statement(tail) {
        self.nodes[id] = { ..node, b: node.b - 1, }
      }
    }
    If => {
      self.trim_function_tail(node.b)
      if node.c >= 0 {
        self.trim_function_tail(node.c)
      }
      let alternative_empty = node.c < 0 || self.is_empty_statement(node.c)
      if self.is_empty_statement(node.b) {
        if alternative_empty {
          let condition = self.node(Parenthesized, node.a, 0, 0, 0)
          self.nodes[id] = Node::new(ExpressionStatement, condition, 0, 0, 0)
        } else {
          let inverted = self.negated_value(node.a)
          self.nodes[id] = Node::new(If, inverted, node.c, -1, 0)
        }
      } else if alternative_empty {
        self.nodes[id] = { ..node, c: -1, }
      }
    }
    _ => ()
  }
}

///|
fn Program::is_empty_statement(self : Program, id : Int32) -> Bool {
  let node = self.nodes[id]
  node.kind is Empty || (node.kind is Block && node.b == 0)
}