// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:_fe_analyzer_shared/src/messages/codes.dart';
import 'package:_fe_analyzer_shared/src/parser/parser.dart';
import 'package:_fe_analyzer_shared/src/parser/quote.dart';
import 'package:_fe_analyzer_shared/src/parser/stack_listener.dart';
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:_fe_analyzer_shared/src/util/null_value.dart';
import 'package:_fe_analyzer_shared/src/util/value_kind.dart';

import 'arguments.dart';
import 'elements.dart';
import 'expressions.dart';
import 'formal_parameters.dart';
import 'proto.dart';
import 'record_fields.dart';
import 'references.dart';
import 'scope.dart';
import 'string_literal_parts.dart';
import 'type_annotations.dart';

/// Parser listener that can create [Expression] node for metadata annotations
/// and constant expressions.
class AnnotationsListener extends StackListener {
  @override
  final bool isDartLibrary;

  @override
  final Uri uri;

  final Scope _initialScope;

  final References _references;

  final bool delayLookup;

  AnnotationsListener(this.uri, this._initialScope, this._references,
      {required this.delayLookup, required this.isDartLibrary});

  final List<FunctionTypeParameterScope> _typeParameterScopes = [];

  Scope get _scope =>
      _typeParameterScopes.isEmpty ? _initialScope : _typeParameterScopes.last;

  @override
  void beginMetadata(Token token) {}

  @override
  void endMetadata(Token beginToken, Token? periodBeforeName, Token endToken) {
    assert(checkState(beginToken, [
      /*arguments*/ _ValueKinds._ArgumentsOrNull,
      /*suffix*/ if (periodBeforeName != null) _ValueKinds._IdentifierProto,
      /*type arguments*/ _ValueKinds._TypeAnnotationsOrNull,
      /*type*/ _ValueKinds._Proto,
    ]));
    List<Argument>? arguments = pop(_NullValues.Arguments) as List<Argument>?;
    IdentifierProto? identifier =
        periodBeforeName != null ? pop() as IdentifierProto : null;
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    Proto proto = pop() as Proto;
    push(proto
        .instantiate(typeArguments)
        .apply(identifier)
        .invoke(arguments)
        .toExpression());
  }

  @override
  void endMetadataStar(int count) {
    assert(checkState(null, repeatedKind(_ValueKinds._Expression, count)));
    List<Expression> expressions = new List.filled(count, _dummyExpression);
    while (--count >= 0) {
      expressions[count] = pop() as Expression;
    }
    push(expressions);
  }

  @override
  void handleIdentifier(Token token, IdentifierContext context) {
    switch (context) {
      case IdentifierContext.metadataReference:
      case IdentifierContext.typeReference:
      case IdentifierContext.prefixedTypeReference:
      case IdentifierContext.expression:
      case IdentifierContext.constructorReference:
        String name = token.lexeme;
        if (delayLookup) {
          push(new UnresolvedIdentifier(_scope, name));
        } else {
          push(_scope.lookup(name));
        }
      case IdentifierContext.typeVariableDeclaration:
        String name = token.lexeme;
        push(_typeParameterScopes.last.declareTypeParameter(name));
      case IdentifierContext.metadataContinuation:
      case IdentifierContext.metadataContinuationAfterTypeArguments:
      case IdentifierContext.typeReferenceContinuation:
      case IdentifierContext.literalSymbol:
      case IdentifierContext.expressionContinuation:
      case IdentifierContext.constructorReferenceContinuation:
      case IdentifierContext.constructorReferenceContinuationAfterTypeArguments:
      case IdentifierContext.namedRecordFieldReference:
      case IdentifierContext.namedArgumentReference:
      case IdentifierContext.formalParameterDeclaration:
      case IdentifierContext.recordFieldDeclaration:
        push(new IdentifierProto(token.lexeme));
      default:
        throw new UnsupportedError("Unsupported context $context");
    }
  }

  @override
  void endConstructorReference(Token start, Token? periodBeforeName,
      Token endToken, ConstructorReferenceContext constructorReferenceContext) {
    assert(checkState(start, [
      if (periodBeforeName != null)
        /* constructor name */ _ValueKinds._IdentifierProto,
      /* type arguments */ _ValueKinds._TypeAnnotationsOrNull,
      /* (qualified) name before type arguments */ _ValueKinds._Proto,
    ]));
    IdentifierProto? constructorName =
        periodBeforeName != null ? pop() as IdentifierProto : null;
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    Proto className = pop() as Proto;
    push(className.instantiate(typeArguments).apply(constructorName));
  }

  @override
  void endConstExpression(Token token) {
    assert(checkState(token, [
      /* arguments */ _ValueKinds._Arguments,
      /* constructor reference */ _ValueKinds._Proto,
    ]));
    List<Argument> arguments = pop() as List<Argument>;
    Proto constructorReference = pop() as Proto;
    push(constructorReference.invoke(arguments));
  }

  @override
  void handleLiteralList(
      int count, Token leftBracket, Token? constKeyword, Token rightBracket) {
    assert(checkState(leftBracket, [
      ...repeatedKind(_ValueKinds._ElementOrProto, count),
      _ValueKinds._TypeAnnotationsOrNull,
    ]));
    List<Element> elements = new List.filled(count, _dummyElement);
    while (--count >= 0) {
      elements[count] = _popElementOrProto();
    }
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    push(new ExpressionProto(
        new ListLiteral(typeArguments ?? const [], elements)));
  }

  Element _popElementOrProto() {
    Object? element = pop();
    if (element is Element) {
      return element;
    } else {
      return new ExpressionElement((element as Proto).toExpression(),
          isNullAware: false);
    }
  }

  Expression _popExpression() {
    return (pop() as Proto).toExpression();
  }

  Argument _popArgument() {
    Object? argument = pop();
    if (argument is Argument) {
      return argument;
    } else {
      return new PositionalArgument((argument as Proto).toExpression());
    }
  }

  RecordField _popRecordField() {
    Object? field = pop();
    if (field is RecordField) {
      return field;
    } else {
      return new RecordPositionalField((field as Proto).toExpression());
    }
  }

  @override
  void handleLiteralSetOrMap(
    int count,
    Token leftBrace,
    Token? constKeyword,
    Token rightBrace,
    bool hasSetEntry,
  ) {
    assert(checkState(leftBrace, [
      ...repeatedKind(_ValueKinds._ElementOrProto, count),
      _ValueKinds._TypeAnnotationsOrNull,
    ]));
    List<Element> elements = new List.filled(count, _dummyElement);
    while (--count >= 0) {
      elements[count] = _popElementOrProto();
    }
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    push(new ExpressionProto(
        new SetOrMapLiteral(typeArguments ?? const [], elements)));
  }

  @override
  void handleLiteralMapEntry(Token colon, Token endToken,
      {Token? nullAwareKeyToken, Token? nullAwareValueToken}) {
    assert(checkState(colon, [
      /* value */ _ValueKinds._Proto,
      /* key */ _ValueKinds._Proto,
    ]));
    Expression value = _popExpression();
    Expression key = _popExpression();
    push(new MapEntryElement(key, value,
        isNullAwareKey: nullAwareKeyToken != null,
        isNullAwareValue: nullAwareValueToken != null));
  }

  @override
  void handleSpreadExpression(Token spreadToken) {
    assert(checkState(spreadToken, [
      /* expression */ _ValueKinds._Proto,
    ]));
    Proto expression = pop() as Proto;
    push(new SpreadElement(expression.toExpression(),
        isNullAware: spreadToken.lexeme == '...?'));
  }

  @override
  void handleNullAwareElement(Token nullAwareToken) {
    assert(checkState(nullAwareToken, [
      /* expression */ _ValueKinds._Proto,
    ]));
    Proto expression = pop() as Proto;
    push(new ExpressionElement(expression.toExpression(), isNullAware: true));
  }

  @override
  void handleParenthesizedCondition(Token token, Token? case_, Token? when) {
    if (case_ != null) {
      throw new UnsupportedError(
          "handleParenthesizedCondition($token,$case_,$when");
    } else {
      assert(checkState(token, [_ValueKinds._Proto]));
    }
  }

  @override
  void endIfControlFlow(Token token) {
    assert(checkState(token, [
      /* then */ _ValueKinds._ElementOrProto,
      /* condition */ _ValueKinds._Proto
    ]));
    Element then = _popElementOrProto();
    Expression condition = _popExpression();
    push(new IfElement(condition, then));
  }

  @override
  void handleElseControlFlow(Token elseToken) {
    assert(checkState(elseToken, [
      /* otherwise */ unionOfKinds([_ValueKinds._Element, _ValueKinds._Proto]),
    ]));
  }

  @override
  void endIfElseControlFlow(Token token) {
    assert(checkState(token, [
      /* otherwise */ unionOfKinds([_ValueKinds._Element, _ValueKinds._Proto]),
      /* then */ unionOfKinds([_ValueKinds._Element, _ValueKinds._Proto]),
      /* condition */ _ValueKinds._Proto
    ]));
    Element otherwise = _popElementOrProto();
    Element then = _popElementOrProto();
    Expression condition = _popExpression();
    push(new IfElement(condition, then, otherwise));
  }

  @override
  void endConstLiteral(Token token) {
    assert(checkState(token, [_ValueKinds._Proto]));
  }

  @override
  void handleNamedRecordField(Token colon) {
    assert(checkState(colon, [
      /* expression */ _ValueKinds._Proto,
      /* name */ _ValueKinds._IdentifierProto,
    ]));
    Expression expression = _popExpression();
    IdentifierProto name = pop() as IdentifierProto;
    push(new RecordNamedField(name.text, expression));
  }

  @override
  void endRecordLiteral(Token token, int count, Token? constKeyword) {
    assert(checkState(
      token,
      /* fields */ repeatedKind(_ValueKinds._RecordFieldOrProto, count),
    ));
    List<RecordField> fields = new List.filled(count, _dummyRecordField);
    while (--count >= 0) {
      fields[count] = _popRecordField();
    }
    push(new ExpressionProto(new RecordLiteral(fields)));
  }

  @override
  void handleEndingBinaryExpression(Token token, Token endToken) {
    endBinaryExpression(token, endToken);
  }

  @override
  void endBinaryExpression(Token token, Token endToken) {
    assert(checkState(token, [
      /* right */ _ValueKinds._Proto,
      /* left */ _ValueKinds._Proto,
    ]));
    Proto right = pop() as Proto;
    Proto left = pop() as Proto;
    switch (token.lexeme) {
      case '.':
        IdentifierProto identifierProto = right as IdentifierProto;
        push(left.apply(identifierProto));
      case '?.':
        IdentifierProto identifierProto = right as IdentifierProto;
        push(left.apply(identifierProto, isNullAware: true));
      case '??':
        push(new ExpressionProto(
            new IfNull(left.toExpression(), right.toExpression())));
      case '||':
        push(new ExpressionProto(new LogicalExpression(
            left.toExpression(), LogicalOperator.or, right.toExpression())));
      case '&&':
        push(new ExpressionProto(new LogicalExpression(
            left.toExpression(), LogicalOperator.and, right.toExpression())));
      case '==':
        push(new ExpressionProto(new EqualityExpression(
            left.toExpression(), right.toExpression(),
            isNotEquals: false)));
      case '!=':
        push(new ExpressionProto(new EqualityExpression(
            left.toExpression(), right.toExpression(),
            isNotEquals: true)));
      case '>':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.greaterThan, right.toExpression())));
      case '>=':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.greaterThanOrEqual, right.toExpression())));
      case '<':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.lessThan, right.toExpression())));
      case '<=':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.lessThanOrEqual, right.toExpression())));
      case '<<':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.shiftLeft, right.toExpression())));
      case '>>':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.signedShiftRight, right.toExpression())));
      case '>>>':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.unsignedShiftRight, right.toExpression())));
      case '+':
        push(new ExpressionProto(new BinaryExpression(
            left.toExpression(), BinaryOperator.plus, right.toExpression())));
      case '-':
        push(new ExpressionProto(new BinaryExpression(
            left.toExpression(), BinaryOperator.minus, right.toExpression())));
      case '*':
        push(new ExpressionProto(new BinaryExpression(
            left.toExpression(), BinaryOperator.times, right.toExpression())));
      case '/':
        push(new ExpressionProto(new BinaryExpression(
            left.toExpression(), BinaryOperator.divide, right.toExpression())));
      case '~/':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.integerDivide, right.toExpression())));
      case '%':
        push(new ExpressionProto(new BinaryExpression(
            left.toExpression(), BinaryOperator.modulo, right.toExpression())));
      case '|':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.bitwiseOr, right.toExpression())));
      case '&':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.bitwiseAnd, right.toExpression())));
      case '^':
        push(new ExpressionProto(new BinaryExpression(left.toExpression(),
            BinaryOperator.bitwiseXor, right.toExpression())));
      default:
        throw new UnimplementedError("Binary operator '${token.lexeme}'.");
    }
  }

  @override
  void handleIsOperator(Token isOperator, Token? not) {
    assert(checkState(isOperator, [
      /* type */ _ValueKinds._TypeAnnotation,
      /* expression */ _ValueKinds._Proto,
    ]));
    TypeAnnotation type = pop() as TypeAnnotation;
    Expression expression = _popExpression();
    push(new ExpressionProto(new IsTest(expression, type, isNot: not != null)));
  }

  @override
  void handleAsOperator(Token operator) {
    assert(checkState(operator, [
      /* type */ _ValueKinds._TypeAnnotation,
      /* expression */ _ValueKinds._Proto,
    ]));
    TypeAnnotation type = pop() as TypeAnnotation;
    Expression expression = _popExpression();
    push(new ExpressionProto(new AsExpression(expression, type)));
  }

  @override
  void endIsOperatorType(Token operator) {
    // Do nothing.
  }

  @override
  void endAsOperatorType(Token operator) {
    // Do nothing.
  }

  @override
  void handleUnaryPrefixExpression(Token token) {
    assert(checkState(token, [
      /* expression */ _ValueKinds._Proto,
    ]));
    Expression expression = _popExpression();
    switch (token.lexeme) {
      case '-':
        push(new ExpressionProto(
            new UnaryExpression(UnaryOperator.minus, expression)));
      case '!':
        push(new ExpressionProto(
            new UnaryExpression(UnaryOperator.bang, expression)));
      case '~':
        push(new ExpressionProto(
            new UnaryExpression(UnaryOperator.tilde, expression)));
      default:
        throw new UnimplementedError("Unary operator '${token.lexeme}'.");
    }
  }

  @override
  void handleNonNullAssertExpression(Token bang) {
    assert(checkState(bang, [
      /* expression */ _ValueKinds._Proto,
    ]));
    Expression expression = _popExpression();
    push(new ExpressionProto(new NullCheck(expression)));
  }

  @override
  void handleQualified(Token period) {
    assert(checkState(period, [
      /* suffix */ _ValueKinds._IdentifierProto,
      /* prefix */ _ValueKinds._Proto,
    ]));
    IdentifierProto suffix = pop() as IdentifierProto;
    Proto prefix = pop() as Proto;
    push(prefix.apply(suffix));
  }

  @override
  void handleSend(Token beginToken, Token endToken) {
    assert(checkState(beginToken, [
      _ValueKinds._ArgumentsOrNull,
      _ValueKinds._TypeAnnotationsOrNull,
      _ValueKinds._Proto,
    ]));
    List<Argument>? arguments = pop(_NullValues.Arguments) as List<Argument>?;
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    Proto proto = pop() as Proto;
    assert(typeArguments == null || arguments != null,
        'Unexpected type argument application as send.');
    push(proto.instantiate(typeArguments).invoke(arguments));
  }

  @override
  void handleNoArguments(Token token) {
    push(_NullValues.Arguments);
  }

  @override
  void handleNamedArgument(Token colon) {
    assert(checkState(colon, [
      /* expression */ _ValueKinds._Proto,
      /* name */ _ValueKinds._IdentifierProto,
    ]));
    Expression expression = _popExpression();
    IdentifierProto name = pop() as IdentifierProto;
    push(new NamedArgument(name.text, expression));
  }

  @override
  void endArguments(int count, Token beginToken, Token endToken) {
    assert(checkState(
      beginToken,
      /* arguments */ repeatedKind(_ValueKinds._ArgumentOrProto, count),
    ));
    List<Argument> arguments = new List.filled(count, _dummyArgument);
    while (--count >= 0) {
      arguments[count] = _popArgument();
    }
    push(arguments);
  }

  @override
  void handleNoTypeArguments(Token token) {
    push(_NullValues.TypeAnnotations);
  }

  @override
  void endTypeArguments(int count, Token beginToken, Token endToken) {
    assert(checkState(
      beginToken,
      /* type arguments */ repeatedKind(_ValueKinds._TypeAnnotation, count),
    ));
    List<TypeAnnotation> typeArguments =
        new List.filled(count, _dummyTypeAnnotation);
    while (--count >= 0) {
      typeArguments[count] = pop() as TypeAnnotation;
    }
    push(typeArguments);
  }

  @override
  void handleType(Token beginToken, Token? questionMark) {
    assert(checkState(beginToken, [
      _ValueKinds._TypeAnnotationsOrNull,
      _ValueKinds._Proto,
    ]));
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    Proto type = pop() as Proto;
    TypeAnnotation typeAnnotation =
        type.instantiate(typeArguments).toTypeAnnotation();
    if (questionMark != null) {
      typeAnnotation = new NullableTypeAnnotation(typeAnnotation);
    }
    push(typeAnnotation);
  }

  @override
  void handleVoidKeywordWithTypeArguments(Token token) {
    assert(checkState(token, [
      _ValueKinds._TypeAnnotationsOrNull,
    ]));
    List<TypeAnnotation>? typeArguments =
        pop(_NullValues.TypeAnnotations) as List<TypeAnnotation>?;
    push(new VoidProto(_references.voidReference)
        .instantiate(typeArguments)
        .toTypeAnnotation());
  }

  @override
  void handleVoidKeyword(Token token) {
    push(new VoidProto(_references.voidReference).toTypeAnnotation());
  }

  @override
  void handleLiteralInt(Token token) {
    int? value = intFromToken(token, hasSeparators: false);
    push(new ExpressionProto(new IntegerLiteral.fromText(token.lexeme, value)));
  }

  @override
  void handleLiteralIntWithSeparators(Token token) {
    int? value = intFromToken(token, hasSeparators: true);
    push(new ExpressionProto(new IntegerLiteral.fromText(token.lexeme, value)));
  }

  @override
  void handleLiteralDouble(Token token) {
    push(new ExpressionProto(new DoubleLiteral(
        token.lexeme, doubleFromToken(token, hasSeparators: false))));
  }

  @override
  void handleLiteralDoubleWithSeparators(Token token) {
    push(new ExpressionProto(new DoubleLiteral(
        token.lexeme, doubleFromToken(token, hasSeparators: true))));
  }

  @override
  void handleLiteralBool(Token token) {
    push(new ExpressionProto(new BooleanLiteral(boolFromToken(token))));
  }

  @override
  void handleLiteralNull(Token token) {
    push(new ExpressionProto(new NullLiteral()));
  }

  @override
  void beginLiteralString(Token token) {
    push(new StringPart(token.lexeme));
  }

  @override
  void handleStringPart(Token token) {
    push(new StringPart(token.lexeme));
  }

  @override
  void endLiteralString(int interpolationCount, Token endToken) {
    int count = 1 + interpolationCount * 2;
    assert(checkState(
        endToken,
        repeatedKind(
            unionOfKinds([_ValueKinds._StringPart, _ValueKinds._Proto]),
            count)));
    if (interpolationCount == 0) {
      // TODO(johnniwinther): Use the token corresponding to [part].
      Token token = endToken;
      StringPart part = pop() as StringPart;
      String value = unescapeString(part.text, token, this);
      push(new ExpressionProto(new StringLiteral([new StringPart(value)])));
    } else {
      List<Object?> objects = new List.filled(count, /* dummyValue */ null);
      int index = count;
      while (--index >= 0) {
        objects[index] = pop();
      }
      StringPart first = objects.first as StringPart;
      StringPart last = objects.last as StringPart;
      Quote quote = analyzeQuote(first.text);
      List<StringLiteralPart> parts = [];
      // Contains more than just \' or \".
      if (first.text.length > 1) {
        // TODO(johnniwinther): Use the token corresponding to [first].
        Token token = endToken;
        String value = unescapeFirstStringPart(first.text, quote, token, this);
        if (value.isNotEmpty) {
          parts.add(new StringPart(value));
        }
      }
      for (int i = 1; i < objects.length - 1; i++) {
        Object? object = objects[i];
        if (object is StringPart) {
          if (object.text.length != 0) {
            // TODO(johnniwinther): Use the token corresponding to [object].
            Token token = endToken;
            String value = unescape(object.text, quote, token, this);
            parts.add(new StringPart(value));
          }
        } else {
          parts.add(new InterpolationPart((object as Proto).toExpression()));
        }
      }
      // Contains more than just \' or \".
      if (last.text.length > 1) {
        // TODO(johnniwinther): Use the token corresponding to [last].
        Token token = endToken;
        String value = unescapeLastStringPart(
            last.text, quote, token, token.isSynthetic, this);
        if (value.isNotEmpty) {
          parts.add(new StringPart(value));
        }
      }
      push(new ExpressionProto(new StringLiteral(parts)));
    }
  }

  @override
  void handleAdjacentStringLiterals(Token startToken, int literalCount) {
    assert(
        checkState(startToken, repeatedKind(_ValueKinds._Proto, literalCount)));
    List<Expression> expressions =
        new List.filled(literalCount, _dummyExpression);
    while (--literalCount >= 0) {
      expressions[literalCount] = _popExpression();
    }
    push(new ExpressionProto(new AdjacentStringLiterals(expressions)));
  }

  @override
  void endLiteralSymbol(Token hashToken, int identifierCount) {
    assert(checkState(
      hashToken,
      repeatedKind(_ValueKinds._IdentifierProto, identifierCount),
    ));
    List<String> parts = new List.filled(identifierCount, /* dummy value */ '');
    while (--identifierCount >= 0) {
      parts[identifierCount] = (pop() as IdentifierProto).text;
    }
    push(new ExpressionProto(new SymbolLiteral(parts)));
  }

  @override
  void handleTypeArgumentApplication(Token openAngleBracket) {
    assert(checkState(openAngleBracket, [
      _ValueKinds._TypeAnnotations,
      _ValueKinds._Proto,
    ]));
    List<TypeAnnotation> typeArguments = pop() as List<TypeAnnotation>;
    Proto receiver = pop() as Proto;
    push(receiver.instantiate(typeArguments));
  }

  @override
  void endParenthesizedExpression(Token token) {
    assert(checkState(token, [
      _ValueKinds._Proto,
    ]));
    Expression expression = _popExpression();
    push(new ExpressionProto(new ParenthesizedExpression(expression)));
  }

  @override
  void endConditionalExpression(Token question, Token colon, Token endToken) {
    assert(checkState(question, [
      /* otherwise */ _ValueKinds._Proto,
      /* then */ _ValueKinds._Proto,
      /* condition */ _ValueKinds._Proto,
    ]));
    Expression otherwise = _popExpression();
    Expression then = _popExpression();
    Expression condition = _popExpression();
    push(new ExpressionProto(
        new ConditionalExpression(condition, then, otherwise)));
  }

  @override
  void handleValuedFormalParameter(
      Token equals, Token token, FormalParameterKind kind) {
    assert(checkState(token, [
      _ValueKinds._Proto,
    ]));
    push(_popExpression());
  }

  @override
  void handleFormalParameterWithoutValue(Token token) {
    push(_NullValues.Expression);
  }

  @override
  void endFormalParameter(
      Token? thisKeyword,
      Token? superKeyword,
      Token? periodAfterThisOrSuper,
      Token nameToken,
      Token? initializerStart,
      Token? initializerEnd,
      FormalParameterKind kind,
      MemberKind memberKind) {
    assert(checkState(nameToken, [
      _ValueKinds._ExpressionOrNull,
      _ValueKinds._IdentifierProtoOrNull,
      _ValueKinds._TypeAnnotationOrNull,
      _ValueKinds._Expressions,
    ]));
    Expression? defaultValue = pop() as Expression?;
    IdentifierProto? name = pop() as IdentifierProto?;
    TypeAnnotation? typeAnnotation = pop() as TypeAnnotation?;
    List<Expression> metadata = pop() as List<Expression>;
    push(new FormalParameter(metadata, typeAnnotation, name?.text, defaultValue,
        isNamed: kind.isNamed, isRequired: kind.isRequired));
  }

  @override
  void handleNoName(Token token) {
    push(_NullValues.Identifier);
  }

  @override
  void endOptionalFormalParameters(
      int count, Token beginToken, Token endToken, MemberKind kind) {
    assert(checkState(
        beginToken, repeatedKind(_ValueKinds._FormalParameter, count)));
    List<FormalParameter> formalParameters =
        new List.filled(count, _dummyFormalParameter);
    while (--count >= 0) {
      formalParameters[count] = pop() as FormalParameter;
    }
    push(new FormalParameterGroup(formalParameters));
  }

  @override
  void endFormalParameters(
      int count, Token beginToken, Token endToken, MemberKind kind) {
    assert(checkState(
        beginToken,
        repeatedKind(
            unionOfKinds([
              _ValueKinds._FormalParameter,
              _ValueKinds._FormalParameterGroup
            ]),
            count)));
    List<Object?> objects = new List.filled(count, /* dummy value */ null);
    while (--count >= 0) {
      objects[count] = pop();
    }
    List<FormalParameter> formalParameters = [];
    for (Object? object in objects) {
      if (object is FormalParameter) {
        formalParameters.add(object);
      } else {
        formalParameters
            .addAll((object as FormalParameterGroup).formalParameters);
      }
    }
    push(formalParameters);
  }

  @override
  void endTypeVariable(
      Token token, int index, Token? extendsOrSuper, Token? variance) {
    assert(checkState(token, [
      _ValueKinds._TypeAnnotationOrNull,
      _ValueKinds._FunctionTypeParameters,
    ]));
    TypeAnnotation? bound = pop(_NullValues.TypeAnnotation) as TypeAnnotation?;
    List<FunctionTypeParameter> functionTypeParameters =
        pop() as List<FunctionTypeParameter>;
    FunctionTypeParameter functionTypeParameter = functionTypeParameters[index];
    functionTypeParameter.bound = bound;
    push(functionTypeParameters);
  }

  @override
  void handleTypeVariablesDefined(Token token, int count) {
    assert(checkState(
        token,
        repeatedKinds([
          _ValueKinds._FunctionTypeParameter,
          _ValueKinds._Expressions,
        ], count)));
    List<FunctionTypeParameter> functionTypeParameters =
        new List.filled(count, _dummyFunctionTypeParameter);
    while (--count >= 0) {
      FunctionTypeParameter functionTypeParameter =
          functionTypeParameters[count] = pop() as FunctionTypeParameter;
      functionTypeParameter.metadata = pop() as List<Expression>;
    }
    push(functionTypeParameters);
  }

  @override
  void endTypeVariables(Token beginToken, Token endToken) {
    assert(checkState(beginToken, [
      _ValueKinds._FunctionTypeParameters,
    ]));
  }

  @override
  void beginFunctionType(Token beginToken) {
    _typeParameterScopes.add(new FunctionTypeParameterScope(_scope));
  }

  @override
  void endFunctionType(Token functionToken, Token? questionMark) {
    assert(checkState(functionToken, [
      _ValueKinds._FormalParameters,
      _ValueKinds._TypeAnnotationOrNull,
      _ValueKinds._FunctionTypeParametersOrNull,
    ]));
    _typeParameterScopes.removeLast();

    List<FormalParameter> formalParameters = pop() as List<FormalParameter>;
    TypeAnnotation? returnType =
        pop(_NullValues.TypeAnnotation) as TypeAnnotation?;
    List<FunctionTypeParameter>? typeParameters =
        pop(_NullValues.FunctionTypeParameters) as List<FunctionTypeParameter>?;
    push(new FunctionTypeAnnotation(
        returnType, typeParameters ?? const [], formalParameters));
  }

  @override
  void endRecordType(
      Token leftBracket, Token? questionMark, int count, bool hasNamedFields) {
    assert(checkState(
      leftBracket,
      hasNamedFields
          ? [
              _ValueKinds._RecordTypeEntries,
              ...repeatedKind(_ValueKinds._RecordTypeEntry, count - 1)
            ]
          : repeatedKind(_ValueKinds._RecordTypeEntry, count),
    ));
    List<RecordTypeEntry>? named;
    if (hasNamedFields) {
      named = pop() as List<RecordTypeEntry>;
      count--;
    }
    List<RecordTypeEntry> positional =
        new List.filled(count, _dummyRecordTypeEntry);
    while (--count >= 0) {
      positional[count] = pop() as RecordTypeEntry;
    }
    push(new RecordTypeAnnotation(positional, named ?? const []));
  }

  @override
  void endRecordTypeEntry() {
    assert(checkState(null, [
      _ValueKinds._IdentifierProtoOrNull,
      _ValueKinds._TypeAnnotation,
      _ValueKinds._Expressions,
    ]));
    IdentifierProto? name = pop() as IdentifierProto?;
    TypeAnnotation type = pop() as TypeAnnotation;
    List<Expression> metadata = pop() as List<Expression>;
    push(new RecordTypeEntry(metadata, type, name?.text));
  }

  @override
  void endRecordTypeNamedFields(int count, Token leftBracket) {
    assert(checkState(
      leftBracket,
      repeatedKind(_ValueKinds._RecordTypeEntry, count),
    ));
    List<RecordTypeEntry> entries =
        new List.filled(count, _dummyRecordTypeEntry);
    while (--count >= 0) {
      entries[count] = pop() as RecordTypeEntry;
    }
    push(entries);
  }

  @override
  void handleNoType(Token lastConsumed) {
    push(_NullValues.TypeAnnotation);
  }

  @override
  void handleNoTypeVariables(Token token) {
    push(_NullValues.FunctionTypeParameters);
  }

  @override
  void addProblem(Message message, int charOffset, int length,
      {bool wasHandled = false, List<LocatedMessage> context = const []}) {
    // Don't report errors.
  }

  @override
  Never internalProblem(Message message, int charOffset, Uri uri) {
    throw new UnimplementedError(message.problemMessage);
  }
}

enum _NullValues implements NullValue<Object> {
  Arguments,
  Expression,
  FunctionTypeParameters,
  Identifier,
  TypeAnnotation,
  TypeAnnotations,
}

final Argument _dummyArgument =
    new PositionalArgument(new IntegerLiteral.fromText('0'));

final RecordField _dummyRecordField =
    new RecordPositionalField(_dummyExpression);

final TypeAnnotation _dummyTypeAnnotation = new InvalidTypeAnnotation();

final Expression _dummyExpression = new NullLiteral();

final Element _dummyElement =
    new ExpressionElement(_dummyExpression, isNullAware: false);

final FormalParameter _dummyFormalParameter = new FormalParameter(
    const [], null, null, null,
    isNamed: false, isRequired: false);

final FunctionTypeParameter _dummyFunctionTypeParameter =
    new FunctionTypeParameter('');

final RecordTypeEntry _dummyRecordTypeEntry =
    new RecordTypeEntry(const [], _dummyTypeAnnotation, null);

class _ValueKinds {
  static const ValueKind _Proto = const SingleValueKind<Proto>();
  static const ValueKind _IdentifierProto =
      const SingleValueKind<IdentifierProto>();
  static const ValueKind _IdentifierProtoOrNull =
      const SingleValueKind<IdentifierProto>(_NullValues.Identifier);
  static const ValueKind _Expression = const SingleValueKind<Expression>();
  static const ValueKind _ExpressionOrNull =
      const SingleValueKind<Expression>(_NullValues.Expression);
  static const ValueKind _Expressions =
      const SingleValueKind<List<Expression>>();
  static const ValueKind _Element = const SingleValueKind<Element>();
  static final ValueKind _ElementOrProto =
      unionOfKinds([_ValueKinds._Element, _ValueKinds._Proto]);
  static const ValueKind _Argument = const SingleValueKind<Argument>();
  static final ValueKind _ArgumentOrProto =
      unionOfKinds([_ValueKinds._Argument, _ValueKinds._Proto]);
  static const ValueKind _RecordField = const SingleValueKind<RecordField>();
  static final ValueKind _RecordFieldOrProto =
      unionOfKinds([_ValueKinds._RecordField, _ValueKinds._Proto]);
  static final ValueKind _RecordTypeEntry =
      const SingleValueKind<RecordTypeEntry>();
  static final ValueKind _RecordTypeEntries =
      const SingleValueKind<List<RecordTypeEntry>>();
  static const ValueKind _Arguments = const SingleValueKind<List<Argument>>();
  static const ValueKind _ArgumentsOrNull =
      const SingleValueKind<List<Argument>>(_NullValues.Arguments);
  static const ValueKind _TypeAnnotation =
      const SingleValueKind<TypeAnnotation>();
  static const ValueKind _TypeAnnotationOrNull =
      const SingleValueKind<TypeAnnotation>(_NullValues.TypeAnnotation);
  static const ValueKind _TypeAnnotations =
      const SingleValueKind<List<TypeAnnotation>>();
  static const ValueKind _TypeAnnotationsOrNull =
      const SingleValueKind<List<TypeAnnotation>>(_NullValues.TypeAnnotations);
  static const ValueKind _StringPart = const SingleValueKind<StringPart>();
  static const ValueKind _FormalParameter =
      const SingleValueKind<FormalParameter>();
  static const ValueKind _FormalParameters =
      const SingleValueKind<List<FormalParameter>>();
  static const ValueKind _FormalParameterGroup =
      const SingleValueKind<FormalParameterGroup>();
  static const ValueKind _FunctionTypeParameter =
      const SingleValueKind<FunctionTypeParameter>();
  static const ValueKind _FunctionTypeParameters =
      const SingleValueKind<List<FunctionTypeParameter>>();
  static const ValueKind _FunctionTypeParametersOrNull =
      const SingleValueKind<List<FunctionTypeParameter>>(
          _NullValues.FunctionTypeParameters);
}

/// Parses the metadata annotation beginning at [atToken].
Expression parseAnnotation(
    Token atToken, Uri fileUri, Scope scope, References references,
    {required bool isDartLibrary, bool delayLookupForTesting = false}) {
  AnnotationsListener listener = new AnnotationsListener(
      fileUri, scope, references,
      delayLookup: delayLookupForTesting, isDartLibrary: isDartLibrary);
  Parser parser = new Parser(listener, useImplicitCreationExpression: false);
  parser.parseMetadata(parser.syntheticPreviousToken(atToken));
  return listener.pop() as Expression;
}

/// Parses the expression beginning at [initializerToken].
Expression parseExpression(
    Token initializerToken, Uri fileUri, Scope scope, References references,
    {required bool isDartLibrary, bool delayLookupForTesting = false}) {
  AnnotationsListener listener = new AnnotationsListener(
      fileUri, scope, references,
      delayLookup: delayLookupForTesting, isDartLibrary: isDartLibrary);
  Parser parser = new Parser(listener, useImplicitCreationExpression: false);
  parser.parseExpression(parser.syntheticPreviousToken(initializerToken));
  return listener._popExpression();
}

/// A [Scope] extended to include function type parameters.
class FunctionTypeParameterScope implements Scope {
  final Scope parentScope;
  final Map<String, FunctionTypeParameter> functionTypeParameterMap = {};

  FunctionTypeParameterScope(this.parentScope);

  FunctionTypeParameter declareTypeParameter(String name) {
    return functionTypeParameterMap[name] = new FunctionTypeParameter(name);
  }

  @override
  Proto lookup(String name) {
    FunctionTypeParameter? functionTypeParameter =
        functionTypeParameterMap[name];
    if (functionTypeParameter != null) {
      return new FunctionTypeParameterProto(functionTypeParameter);
    }
    return parentScope.lookup(name);
  }
}
