///|
priv enum ImportBinding {
  Default(Int)
  Namespace(Int)
  // Original imported name node and local binding reference.
  Named(Int, Int)
}

///|
priv enum ModuleStatement {
  Import(Array[ImportBinding], Int, Int)
  // Local expression/original re-export name, followed by the public name.
  ExportNames(Array[(Int, Int)], Int, Int)
  ExportAll(Int, Int, Int)
  ExportDeclaration(Int)
  ExportDefault(Int)
}

///|
fn Parser::module_node(self : Parser, statement : ModuleStatement) -> Int {
  let index = self.program.modules.length()
  self.program.modules.push(statement)
  self.program.node(ModuleStatement, index, 0, 0, 0)
}

///|
fn Parser::expect_keyword(
  self : Parser,
  keyword : Keyword,
) -> Unit raise ParseError {
  guard self.lexer.keyword == keyword else {
    raise self.unexpected(keyword.spelling())
  }
  self.advance()
}

///|
fn Parser::module_name(self : Parser) -> Int raise ParseError {
  guard self.lexer.token == Identifier || self.lexer.token == StringLiteral else {
    raise self.unexpected("module name")
  }
  self.property_key()
}

///|
fn Parser::import_binding(self : Parser) -> Int raise ParseError {
  guard self.lexer.token == Identifier else {
    raise self.unexpected("import binding")
  }
  let reference = self.declare_symbol(0, self.lexer.start, self.lexer.end)
  self.advance()
  reference
}

///|
fn Parser::module_source(self : Parser) -> Int raise ParseError {
  guard self.lexer.token == StringLiteral else {
    raise self.unexpected("module source string")
  }
  self.property_key()
}

///|
fn Parser::import_attributes(self : Parser) -> Int raise ParseError {
  guard self.lexer.keyword == Keyword::With else { return -1 }
  self.advance()
  self.object_literal()
}

///|
fn Parser::parse_import(self : Parser) -> Int raise ParseError {
  guard self.scope == 0 else {
    raise self.syntax_error("import must be at module top level")
  }
  self.expect_keyword(Keyword::Import)
  let bindings = if self.lexer.token == StringLiteral {
    []
  } else {
    self.import_bindings()
  }
  let source = self.module_source()
  let attributes = self.import_attributes()
  self.eat_semicolon()
  self.module_node(Import(bindings, source, attributes))
}

///|
fn Parser::import_bindings(
  self : Parser,
) -> Array[ImportBinding] raise ParseError {
  let bindings : Array[ImportBinding] = []
  let has_default = self.lexer.token == Identifier
  if has_default {
    bindings.push(Default(self.import_binding()))
  }
  let has_more = !has_default || self.eat(Comma)
  if has_more {
    if self.eat(Asterisk) {
      self.expect_keyword(Keyword::As)
      bindings.push(Namespace(self.import_binding()))
    } else {
      self.named_import_bindings(bindings)
    }
  }
  self.expect_keyword(Keyword::From)
  bindings
}

///|
fn Parser::named_import_bindings(
  self : Parser,
  bindings : Array[ImportBinding],
) -> Unit raise ParseError {
  self.expect(LeftBrace)
  while self.lexer.token != RightBrace {
    let imported = self.module_name()
    let reference = if self.lexer.keyword == Keyword::As {
      self.advance()
      self.import_binding()
    } else {
      let name = self.program.nodes[imported]
      let first_character = self.program.source[name.a]
      guard first_character != '\'' && first_character != '"' else {
        raise self.syntax_error("string import name requires a local alias")
      }
      self.declare_symbol(0, name.a, name.b)
    }
    bindings.push(Named(imported, reference))
    guard self.eat(Comma) else { break }
  }
  self.expect(RightBrace)
}

///|
fn Parser::parse_export(self : Parser) -> Int raise ParseError {
  guard self.scope == 0 else {
    raise self.syntax_error("export must be at module top level")
  }
  self.expect_keyword(Keyword::Export)
  let statement = match self.lexer.token {
    _ if self.lexer.keyword == Keyword::Default => self.default_export()
    Asterisk => self.namespace_export()
    LeftBrace => self.named_export()
    _ => self.export_declaration()
  }
  self.module_node(statement)
}

///|
fn Parser::default_export(self : Parser) -> ModuleStatement raise ParseError {
  self.advance()
  let is_declaration = self.lexer.keyword == Keyword::Function ||
    self.lexer.keyword == Keyword::Class ||
    (
      self.lexer.keyword == Keyword::Async &&
      self.peek_is_keyword(Keyword::Function)
    )
  guard !is_declaration else { return ExportDefault(self.statement()) }
  let body = self.assignment_with_in()
  self.eat_semicolon()
  ExportDefault(body)
}

///|
fn Parser::namespace_export(self : Parser) -> ModuleStatement raise ParseError {
  self.advance()
  let export_name = if self.lexer.keyword == Keyword::As {
    self.advance()
    self.module_name()
  } else {
    -1
  }
  self.expect_keyword(Keyword::From)
  let source = self.module_source()
  let attributes = self.import_attributes()
  self.eat_semicolon()
  ExportAll(export_name, source, attributes)
}

///|
fn Parser::named_export(self : Parser) -> ModuleStatement raise ParseError {
  self.advance()
  let names : Array[(Int, Int)] = []
  while self.lexer.token != RightBrace {
    let source_name = self.module_name()
    let exported = if self.lexer.keyword == Keyword::As {
      self.advance()
      self.module_name()
    } else {
      source_name
    }
    names.push((source_name, exported))
    guard self.eat(Comma) else { break }
  }
  self.expect(RightBrace)
  let source = if self.lexer.keyword == Keyword::From {
    self.advance()
    self.module_source()
  } else {
    -1
  }
  let specifiers = names.map(pair => {
    let (source_name, exported) = pair
    guard source < 0 else { return pair }
    let name = self.program.nodes[source_name]
    let reference = self.add_reference(0, name.a, name.b)
    (self.program.node(Identifier, reference, 0, 0, 0), exported)
  })
  let attributes = if source >= 0 { self.import_attributes() } else { -1 }
  self.eat_semicolon()
  ExportNames(specifiers, source, attributes)
}

///|
fn Parser::export_declaration(
  self : Parser,
) -> ModuleStatement raise ParseError {
  let body = self.statement()
  let declaration_kind = self.program.nodes[body].kind
  guard declaration_kind == VariableDeclaration ||
    declaration_kind == FunctionDeclaration ||
    declaration_kind == ClassDeclaration else {
    raise self.unexpected("export declaration")
  }
  self.preserve_exported_names(body)
  ExportDeclaration(body)
}

///|
/// Direct export declarations expose their binding spelling as the public name.
fn Parser::preserve_exported_names(self : Parser, id : Int) -> Unit {
  let node = self.program.nodes[id]
  match node.kind {
    PatternIdentifier => {
      let symbol = self.program.references[node.a].symbol
      self.program.symbols[symbol] = {
        ..self.program.symbols[symbol],
        flags: symbol_preserved,
      }
    }
    PatternArray | PatternObject =>
      for index in 0..= 0 else { continue }
        self.preserve_exported_names(item)
      }
    PatternProperty => self.preserve_exported_names(node.b)
    PatternRest | PatternDefault | Declarator =>
      self.preserve_exported_names(node.a)
    VariableDeclaration =>
      for index in 0.. {
      let reference = if node.kind == FunctionDeclaration {
        self.program.functions[node.a].name
      } else {
        self.program.classes[node.a].name
      }
      guard reference >= 0 else { return }
      let symbol = self.program.references[reference].symbol
      self.program.symbols[symbol] = {
        ..self.program.symbols[symbol],
        flags: symbol_preserved,
      }
    }
    _ => ()
  }
}

///|
fn Printer::module_source(
  self : Printer,
  source : Int,
  attributes : Int,
) -> Unit {
  self.expression(source)
  guard attributes >= 0 else { return }
  self.text("with", true)
  self.expression(attributes)
}

///|
fn Printer::module_stmt(self : Printer, index : Int) -> Unit {
  match self.program.modules[index] {
    Import(bindings, source, attributes) =>
      self.import_statement(bindings, source, attributes)
    ExportNames(specifiers, source, attributes) =>
      self.named_export_statement(specifiers, source, attributes)
    ExportAll(export_name, source, attributes) =>
      self.namespace_export_statement(export_name, source, attributes)
    ExportDeclaration(body) => {
      self.text("export", true)
      self.statement(body)
    }
    ExportDefault(body) => {
      self.text("export default", true)
      self.expression(body)
      self.text(";", false)
    }
  }
}

///|
fn Printer::import_statement(
  self : Printer,
  bindings : Array[ImportBinding],
  source : Int,
  attributes : Int,
) -> Unit {
  self.text("import", true)
  self.import_bindings(bindings)
  if bindings.length() > 0 {
    self.text("from", true)
  }
  self.module_source(source, attributes)
  self.text(";", false)
}

///|
fn Printer::import_bindings(
  self : Printer,
  bindings : Array[ImportBinding],
) -> Unit {
  let mut has_default_binding = false
  let mut named_group_open = false
  for binding in bindings {
    match binding {
      Default(reference) => {
        self.reference(reference)
        has_default_binding = true
      }
      Namespace(reference) => {
        if has_default_binding {
          self.text(",", false)
        }
        self.text("*", false)
        self.text("as", true)
        self.reference(reference)
      }
      Named(imported, reference) => {
        if named_group_open || has_default_binding {
          self.text(",", false)
        }
        if !named_group_open {
          self.text("{", false)
          named_group_open = true
        }
        self.expression(imported)
        self.text("as", true)
        self.reference(reference)
      }
    }
  }
  guard named_group_open else { return }
  self.text("}", false)
}

///|
fn Printer::named_export_statement(
  self : Printer,
  specifiers : Array[(Int, Int)],
  source : Int,
  attributes : Int,
) -> Unit {
  self.text("export", true)
  self.text("{", false)
  for index in 0.. 0 {
      self.text(",", false)
    }
    let (source_name, exported) = specifiers[index]
    self.expression(source_name)
    self.text("as", true)
    self.expression(exported)
  }
  self.text("}", false)
  if source >= 0 {
    self.text("from", true)
    self.module_source(source, attributes)
  }
  self.text(";", false)
}

///|
fn Printer::namespace_export_statement(
  self : Printer,
  export_name : Int,
  source : Int,
  attributes : Int,
) -> Unit {
  self.text("export", true)
  self.text("*", false)
  if export_name >= 0 {
    self.text("as", true)
    self.expression(export_name)
  }
  self.text("from", true)
  self.module_source(source, attributes)
  self.text(";", false)
}