///|
priv enum ClassPrefix {
  Plain
  Static
  Getter
  Setter
  Async
} derive(Eq)

///|
#valtype
priv struct ClassKey {
  node : Int
  computed : Bool
  prefix : ClassPrefix
}

///|
fn Parser::class_key(self : Parser) -> ClassKey raise ParseError {
  let computed = self.lexer.token == LeftBracket
  let prefix : ClassPrefix = match self.lexer.keyword {
    Static => ClassPrefix::Static
    Get => Getter
    Set => Setter
    Async => ClassPrefix::Async
    _ => Plain
  }
  let node = self.property_key()
  { node, computed, prefix, }
}

///|
fn ClassKey::flags(self : ClassKey, is_static : Bool) -> Int {
  (if is_static { CLASS_MEMBER_STATIC } else { 0 }) |
  (if self.computed { CLASS_MEMBER_COMPUTED } else { 0 })
}

///|
fn Parser::class_method(
  self : Parser,
  key : ClassKey,
  is_static : Bool,
  is_async : Bool,
  is_generator : Bool,
  kind : Int,
) -> Int raise ParseError {
  let function = self.function_tail(-1, is_async, is_generator, false)
  self.program.node(Method, key.node, function, kind, key.flags(is_static))
}

///|
/// Contextual modifiers and computed keys are consumed once, without a parser
/// probe. Only async requires the following key to be on the same line.
fn Parser::class_member(
  self : Parser,
  members : PendingEdges,
) -> Unit raise ParseError {
  let is_generator = self.eat(Asterisk)
  let key = self.class_key()
  guard key.is_static_modifier(self.lexer.token, is_generator) else {
    members.push(self.class_member_tail(key, false, is_generator))
    return
  }
  guard self.lexer.token != LeftBrace else {
    members.push(self.class_static_block())
    return
  }
  let is_generator = self.eat(Asterisk)
  let key = self.class_key()
  members.push(self.class_member_tail(key, true, is_generator))
}

///|
fn ClassKey::is_static_modifier(
  self : ClassKey,
  following : Token,
  is_generator : Bool,
) -> Bool {
  !is_generator &&
  self.prefix == Static &&
  following != LeftParenthesis &&
  following != Assignment &&
  following != Semicolon &&
  following != RightBrace
}

///|
fn Parser::class_static_block(self : Parser) -> Int raise ParseError {
  let outer_scope = self.scope
  self.scope = self.program.push_scope(outer_scope, scope_function)
  let body = self.block()
  self.scope = outer_scope
  self.program.node(ClassStaticBlock, body, 0, 0, 0)
}

///|
fn Parser::class_member_tail(
  self : Parser,
  key : ClassKey,
  is_static : Bool,
  is_generator : Bool,
) -> Int raise ParseError {
  let following = self.lexer.token
  guard !is_generator && following != LeftParenthesis else {
    return self.class_method(
      key,
      is_static,
      false,
      is_generator,
      METHOD_ORDINARY,
    )
  }
  let property_follows = following == Identifier ||
    following == StringLiteral ||
    following == Number ||
    following == LeftBracket
  let is_accessor = key.prefix == Getter || key.prefix == Setter
  let is_async = key.prefix == Async && !self.lexer.newline_before
  let has_method_prefix = (is_accessor && property_follows) ||
    (is_async && (property_follows || following == Asterisk))
  guard has_method_prefix else { return self.class_field(key, is_static) }
  self.prefixed_class_method(key.prefix, is_static, is_async)
}

///|
fn Parser::prefixed_class_method(
  self : Parser,
  prefix : ClassPrefix,
  is_static : Bool,
  is_async : Bool,
) -> Int raise ParseError {
  let is_generator = is_async && self.eat(Asterisk)
  let key = self.class_key()
  guard self.lexer.token == LeftParenthesis else {
    raise self.unexpected("method parameter list")
  }
  let kind = match prefix {
    Getter => METHOD_GETTER
    Setter => METHOD_SETTER
    _ => METHOD_ORDINARY
  }
  self.class_method(key, is_static, is_async, is_generator, kind)
}

///|
fn Parser::class_field(
  self : Parser,
  key : ClassKey,
  is_static : Bool,
) -> Int raise ParseError {
  let initializer = if self.eat(Assignment) {
    self.assignment_with_in()
  } else {
    -1
  }
  let field_terminated = self.lexer.token == Semicolon ||
    self.lexer.token == RightBrace ||
    self.lexer.newline_before
  guard field_terminated else {
    raise self.unexpected("class field terminator")
  }
  self.eat_semicolon()
  self.program.node(ClassField, key.node, initializer, 0, key.flags(is_static))
}