///|
fn NameAlphabet::short_name(alphabet : NameAlphabet, ordinal : Int32) -> 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 : Int32) -> 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(declarations)
  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[Int32]] {
  let declarations : Array[Array[Int32]] = Array::makei(self.scopes.length(), _ => {
    []
  })
  for index in 0.. Array[@hashset.HashSet[Int32]?] {
  let captured : Array[@hashset.HashSet[Int32]?] = Array::make(
    self.scopes.length(),
    None,
  )
  for reference in self.references {
    guard reference.symbol >= 0 else { continue }
    self.reserve_capture(
      captured,
      declarations,
      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[Int32] = []
  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
    })
    if captured[scope] is Some(capture_set) {
      for symbol in capture_set {
        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[Int32]?],
  declarations : Array[Array[Int32]],
  scope : Int32,
  symbol : Int32,
) -> Unit {
  let binding = program.symbols[symbol]
  guard (binding.flags & (SYMBOL_PRESERVED | SYMBOL_ELIDED)) == 0 else {
    return
  }
  for current = scope {
    guard current >= 0 && current != binding.scope else { break }
    // Empty scopes assign no names, but captures still constrain ancestors.
    if declarations[current].length() == 0 {
      continue program.scopes[current].parent
    }
    let capture_set = if captured[current] is Some(existing) {
      existing
    } else {
      let created = @hashset.HashSet([])
      captured[current] = Some(created)
      created
    }
    guard capture_set.add_and_check(symbol) else { break }
    continue program.scopes[current].parent
  }
}