///|
priv enum NodeKind {
  Identifier
  NumberLiteral
  ZeroLiteral
  StringLiteral
  RegexLiteral
  TemplateNoSubstitution
  Template
  TaggedTemplate
  BooleanLiteral
  NullLiteral
  This
  Super
  ArrayLiteral
  ObjectLiteral
  Property
  PatternIdentifier
  PatternArray
  PatternObject
  PatternProperty
  PatternRest
  PatternDefault
  CoverDefault
  FunctionExpression
  FunctionDeclaration
  ArrowExpression
  ClassExpression
  ClassDeclaration
  Unary
  Update
  Binary
  Logical
  Assignment
  Conditional
  Call
  New
  Member
  Index
  Sequence
  Spread
  Parenthesized
  EvaluatedValue
  ExpressionStatement
  Block
  VariableDeclaration
  Declarator
  If
  For
  ForIn
  ForOf
  While
  DoWhile
  Return
  Throw
  Break
  Continue
  Labeled
  Switch
  SwitchCase
  Try
  Catch
  Debugger
  Empty
  Elided
  With
  Method
  ClassField
  ClassStaticBlock
  ModuleStatement
} derive(Eq, Debug)

///|
extend NodeKind with @debug.Debug::{to_repr}

///|
priv enum Operator {
  Assignment
  Add
  Subtract
  Multiply
  Divide
  Modulo
  Exponent
  ShiftLeft
  ShiftRight
  UnsignedShiftRight
  LessThan
  GreaterThan
  LessThanOrEqual
  GreaterThanOrEqual
  Equal
  NotEqual
  StrictlyEqual
  StrictlyNotEqual
  BitwiseAnd
  BitwiseOr
  BitwiseXor
  LogicalAnd
  LogicalOr
  NullishCoalescing
  In
  InstanceOf
  LogicalNot
  BitwiseNot
  Plus
  Minus
  TypeOf
  Void
  Delete
  Await
  Yield
  YieldStar
  Increment
  Decrement
} derive(Eq)

///|
#valtype
priv struct Node {
  kind : NodeKind
  a : Int32
  b : Int32
  c : Int32
  d : Int32
}

///|
fn Node::new(
  kind : NodeKind,
  a : Int32,
  b : Int32,
  c : Int32,
  d : Int32,
) -> Node {
  { kind, a, b, c, d, }
}

///|
const DECLARATION_VAR : Int32 = 0

///|
const DECLARATION_LET : Int32 = 1

///|
const DECLARATION_CONST : Int32 = 2

///|
const METHOD_ORDINARY : Int32 = 0

///|
const METHOD_GETTER : Int32 = 1

///|
const METHOD_SETTER : Int32 = 2

///|
const CLASS_MEMBER_STATIC : Int32 = 1

///|
const CLASS_MEMBER_COMPUTED : Int32 = 2

///|
const PROPERTY_COMPUTED : Int32 = 1

///|
const PROPERTY_SHORTHAND : Int32 = 1

///|
const CHAIN_OPTIONAL : Int32 = 1

///|
const MEMBER_NEW_TARGET : Int32 = 2

///|
const UPDATE_PREFIX : Int32 = 1

///|
const UPDATE_POSTFIX : Int32 = 0

///|
#valtype
priv struct Function {
  name : Int32
  parameters_offset : Int32
  parameters_count : Int32
  body : Int32
  flags : Int32
}

///|
const FUNCTION_ASYNC : Int32 = 1

///|
const FUNCTION_GENERATOR : Int32 = 2

///|
const FUNCTION_ARROW : Int32 = 4

///|
const FUNCTION_EXPRESSION_BODY : Int32 = 8

///|
#valtype
priv struct Class {
  name : Int32
  superclass : Int32
  members_offset : Int32
  members_count : Int32
}

///|
#valtype
priv struct Reference {
  scope : Int32
  name_start : Int32
  name_end : Int32
  name : Int32
  symbol : Int32
}

///|
#valtype
priv struct Symbol {
  name : Int32
  scope : Int32
  flags : Int32
  link : Int32
  use_count : Int32
  name_slot : Int32
}

///|
const SYMBOL_PRESERVED : Int32 = 1

///|
const SYMBOL_ELIDED : Int32 = 2

///|
#valtype
priv struct Scope {
  parent : Int32
  kind : Int32
}

///|
const SCOPE_MODULE : Int32 = 0

///|
const SCOPE_FUNCTION : Int32 = 1

///|
const SCOPE_BLOCK : Int32 = 2

///|
const SCOPE_CATCH : Int32 = 3

///|
const SCOPE_CLASS : Int32 = 4

///|
const SCOPE_PARAMETERS : Int32 = 5

///|
const SCOPE_ARROW_PARAMETERS : Int32 = 6

///|
const SCOPE_TRANSPARENT : Int32 = 7

///|
const SCOPE_SWITCH : Int32 = 8

///|
priv struct Program {
  source : String
  nodes : Array[Node]
  edges : Array[Int32]
  references : Array[Reference]
  symbols : Array[Symbol]
  scopes : Array[Scope]
  scope_members : Array[Map[Int32, Int32]]
  functions : Array[Function]
  classes : Array[Class]
  operators : Array[Operator]
  modules : Array[ModuleStatement]
  mut root : Int32
  preserved_names : Map[Int32, Bool]
  names : Array[String]
  generated_names : Array[String]
  identifier_ids : IdentifierTable
  mut has_dynamic_scope : Bool
  mut cover_defaults : Int32
  character_counts : FixedArray[Int32]
  mut alphabet : NameAlphabet
  property_names : Map[Int32, String]
  member_names : Map[Int32, String]
  property_reserved : @hashset.HashSet[String]
}

///|
fn Program::new(source : String) -> Program {
  let program : Program = {
    source,
    nodes: [],
    edges: [],
    references: [],
    symbols: [],
    scopes: [],
    scope_members: [],
    functions: [],
    classes: [],
    operators: [],
    modules: [],
    root: -1,
    preserved_names: {},
    names: ["arguments", "eval", "__proto__", "undefined"],
    generated_names: [],
    identifier_ids: IdentifierTable::new(),
    has_dynamic_scope: false,
    cover_defaults: 0,
    character_counts: FixedArray::make(ASCII_CHARACTER_COUNT, 0),
    alphabet: { first: NAME_FIRST, rest: NAME_REST, },
    property_names: {},
    member_names: {},
    property_reserved: @hashset.HashSet([]),
  }
  ignore(program.push_scope(-1, SCOPE_MODULE))
  program
}

///|
fn Program::node(
  self : Program,
  kind : NodeKind,
  a : Int32,
  b : Int32,
  c : Int32,
  d : Int32,
) -> Int32 {
  self.nodes.push(Node::new(kind, a, b, c, d))
  self.nodes.length() - 1
}

///|
fn Program::add_reference(self : Program, reference : Reference) -> Int32 {
  self.references.push(reference)
  self.references.length() - 1
}

///|
fn Program::add_operator(self : Program, operator : Operator) -> Int32 {
  self.operators.push(operator)
  self.operators.length() - 1
}

///|
fn Program::edge_list(self : Program, items : PendingEdges) -> EdgeRange {
  let offset = self.edges.length()
  let count = items.length()
  for item in items.view() {
    self.edges.push(item)
  }
  items.release()
  { offset, count, }
}

///|
fn Program::push_scope(self : Program, parent : Int32, kind : Int32) -> Int32 {
  self.scope_members.push(Map([]))
  self.scopes.push({ parent, kind, })
  self.scopes.length() - 1
}

///|
fn Program::add_symbol(
  self : Program,
  name_start : Int32,
  name_end : Int32,
  scope : Int32,
) -> Int32 raise ParseError {
  let name = self.identifier(name_start, name_end)
  guard self.scope_members[scope].get(name) is Some(id) else {
    let id = self.symbols.length()
    self.symbols.push({
      name,
      scope,
      flags: 0,
      link: -1,
      use_count: 0,
      name_slot: -1,
    })
    self.scope_members[scope][name] = id
    return id
  }
  id
}

///|
/// Symbols linked here must keep the same spelling even when JavaScript creates
/// distinct bindings (for example, a catch parameter and a hoisted var).
fn Program::symbol_root(self : Program, id : Int32) -> Int32 {
  for current = id {
    let next = self.symbols[current].link
    guard next >= 0 else { break current }
    continue next
  }
}

///|
fn Program::link_symbols(self : Program, left : Int32, right : Int32) -> Unit {
  let left_root = self.symbol_root(left)
  let right_root = self.symbol_root(right)
  guard left_root != right_root else { return }
  self.symbols[right_root] = { ..self.symbols[right_root], link: left_root, }
}

///|
const NAME_ARGUMENTS : Int32 = 0

///|
const NAME_EVAL : Int32 = 1

///|
const NAME_PROTO : Int32 = 2

///|
const NAME_UNDEFINED : Int32 = 3

///|
/// Intern source views without copying ordinary identifiers on each reference.
/// Escaped spellings and literal spellings share the same canonical identity.
fn Program::identifier(
  self : Program,
  start : Int32,
  end : Int32,
) -> Int32 raise ParseError {
  let spelling = self.source[start:end]
  let hash = identifier_hash(spelling)
  guard self.identifier_ids.get_hashed(spelling, hash) is Some(id) else {
    return self.intern_identifier(spelling, hash)
  }
  id
}

///|
fn Program::intern_identifier(
  self : Program,
  spelling : StringView,
  hash : Int32,
) -> Int32 raise ParseError {
  let start = spelling.start_offset()
  let end = start + spelling.length()
  let escaped = spelling.contains_code_unit('\\')
  let name = if escaped {
    identifier_name(self.source, start, end)
  } else {
    spelling.to_owned()
  }
  let canonical_id = if escaped {
    self.identifier_ids.get(name[:])
  } else {
    None
  }
  guard canonical_id is Some(id) else {
    let id = self.names.length()
    self.names.push(name)
    self.identifier_ids.insert_hashed(spelling, hash, id)
    if escaped {
      self.identifier_ids.insert(name[:], id)
    }
    return id
  }
  self.identifier_ids.insert_hashed(spelling, hash, id)
  id
}

///|
#valtype
priv struct EdgeRange {
  offset : Int32
  count : Int32
}