// Copyright (c) 2014, 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/parser/parser.dart' as fasta;
import 'package:_fe_analyzer_shared/src/parser/type_info.dart' as fasta;
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart'
    show ExperimentalFeaturesStatus;
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/fasta/ast_builder.dart';

/// A parser used to parse tokens into an AST structure.
class Parser {
  late Token currentToken;

  /// The fasta parser being wrapped.
  late final fasta.Parser fastaParser;

  /// The builder which creates the analyzer AST data structures
  /// based on the Fasta parser.
  final AstBuilder astBuilder;

  Parser(
    DiagnosticReporter diagnosticReporter, {
    required FeatureSet featureSet,
    bool allowNativeClause = true,
    required LibraryLanguageVersion languageVersion,
    required LineInfo lineInfo,
  }) : astBuilder = AstBuilder(
         diagnosticReporter,
         diagnosticReporter.source.uri,
         true,
         featureSet,
         languageVersion,
         lineInfo,
       ) {
    fastaParser = fasta.Parser(
      astBuilder,
      experimentalFeatures: ExperimentalFeaturesStatus(featureSet),
    );
    astBuilder.parser = fastaParser;
    astBuilder.allowNativeClause = allowNativeClause;
  }

  set allowNativeClause(bool value) {
    astBuilder.allowNativeClause = value;
  }

  set parseFunctionBodies(bool parseFunctionBodies) {
    astBuilder.parseFunctionBodies = parseFunctionBodies;
  }

  /// Append the given token to the end of the token stream,
  /// and update the token's offset.
  void appendToken(Token token, Token newToken) {
    while (!token.next!.isEof) {
      token = token.next!;
    }
    newToken
      ..offset = token.end
      ..setNext(token.next!);
    token.setNext(newToken);
  }

  Expression parseAdditiveExpression() => parseExpression2();

  Expression parseArgument() {
    currentToken = SimpleToken(TokenType.OPEN_PAREN, 0)..setNext(currentToken);
    appendToken(currentToken, SimpleToken(TokenType.CLOSE_PAREN, 0));
    currentToken = fastaParser
        .parseArguments(fastaParser.syntheticPreviousToken(currentToken))
        .next!;
    var invocation = astBuilder.pop() as MethodInvocation;
    return invocation.argumentList.arguments[0];
  }

  Expression parseAssignableExpression(bool primaryAllowed) =>
      parseExpression2();

  Expression parseBitwiseAndExpression() => parseExpression2();

  Expression parseBitwiseOrExpression() => parseExpression2();

  Expression parseBitwiseXorExpression() => parseExpression2();

  CompilationUnitImpl parseCompilationUnit(Token token) {
    currentToken = token;
    return parseCompilationUnit2();
  }

  CompilationUnitImpl parseCompilationUnit2() {
    currentToken = fastaParser.parseUnit(currentToken);
    return astBuilder.pop() as CompilationUnitImpl;
  }

  Expression parseConditionalExpression() => parseExpression2();

  Configuration parseConfiguration() {
    currentToken = fastaParser
        .parseConditionalUri(fastaParser.syntheticPreviousToken(currentToken))
        .next!;
    return astBuilder.pop() as Configuration;
  }

  Expression parseConstExpression() => parseExpression2();

  CompilationUnit parseDirectives(Token token) {
    currentToken = token;
    return parseDirectives2();
  }

  CompilationUnit parseDirectives2() {
    currentToken = fastaParser.parseDirectives(currentToken);
    return astBuilder.pop() as CompilationUnit;
  }

  DottedName parseDottedName() {
    currentToken = fastaParser
        .parseDottedName(fastaParser.syntheticPreviousToken(currentToken))
        .next!;
    return astBuilder.pop() as DottedName;
  }

  Expression parseEqualityExpression() => parseExpression2();

  Expression parseExpression(Token token) {
    currentToken = token;
    return parseExpression2();
  }

  Expression parseExpression2() {
    currentToken = fastaParser
        .parseExpression(fastaParser.syntheticPreviousToken(currentToken))
        .next!;
    return astBuilder.pop() as Expression;
  }

  Expression parseExpressionWithoutCascade() => parseExpression2();

  FormalParameterList parseFormalParameterList({bool inFunctionType = false}) {
    currentToken = fastaParser
        .parseFormalParametersRequiredOpt(
          fastaParser.syntheticPreviousToken(currentToken),
          inFunctionType
              ? fasta.MemberKind.GeneralizedFunctionType
              : fasta.MemberKind.NonStaticMethod,
        )
        .next!;
    return astBuilder.pop() as FormalParameterList;
  }

  FunctionBody parseFunctionBody(
    bool mayBeEmpty,
    DiagnosticCode emptyErrorCode,
    bool inExpression,
  ) {
    currentToken = fastaParser.parseAsyncModifierOpt(
      fastaParser.syntheticPreviousToken(currentToken),
    );
    currentToken = fastaParser.parseFunctionBody(
      currentToken,
      inExpression,
      mayBeEmpty,
    );
    return astBuilder.pop() as FunctionBody;
  }

  FunctionExpression parseFunctionExpression() =>
      parseExpression2() as FunctionExpression;

  Expression parseLogicalAndExpression() => parseExpression2();

  Expression parseLogicalOrExpression() => parseExpression2();

  Expression parseMultiplicativeExpression() => parseExpression2();

  InstanceCreationExpression parseNewExpression() =>
      parseExpression2() as InstanceCreationExpression;

  Expression parsePostfixExpression() => parseExpression2();

  Identifier parsePrefixedIdentifier() => parseExpression2() as Identifier;

  Expression parsePrimaryExpression() {
    currentToken = fastaParser
        .parsePrimary(
          fastaParser.syntheticPreviousToken(currentToken),
          fasta.IdentifierContext.expression,
          fasta.ConstantPatternContext.none,
        )
        .next!;
    return astBuilder.pop() as Expression;
  }

  Expression parseRelationalExpression() => parseExpression2();

  Expression parseRethrowExpression() => parseExpression2();

  Expression parseShiftExpression() => parseExpression2();

  SimpleIdentifier parseSimpleIdentifier({
    bool allowKeyword = false,
    bool isDeclaration = false,
  }) => parseExpression2() as SimpleIdentifier;

  Statement parseStatement(Token token) {
    currentToken = token;
    return parseStatement2();
  }

  Statement parseStatement2() {
    currentToken = fastaParser
        .parseStatement(fastaParser.syntheticPreviousToken(currentToken))
        .next!;
    return astBuilder.pop() as Statement;
  }

  StringLiteral parseStringLiteral() => parseExpression2() as StringLiteral;

  SymbolLiteral parseSymbolLiteral() => parseExpression2() as SymbolLiteral;

  Expression parseThrowExpression() => parseExpression2();

  Expression parseThrowExpressionWithoutCascade() => parseExpression2();

  AnnotatedNode parseTopLevelDeclaration(bool isDirective) {
    currentToken = fastaParser.parseTopLevelDeclaration(currentToken);
    return (isDirective ? astBuilder.directives : astBuilder.declarations)
        .removeLast();
  }

  TypeAnnotation parseTypeAnnotation(bool inExpression) {
    Token previous = fastaParser.syntheticPreviousToken(currentToken);
    currentToken = fasta
        .computeType(previous, true, !inExpression)
        .parseType(previous, fastaParser)
        .next!;
    return astBuilder.pop() as TypeAnnotation;
  }

  TypeArgumentList parseTypeArgumentList() {
    Token previous = fastaParser.syntheticPreviousToken(currentToken);
    currentToken = fasta
        .computeTypeParamOrArg(previous)
        .parseArguments(previous, fastaParser)
        .next!;
    return astBuilder.pop() as TypeArgumentList;
  }

  NamedType parseTypeName(bool inExpression) {
    Token previous = fastaParser.syntheticPreviousToken(currentToken);
    currentToken = fasta
        .computeType(previous, true, !inExpression)
        .parseType(previous, fastaParser)
        .next!;
    return astBuilder.pop() as NamedType;
  }

  TypeParameter parseTypeParameter() {
    currentToken = SyntheticBeginToken(TokenType.LT, 0)
      ..endGroup = SyntheticToken(TokenType.GT, 0)
      ..setNext(currentToken);
    appendToken(currentToken, currentToken.endGroup!);
    TypeParameterList typeParams = parseTypeParameterList()!;
    return typeParams.typeParameters[0];
  }

  TypeParameterList? parseTypeParameterList() {
    Token token = fastaParser.syntheticPreviousToken(currentToken);
    currentToken = fasta
        .computeTypeParamOrArg(token, true)
        .parseVariables(token, fastaParser)
        .next!;
    return astBuilder.pop() as TypeParameterList?;
  }

  Expression parseUnaryExpression() => parseExpression2();
}
