///|
fn NameAlphabet::short_name(alphabet : NameAlphabet, ordinal : Int) -> String {
  let builder = StringBuilder()
  let first_radix = alphabet.first.length()
  let rest_radix = alphabet.rest.length()
  builder.write_char(alphabet.first.get_char(ordinal % first_radix).unwrap())
  for remaining = ordinal / first_radix
      remaining > 0
      remaining = (remaining - 1) / rest_radix {
    builder.write_char(
      alphabet.rest.get_char((remaining - 1) % rest_radix).unwrap(),
    )
  }
  builder.to_string()
}

///|
let keywords : @hashset.HashSet[String] = @hashset.HashSet([
  "break", "case", "catch", "class", "const", "continue", "debugger", "default",
  "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for",
  "function", "if", "import", "in", "instanceof", "new", "null", "return", "super",
  "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with",
  "yield", "let", "static", "await", "async", "get", "set", "of", "as", "from", "arguments",
  "eval", "undefined", "NaN", "Infinity", "implements", "interface", "package", "private",
  "protected", "public",
])

///|
fn Program::symbol_name(program : Program, symbol : Int) -> String {
  program.names[program.symbols[symbol].name]
}

///|
fn Program::mangle(program : Program, rename_top_level : Bool) -> Unit {
  guard !program.has_dynamic_scope else { return }
  let reserved = program.reserve_symbol_names(rename_top_level)
  program.alphabet = program.name_alphabet()
  let declarations = program.renameable_declarations()
  let captured = program.captured_symbols()
  program.assign_symbol_names(reserved, declarations, captured)
}

///|
fn Program::reserve_symbol_names(
  self : Program,
  rename_top_level : Bool,
) -> @hashset.HashSet[String] {
  let reserved = keywords.copy()
  for reference in self.references {
    guard reference.symbol < 0 else { continue }
    reserved.add(self.names[reference.name])
  }
  for index in 0.. Array[Array[Int]] {
  let declarations : Array[Array[Int]] = Array::makei(self.scopes.length(), _ => {
    []
  })
  for index in 0.. Array[@hashset.HashSet[Int]] {
  let captured : Array[@hashset.HashSet[Int]] = Array::makei(
    self.scopes.length(),
    _ => @hashset.HashSet([]),
  )
  for reference in self.references {
    guard reference.symbol >= 0 else { continue }
    self.reserve_capture(captured, reference.scope, reference.symbol)
  }
  // Parameters copied into a function-body environment and linked catch/var
  // declarations also constrain names even when the binding is never read.
  for scope in 0.. Unit {
  let candidates = self.generated_names
  let blocked : Array[Int] = []
  let generator = NameGenerator::new(self.alphabet, reserved)
  // Scope allocation is preorder; ancestors receive names before descendants.
  for scope in 0.. 0 else { continue }
    targets.sort_by((left, right) => {
      let frequency = self.symbols[right].use_count -
        self.symbols[left].use_count
      guard frequency == 0 else { return frequency }
      left - right
    })
    for symbol in captured[scope] {
      let slot = self.symbols[symbol].name_slot
      guard slot >= 0 else { continue }
      blocked[slot] = scope
    }
    let mut slot = 0
    for index in targets {
      while slot < blocked.length() && blocked[slot] == scope {
        slot += 1
      }
      while slot >= candidates.length() {
        candidates.push(generator.next_name())
        blocked.push(-1)
      }
      self.symbols[index] = { ..self.symbols[index], name_slot: slot, }
      slot += 1
    }
  }
}

///|
/// A binding's name must remain visible on the path from a use or declaration
/// occurrence to its owning scope. Disjoint subtrees may reuse that name.
fn Program::reserve_capture(
  program : Program,
  captured : Array[@hashset.HashSet[Int]],
  scope : Int,
  symbol : Int,
) -> Unit {
  let binding = program.symbols[symbol]
  guard (binding.flags & symbol_preserved) == 0 else { return }
  for current = scope {
    guard current >= 0 && current != binding.scope else { break }
    guard captured[current].add_and_check(symbol) else { break }
    continue program.scopes[current].parent
  }
}