// Copyright (c) 2021, 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:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/invokes_super_self.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
import 'package:analyzer/src/summary2/augmentation.dart';
import 'package:analyzer/src/summary2/library_builder.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/reference.dart';
import 'package:analyzer/src/util/comment.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:collection/collection.dart';

class ElementBuilder extends ThrowingAstVisitor<void> {
  final LibraryBuilder _libraryBuilder;
  final CompilationUnitElementImpl _unitElement;

  var _exportDirectiveIndex = 0;
  var _importDirectiveIndex = 0;
  var _partDirectiveIndex = 0;

  _EnclosingContext _enclosingContext;
  int _nextUnnamedId = 0;

  ElementBuilder({
    required LibraryBuilder libraryBuilder,
    required CompilationUnitElementImpl unitElement,
  })  : _libraryBuilder = libraryBuilder,
        _unitElement = unitElement,
        _enclosingContext = _EnclosingContext(
          instanceElementBuilder: null,
          fragment: unitElement,
        );

  Linker get _linker => _libraryBuilder.linker;

  void buildDeclarationElements(CompilationUnit unit) {
    _visitPropertyFirst<TopLevelVariableDeclaration>(unit.declarations);
    _unitElement.accessors = _enclosingContext.propertyAccessors;
    _unitElement.classes = _enclosingContext.classes;
    _unitElement.enums = _enclosingContext.enums;
    _unitElement.extensions = _enclosingContext.extensions;
    _unitElement.extensionTypes = _enclosingContext.extensionTypes;
    _unitElement.functions = _enclosingContext.functions;
    _unitElement.mixins = _enclosingContext.mixins;
    _unitElement.topLevelVariables = _enclosingContext.topLevelVariables;
    _unitElement.typeAliases = _enclosingContext.typeAliases;
  }

  /// Builds exports and imports, metadata into [_unitElement].
  void buildDirectiveElements(CompilationUnitImpl unit) {
    unit.directives.accept(this);
  }

  /// Updates metadata and documentation for [_libraryBuilder].
  ///
  /// This method must be invoked after [buildDirectiveElements].
  void buildLibraryMetadata(CompilationUnitImpl unit) {
    var libraryElement = _libraryBuilder.element;

    // Prefer the actual library directive.
    var libraryDirective =
        unit.directives.whereType<LibraryDirectiveImpl>().firstOrNull;
    if (libraryDirective != null) {
      libraryDirective.element = libraryElement;
      libraryElement.documentationComment = getCommentNodeRawText(
        libraryDirective.documentationComment,
      );
      libraryElement.metadata = _buildAnnotations(libraryDirective.metadata);
      return;
    }

    // Otherwise use the first directive.
    var firstDirective = unit.directives.firstOrNull;
    if (firstDirective != null) {
      libraryElement.documentationComment = getCommentNodeRawText(
        firstDirective.documentationComment,
      );
      var firstDirectiveMetadata = firstDirective.element?.metadata;
      if (firstDirectiveMetadata != null) {
        libraryElement.metadata = firstDirectiveMetadata;
      }
    }
  }

  @override
  void visitClassDeclaration(covariant ClassDeclarationImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = ClassElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAbstract = node.abstractKeyword != null;
    fragment.isAugmentation = node.augmentKeyword != null;
    fragment.isBase = node.baseKeyword != null;
    fragment.isFinal = node.finalKeyword != null;
    fragment.isInterface = node.interfaceKeyword != null;
    fragment.isMixinClass = node.mixinKeyword != null;
    if (node.sealedKeyword != null) {
      fragment.isAbstract = true;
      fragment.isSealed = true;
    }
    fragment.hasExtendsClause = node.extendsClause != null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var fragmentReference = _enclosingContext.addClass(refName, fragment);
    if (!fragment.isAugmentation) {
      _libraryBuilder.declare(name, fragmentReference);
    }

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder?.setPreviousFor(fragment);
    if (fragment.isAugmentation && elementBuilder is ClassElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@class');
      var elementReference = containerRef.addChild(refName);
      var element = ClassElementImpl2(elementReference, fragment);
      _libraryBuilder.element.classes.add(element);

      elementBuilder = ClassElementBuilder(
        element: element,
        firstFragment: fragment,
      );
      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: elementBuilder,
      fragment: fragment,
      constFieldsForFinalInstance: true,
    );
    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
      _visitPropertyFirst<FieldDeclaration>(node.members);
    });
    fragment.typeParameters = holder.typeParameters;
    fragment.accessors = holder.propertyAccessors;
    fragment.constructors = holder.constructors;
    fragment.fields = holder.fields;
    fragment.methods = holder.methods;

    node.extendsClause?.accept(this);
    node.withClause?.accept(this);
    node.implementsClause?.accept(this);

    elementBuilder.addFragment(fragment);
  }

  @override
  void visitClassTypeAlias(covariant ClassTypeAliasImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = ClassElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAbstract = node.abstractKeyword != null;
    fragment.isBase = node.baseKeyword != null;
    fragment.isFinal = node.finalKeyword != null;
    fragment.isInterface = node.interfaceKeyword != null;
    fragment.isMixinApplication = true;
    fragment.isMixinClass = node.mixinKeyword != null;
    if (node.sealedKeyword != null) {
      fragment.isAbstract = true;
      fragment.isSealed = true;
    }
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addClass(refName, fragment);
    if (!fragment.isAugmentation) {
      _libraryBuilder.declare(name, reference);
    }

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder?.setPreviousFor(fragment);
    if (fragment.isAugmentation && elementBuilder is ClassElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@class');
      var elementReference = containerRef.addChild(refName);
      var element = ClassElementImpl2(elementReference, fragment);
      _libraryBuilder.element.classes.add(element);

      elementBuilder = ClassElementBuilder(
        element: element,
        firstFragment: fragment,
      );
      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: elementBuilder,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      var typeParameters = node.typeParameters;
      if (typeParameters != null) {
        typeParameters.accept(this);
        fragment.typeParameters = holder.typeParameters;
      }
    });

    node.superclass.accept(this);
    node.withClause.accept(this);
    node.implementsClause?.accept(this);
  }

  @override
  void visitConstructorDeclaration(
    covariant ConstructorDeclarationImpl node,
  ) {
    var nameNode = node.name ?? node.returnType;
    var name = node.name?.lexeme ?? '';
    if (name == 'new') {
      // A constructor declared as `C.new` is unnamed, and is modeled as such.
      name = '';
    }
    var nameOffset = nameNode.offset;

    var element = ConstructorElementImpl(name, nameOffset);
    element.isAugmentation = node.augmentKeyword != null;
    element.isConst = node.constKeyword != null;
    element.isExternal = node.externalKeyword != null;
    element.isFactory = node.factoryKeyword != null;
    element.metadata = _buildAnnotations(node.metadata);
    element.typeName = node.returnType.name;
    element.typeNameOffset = node.returnType.offset;
    element.periodOffset = node.period?.offset;
    element.nameEnd = nameNode.end;
    if ((node.period, node.name) case (var _?, var name?)) {
      element.name2 = name.lexeme;
      element.nameOffset2 = name.offset;
    } else {
      element.name2 = 'new';
    }
    _setCodeRange(element, node);
    _setDocumentation(element, node);

    if (element.isConst || element.isFactory) {
      element.constantInitializers = node.initializers;
    }

    node.declaredFragment = element;
    _linker.elementNodes[element] = node;

    var reference = _enclosingContext.addConstructor(element);
    _buildExecutableElementChildren(
      reference: reference,
      fragment: element,
      formalParameters: node.parameters,
    );
  }

  @override
  void visitDefaultFormalParameter(DefaultFormalParameter node) {
    node.parameter.accept(this);
  }

  @override
  void visitEnumDeclaration(covariant EnumDeclarationImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;
    var nameOffset = nameToken.offset;

    var fragment = EnumElementImpl(name, nameOffset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAugmentation = node.augmentKeyword != null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addEnum(refName, fragment);
    if (!fragment.isAugmentation) {
      _libraryBuilder.declare(name, reference);
    }

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder?.setPreviousFor(fragment);
    if (fragment.isAugmentation && elementBuilder is EnumElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@enum');
      var elementReference = containerRef.addChild(refName);
      var element = EnumElementImpl2(elementReference, fragment);
      _libraryBuilder.element.enums.add(element);

      elementBuilder = EnumElementBuilder(
        firstFragment: fragment,
        element: element,
      );
      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: elementBuilder,
      fragment: fragment,
      constFieldsForFinalInstance: true,
    );

    // Build fields for all enum constants.
    var constants = node.constants;
    var valuesElements = <SimpleIdentifierImpl>[];
    var valuesNames = <String>{};
    for (var i = 0; i < constants.length; ++i) {
      var constant = constants[i];
      var nameToken = constant.name;
      var name = nameToken.lexeme;
      var field = ConstFieldElementImpl(name, constant.name.offset)
        ..hasImplicitType = true
        ..hasInitializer = true
        ..isAugmentation = constant.augmentKeyword != null
        ..isConst = true
        ..isEnumConstant = true
        ..isStatic = true;
      field.name2 = _getFragmentName(nameToken);
      field.nameOffset2 = _getFragmentNameOffset(nameToken);
      _setCodeRange(field, constant);
      _setDocumentation(field, constant);
      field.metadata = _buildAnnotationsWithUnit(
        _unitElement,
        constant.metadata,
      );

      var constructorSelector = constant.arguments?.constructorSelector;
      var constructorName = constructorSelector?.name.name;

      var initializer = InstanceCreationExpressionImpl(
        keyword: null,
        constructorName: ConstructorNameImpl(
          type: NamedTypeImpl(
            importPrefix: null,
            name2: StringToken(TokenType.STRING, fragment.name, -1),
            typeArguments: constant.arguments?.typeArguments,
            question: null,
          ),
          period: constructorName != null ? Tokens.period() : null,
          name: constructorName != null
              ? SimpleIdentifierImpl(
                  StringToken(TokenType.STRING, constructorName, -1),
                )
              : null,
        ),
        argumentList: ArgumentListImpl(
          leftParenthesis: Tokens.openParenthesis(),
          arguments: [
            ...?constant.arguments?.argumentList.arguments,
          ],
          rightParenthesis: Tokens.closeParenthesis(),
        ),
        typeArguments: null,
      );

      var variableDeclaration = VariableDeclarationImpl(
        name: StringToken(TokenType.STRING, name, -1),
        equals: Tokens.eq(),
        initializer: initializer,
      );
      constant.declaredFragment = field;
      variableDeclaration.declaredFragment = field;
      VariableDeclarationListImpl(
        comment: null,
        metadata: null,
        lateKeyword: null,
        keyword: null,
        type: null,
        variables: [variableDeclaration],
      );
      _linker.elementNodes[field] = variableDeclaration;

      field.constantInitializer = initializer;

      var refName = field.name2 ?? '${_nextUnnamedId++}';
      holder.addNonSyntheticField(refName, field);

      valuesElements.add(
        SimpleIdentifierImpl(
          StringToken(TokenType.STRING, name, -1),
        ),
      );
      valuesNames.add(name);
    }

    // Build the 'values' field.
    var valuesField = ConstFieldElementImpl('values', -1)
      ..hasEnclosingTypeParameterReference = false
      ..isConst = true
      ..isStatic = true
      ..isSynthetic = true
      ..name2 = 'values';
    var initializer = ListLiteralImpl(
      constKeyword: null,
      typeArguments: null,
      leftBracket: Tokens.openSquareBracket(),
      elements: valuesElements,
      rightBracket: Tokens.closeSquareBracket(),
    );
    valuesField.constantInitializer = initializer;

    var variableDeclaration = VariableDeclarationImpl(
      name: StringToken(TokenType.STRING, 'values', -1),
      equals: Tokens.eq(),
      initializer: initializer,
    );
    var valuesTypeNode = NamedTypeImpl(
      importPrefix: null,
      name2: StringToken(TokenType.STRING, 'List', -1),
      typeArguments: TypeArgumentListImpl(
        leftBracket: Tokens.lt(),
        arguments: [
          NamedTypeImpl(
            importPrefix: null,
            name2: StringToken(TokenType.STRING, fragment.name, -1),
            typeArguments: null,
            question: null,
          )..element2 = fragment.asElement2,
        ],
        rightBracket: Tokens.gt(),
      ),
      question: null,
    );
    VariableDeclarationListImpl(
      comment: null,
      metadata: null,
      lateKeyword: null,
      keyword: Tokens.const_(),
      variables: [variableDeclaration],
      type: valuesTypeNode,
    );
    _linker.elementNodes[valuesField] = variableDeclaration;

    holder.addNonSyntheticField('values', valuesField);

    _libraryBuilder.implicitEnumNodes[fragment] = ImplicitEnumNodes(
      element: fragment,
      valuesTypeNode: valuesTypeNode,
      valuesNode: variableDeclaration,
      valuesElement: valuesField,
      valuesNames: valuesNames,
      valuesInitializer: initializer,
    );

    node.withClause?.accept(this);
    node.implementsClause?.accept(this);

    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
      _visitPropertyFirst<FieldDeclaration>(node.members);
    });

    fragment.accessors = holder.propertyAccessors;
    fragment.constructors = holder.constructors;
    fragment.fields = holder.fields;
    fragment.methods = holder.methods;
    fragment.typeParameters = holder.typeParameters;
    elementBuilder.addFragment(fragment);
  }

  @override
  void visitExportDirective(covariant ExportDirectiveImpl node) {
    var index = _exportDirectiveIndex++;
    var exportElement = _unitElement.libraryExports[index];
    exportElement.metadata = _buildAnnotations(node.metadata);
    node.element = exportElement;
  }

  @override
  void visitExtendsClause(ExtendsClause node) {
    node.superclass.accept(this);
  }

  @override
  void visitExtensionDeclaration(covariant ExtensionDeclarationImpl node) {
    var nameToken = node.name;
    var name = nameToken?.lexeme;
    var nameOffset = nameToken?.offset ?? -1;

    var fragment = ExtensionElementImpl(name, nameOffset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAugmentation = node.augmentKeyword != null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addExtension(refName, fragment);

    if (name != null) {
      if (!fragment.isAugmentation) {
        _libraryBuilder.declare(name, reference);
      }
    }

    FragmentedElementBuilder? elementBuilder;
    if (name != null) {
      elementBuilder = _libraryBuilder.elementBuilderGetters[name];
      elementBuilder?.setPreviousFor(fragment);
      if (fragment.isAugmentation &&
          elementBuilder is ExtensionElementBuilder) {
      } else {
        var elementReference =
            _libraryBuilder.reference.getChild('@extension').addChild(refName);
        var element = ExtensionElementImpl2(elementReference, fragment);
        _libraryBuilder.element.extensions.add(element);

        elementBuilder = ExtensionElementBuilder(
          firstFragment: fragment,
          element: element,
        );
        _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
      }
    } else {
      var elementReference =
          _libraryBuilder.reference.getChild('@extension').addChild(refName);
      var element = ExtensionElementImpl2(elementReference, fragment);
      _libraryBuilder.element.extensions.add(element);
      elementBuilder = ExtensionElementBuilder(
        firstFragment: fragment,
        element: element,
      );
      elementBuilder as ExtensionElementBuilder; // force
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: elementBuilder,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
      _visitPropertyFirst<FieldDeclaration>(node.members);
    });
    fragment.typeParameters = holder.typeParameters;
    fragment.accessors = holder.propertyAccessors;
    fragment.fields = holder.fields;
    fragment.methods = holder.methods;

    node.onClause?.accept(this);
    elementBuilder.addFragment(fragment);
  }

  @override
  void visitExtensionOnClause(ExtensionOnClause node) {
    node.extendedType.accept(this);
  }

  @override
  void visitExtensionTypeDeclaration(
    covariant ExtensionTypeDeclarationImpl node,
  ) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = ExtensionTypeElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAugmentation = node.augmentKeyword != null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addExtensionType(refName, fragment);
    if (!fragment.isAugmentation) {
      _libraryBuilder.declare(name, reference);
    }

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder?.setPreviousFor(fragment);
    if (fragment.isAugmentation &&
        elementBuilder is ExtensionTypeElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@extensionType');
      var elementReference = containerRef.addChild(refName);
      var element = ExtensionTypeElementImpl2(elementReference, fragment);
      _libraryBuilder.element.extensionTypes.add(element);

      elementBuilder = ExtensionTypeElementBuilder(
        firstFragment: fragment,
        element: element,
      );

      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: elementBuilder,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
      _builtRepresentationDeclaration(
        extensionNode: node,
        representation: node.representation,
        extensionFragment: fragment,
      );
      _visitPropertyFirst<FieldDeclaration>(node.members);
    });

    fragment.accessors = holder.propertyAccessors;
    fragment.constructors = holder.constructors;
    fragment.fields = holder.fields;
    fragment.methods = holder.methods;
    fragment.typeParameters = holder.typeParameters;

    var executables = const <ExecutableElementImpl>[]
        .followedBy(fragment.accessors)
        .followedBy(fragment.methods);
    for (var executable in executables) {
      executable.isExtensionTypeMember = true;
    }

    node.implementsClause?.accept(this);
    elementBuilder.addFragment(fragment);
  }

  @override
  void visitFieldDeclaration(
    covariant FieldDeclarationImpl node,
  ) {
    var metadata = _buildAnnotations(node.metadata);
    for (var variable in node.fields.variables) {
      var nameToken = variable.name;
      var name = nameToken.lexeme;
      var nameOffset = nameToken.offset;

      var fragment = FieldElementImpl(name, nameOffset);
      if (variable.initializer case var initializer?) {
        if (node.fields.isConst) {
          fragment = ConstFieldElementImpl(name, nameOffset)
            ..constantInitializer = initializer;
        } else if (_enclosingContext.constFieldsForFinalInstance) {
          if (node.fields.isFinal && !node.isStatic) {
            var constElement = ConstFieldElementImpl(name, nameOffset)
              ..constantInitializer = initializer;
            fragment = constElement;
            _libraryBuilder.finalInstanceFields.add(constElement);
          }
        }
      }

      fragment.name2 = _getFragmentName(nameToken);
      fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      fragment.hasInitializer = variable.initializer != null;
      fragment.isAbstract = node.abstractKeyword != null;
      fragment.isAugmentation = node.augmentKeyword != null;
      fragment.isConst = node.fields.isConst;
      fragment.isCovariant = node.covariantKeyword != null;
      fragment.isExternal = node.externalKeyword != null;
      fragment.isFinal = node.fields.isFinal;
      fragment.isLate = node.fields.isLate;
      fragment.isStatic = node.isStatic;
      fragment.metadata = metadata;
      _setCodeRange(fragment, variable);
      _setDocumentation(fragment, node);

      if (node.fields.type == null) {
        fragment.hasImplicitType = true;
      }

      var refName = fragment.name2 ?? '${_nextUnnamedId++}';
      _enclosingContext.addNonSyntheticField(refName, fragment);

      _linker.elementNodes[fragment] = variable;
      variable.declaredFragment = fragment;
    }
    _buildType(node.fields.type);
  }

  @override
  void visitFieldFormalParameter(
    covariant FieldFormalParameterImpl node,
  ) {
    var nameToken = node.name;
    var name2 = nameToken.lexeme.nullIfEmpty;
    var nameOffset2 = nameToken.offset.nullIfNegative;

    ParameterElementImpl fragment;
    var parent = node.parent;
    if (parent is DefaultFormalParameterImpl) {
      fragment = DefaultFieldFormalParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      )..constantInitializer = parent.defaultValue;
      _linker.elementNodes[fragment] = parent;
      var refName = node.isNamed ? name2 : null;
      _enclosingContext.addParameter(refName, fragment);
    } else {
      fragment = FieldFormalParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      );
      _linker.elementNodes[fragment] = node;
      _enclosingContext.addParameter(null, fragment);
    }

    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.hasImplicitType = node.type == null && node.parameters == null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);

    node.declaredFragment = fragment;

    // TODO(scheglov): check that we don't set reference for parameters
    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      var formalParameters = node.parameters;
      if (formalParameters != null) {
        formalParameters.accept(this);
        fragment.parameters = holder.parameters;
      }

      var typeParameters = node.typeParameters;
      if (typeParameters != null) {
        typeParameters.accept(this);
        fragment.typeParameters = holder.typeParameters;
      }
    });

    _buildType(node.type);
  }

  @override
  void visitFormalParameterList(FormalParameterList node) {
    node.parameters.accept(this);
  }

  @override
  void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;
    var nameOffset = nameToken.offset;

    var functionExpression = node.functionExpression;
    var body = functionExpression.body;

    Reference reference;
    ExecutableElementImpl executableFragment;
    FragmentedElementBuilder? elementBuilder;
    if (node.isGetter) {
      var getterFragment = GetterFragmentImpl(name, nameOffset);
      getterFragment.name2 = _getFragmentName(nameToken);
      getterFragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      getterFragment.isAugmentation = node.augmentKeyword != null;
      getterFragment.isStatic = true;

      var refName = getterFragment.name2 ?? '${_nextUnnamedId++}';
      reference = _enclosingContext.addGetter(refName, getterFragment);
      executableFragment = getterFragment;

      if (!getterFragment.isAugmentation) {
        var variableFragment = _buildSyntheticVariable(
          name: name,
          accessorElement: getterFragment,
        ) as TopLevelVariableElementImpl;

        var elementBuilder = TopLevelVariableElementBuilder(
          element: variableFragment.element,
          firstFragment: variableFragment,
        );
        _libraryBuilder.elementBuilderVariables[name] = elementBuilder;
      }

      elementBuilder = _libraryBuilder.elementBuilderGetters[name];
      elementBuilder ??= _libraryBuilder.elementBuilderSetters[name];
      elementBuilder?.setPreviousFor(getterFragment);
      if (getterFragment.isAugmentation &&
          elementBuilder is GetterElementBuilder) {
        elementBuilder.addFragment(getterFragment);
      } else {
        elementBuilder = GetterElementBuilder(
          element: GetterElementImpl(getterFragment),
          firstFragment: getterFragment,
        );
        _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
      }
    } else if (node.isSetter) {
      var setterFragment = SetterFragmentImpl(name, nameOffset);
      setterFragment.name2 = _getFragmentName(nameToken);
      setterFragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      setterFragment.isAugmentation = node.augmentKeyword != null;
      setterFragment.isStatic = true;

      var refName = setterFragment.name2 ?? '${_nextUnnamedId++}';
      reference = _enclosingContext.addSetter(refName, setterFragment);
      executableFragment = setterFragment;

      if (!setterFragment.isAugmentation) {
        var variableFragment = _buildSyntheticVariable(
          name: name,
          accessorElement: setterFragment,
        ) as TopLevelVariableElementImpl;

        var elementBuilder = TopLevelVariableElementBuilder(
          element: variableFragment.element,
          firstFragment: variableFragment,
        );
        _libraryBuilder.elementBuilderVariables[name] = elementBuilder;
      }

      elementBuilder = _libraryBuilder.elementBuilderSetters[name];
      elementBuilder ??= _libraryBuilder.elementBuilderGetters[name];
      elementBuilder?.setPreviousFor(setterFragment);
      if (setterFragment.isAugmentation &&
          elementBuilder is SetterElementBuilder) {
        elementBuilder.addFragment(setterFragment);
      } else {
        elementBuilder = SetterElementBuilder(
          element: SetterElementImpl(setterFragment),
          firstFragment: setterFragment,
        );
        _libraryBuilder.elementBuilderSetters[name] = elementBuilder;
      }
    } else {
      var fragment = TopLevelFunctionFragmentImpl(name, nameOffset);
      fragment.name2 = _getFragmentName(nameToken);
      fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      fragment.isAugmentation = node.augmentKeyword != null;
      fragment.isStatic = true;
      executableFragment = fragment;

      var refName = fragment.name2 ?? '${_nextUnnamedId++}';
      reference = _enclosingContext.addFunction(name, fragment);

      elementBuilder = _libraryBuilder.elementBuilderGetters[name];
      elementBuilder ??= _libraryBuilder.elementBuilderSetters[name];
      elementBuilder?.setPreviousFor(fragment);
      if (fragment.isAugmentation &&
          elementBuilder is TopLevelFunctionElementBuilder) {
        elementBuilder.addFragment(fragment);
      } else {
        var libraryRef = _libraryBuilder.reference;
        var containerRef = libraryRef.getChild('@function');
        var elementReference = containerRef.addChild(refName);
        var element = TopLevelFunctionElementImpl(
          elementReference,
          fragment,
        );
        _libraryBuilder.element.topLevelFunctions.add(element);

        elementBuilder = TopLevelFunctionElementBuilder(
          element: element,
          firstFragment: fragment,
        );
        _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
      }
    }

    executableFragment.hasImplicitReturnType = node.returnType == null;
    executableFragment.isAsynchronous = body.isAsynchronous;
    executableFragment.isExternal = node.externalKeyword != null;
    executableFragment.isGenerator = body.isGenerator;
    executableFragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(executableFragment, node);
    _setDocumentation(executableFragment, node);

    node.declaredFragment = executableFragment;
    _linker.elementNodes[executableFragment] = node;

    _buildExecutableElementChildren(
      reference: reference,
      fragment: executableFragment,
      formalParameters: functionExpression.parameters,
      typeParameters: functionExpression.typeParameters,
    );

    var getterOrSetterName = node.isSetter ? '$name=' : name;

    if (!executableFragment.isAugmentation) {
      _libraryBuilder.declare(getterOrSetterName, reference);
    }

    _buildType(node.returnType);
  }

  @override
  void visitFunctionTypeAlias(covariant FunctionTypeAliasImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = TypeAliasElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isFunctionTypeAliasBased = true;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addTypeAlias(refName, fragment);
    _libraryBuilder.declare(name, reference);

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder ??= _libraryBuilder.elementBuilderSetters[name];
    elementBuilder?.setPreviousFor(fragment);
    if (fragment.isAugmentation && elementBuilder is TypeAliasElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@typeAlias');
      var elementReference = containerRef.addChild(refName);
      var element = TypeAliasElementImpl2(elementReference, fragment);
      _libraryBuilder.element.typeAliases.add(element);

      elementBuilder = TypeAliasElementBuilder(
        element: element,
        firstFragment: fragment,
      );
      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
      node.returnType?.accept(this);
      node.parameters.accept(this);
    });

    var aliasedElement = GenericFunctionTypeElementImpl.forOffset(
      nameToken.offset,
    );
    aliasedElement.parameters = holder.parameters;

    fragment.typeParameters = holder.typeParameters;
    fragment.aliasedElement = aliasedElement;

    elementBuilder.addFragment(fragment);
  }

  @override
  void visitFunctionTypedFormalParameter(
    covariant FunctionTypedFormalParameterImpl node,
  ) {
    var nameToken = node.name;
    var name2 = nameToken.lexeme.nullIfEmpty;
    var nameOffset2 = nameToken.offset.nullIfNegative;

    ParameterElementImpl fragment;
    var parent = node.parent;
    if (parent is DefaultFormalParameterImpl) {
      fragment = DefaultParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      )..constantInitializer = parent.defaultValue;
      _linker.elementNodes[fragment] = parent;
    } else {
      fragment = ParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      );
      _linker.elementNodes[fragment] = node;
    }
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isExplicitlyCovariant = node.covariantKeyword != null;
    fragment.isFinal = node.isFinal;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;
    var refName = node.isNamed ? name2 : null;
    _enclosingContext.addParameter(refName, fragment);

    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      var formalParameters = node.parameters;
      formalParameters.accept(this);
      fragment.parameters = holder.parameters;

      var typeParameters = node.typeParameters;
      if (typeParameters != null) {
        typeParameters.accept(this);
        fragment.typeParameters = holder.typeParameters;
      }
    });

    _buildType(node.returnType);
  }

  @override
  void visitGenericFunctionType(covariant GenericFunctionTypeImpl node) {
    var fragment = GenericFunctionTypeElementImpl.forOffset(node.offset);
    _unitElement.encloseElement(fragment);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      var formalParameters = node.parameters;
      formalParameters.accept(this);
      fragment.parameters = holder.parameters;

      var typeParameters = node.typeParameters;
      if (typeParameters != null) {
        typeParameters.accept(this);
        fragment.typeParameters = holder.typeParameters;
      }
    });

    _buildType(node.returnType);
  }

  @override
  void visitGenericTypeAlias(covariant GenericTypeAliasImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = TypeAliasElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAugmentation = node.augmentKeyword != null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addTypeAlias(refName, fragment);
    if (!fragment.isAugmentation) {
      _libraryBuilder.declare(name, reference);
    }

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder ??= _libraryBuilder.elementBuilderSetters[name];
    elementBuilder?.setPreviousFor(fragment);
    if (fragment.isAugmentation && elementBuilder is TypeAliasElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@typeAlias');
      var elementReference = containerRef.addChild(refName);
      var element = TypeAliasElementImpl2(elementReference, fragment);
      _libraryBuilder.element.typeAliases.add(element);

      elementBuilder = TypeAliasElementBuilder(
        element: element,
        firstFragment: fragment,
      );
      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
    });
    fragment.typeParameters = holder.typeParameters;

    var typeNode = node.type;
    typeNode.accept(this);

    if (typeNode is GenericFunctionTypeImpl) {
      fragment.aliasedElement = typeNode.declaredFragment!;
    }

    elementBuilder.addFragment(fragment);
  }

  @override
  void visitImplementsClause(ImplementsClause node) {
    node.interfaces.accept(this);
  }

  @override
  void visitImportDirective(covariant ImportDirectiveImpl node) {
    var index = _importDirectiveIndex++;
    var importElement = _unitElement.libraryImports[index];
    importElement.metadata = _buildAnnotations(node.metadata);
    node.element = importElement;
  }

  @override
  void visitLibraryDirective(covariant LibraryDirectiveImpl node) {}

  @override
  void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;
    var nameOffset = nameToken.offset;

    Reference reference;
    ExecutableElementImpl executableFragment;
    if (node.isGetter) {
      var fragment = GetterFragmentImpl(name, nameOffset);
      fragment.name2 = _getFragmentName(nameToken);
      fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      fragment.isAbstract = node.isAbstract;
      fragment.isAugmentation = node.augmentKeyword != null;
      fragment.isStatic = node.isStatic;

      // `class Enum {}` in `dart:core` declares `int get index` as abstract.
      // But the specification says that practically a different class
      // implementing `Enum` is used as a superclass, so `index` should be
      // considered to have non-abstract implementation.
      if (_enclosingContext.isDartCoreEnum && name == 'index') {
        fragment.isAbstract = false;
      }

      reference = _enclosingContext.addGetter(name, fragment);
      executableFragment = fragment;

      if (!fragment.isAugmentation) {
        var refName = fragment.name2 ?? '${_nextUnnamedId++}';
        _buildSyntheticVariable(name: refName, accessorElement: fragment);
      }
    } else if (node.isSetter) {
      var fragment = SetterFragmentImpl(name, nameOffset);
      fragment.name2 = _getFragmentName(nameToken);
      fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      fragment.isAbstract = node.isAbstract;
      fragment.isAugmentation = node.augmentKeyword != null;
      fragment.isStatic = node.isStatic;

      var refName = fragment.name2 ?? '${_nextUnnamedId++}';
      reference = _enclosingContext.addSetter(refName, fragment);
      executableFragment = fragment;

      if (!fragment.isAugmentation) {
        _buildSyntheticVariable(name: name, accessorElement: fragment);
      }
    } else {
      var isUnaryMinus = false;
      if (nameToken.lexeme == '-') {
        var parameters = node.parameters;
        isUnaryMinus = parameters != null && parameters.parameters.isEmpty;
      }

      if (isUnaryMinus) {
        name = 'unary-';
      }

      var fragment = MethodElementImpl(name, nameOffset);
      fragment.name2 = _getFragmentName(nameToken);
      fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      fragment.isAbstract = node.isAbstract;
      fragment.isAugmentation = node.augmentKeyword != null;
      fragment.isStatic = node.isStatic;

      String refName;
      if (isUnaryMinus) {
        refName = 'unary-';
      } else {
        refName = fragment.name2 ?? '${_nextUnnamedId++}';
      }

      reference = _enclosingContext.addMethod(refName, fragment);
      executableFragment = fragment;
    }
    executableFragment.hasImplicitReturnType = node.returnType == null;
    executableFragment.invokesSuperSelf = node.invokesSuperSelf;
    executableFragment.isAsynchronous = node.body.isAsynchronous;
    executableFragment.isExternal =
        node.externalKeyword != null || node.body is NativeFunctionBody;
    executableFragment.isGenerator = node.body.isGenerator;
    executableFragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(executableFragment, node);
    _setDocumentation(executableFragment, node);

    node.declaredFragment = executableFragment;
    _linker.elementNodes[executableFragment] = node;

    _buildExecutableElementChildren(
      reference: reference,
      fragment: executableFragment,
      formalParameters: node.parameters,
      typeParameters: node.typeParameters,
    );

    _buildType(node.returnType);
  }

  @override
  void visitMixinDeclaration(covariant MixinDeclarationImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = MixinElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.isAugmentation = node.augmentKeyword != null;
    fragment.isBase = node.baseKeyword != null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);
    _setDocumentation(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;

    var refName = fragment.name2 ?? '${_nextUnnamedId++}';
    var reference = _enclosingContext.addMixin(refName, fragment);
    if (!fragment.isAugmentation) {
      _libraryBuilder.declare(name, reference);
    }

    var elementBuilder = _libraryBuilder.elementBuilderGetters[name];
    elementBuilder?.setPreviousFor(fragment);

    // If the fragment is an augmentation, and the corresponding builder
    // has correct type, add the fragment to the builder. Otherwise, create
    // a new builder.
    if (fragment.isAugmentation && elementBuilder is MixinElementBuilder) {
    } else {
      var libraryRef = _libraryBuilder.reference;
      var containerRef = libraryRef.getChild('@mixin');
      var elementReference = containerRef.addChild(refName);
      var element = MixinElementImpl2(elementReference, fragment);
      _libraryBuilder.element.mixins.add(element);

      elementBuilder = MixinElementBuilder(
        firstFragment: fragment,
        element: element,
      );
      _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
    }

    var holder = _EnclosingContext(
      instanceElementBuilder: elementBuilder,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      node.typeParameters?.accept(this);
      _visitPropertyFirst<FieldDeclaration>(node.members);
    });
    fragment.typeParameters = holder.typeParameters;
    fragment.accessors = holder.propertyAccessors;
    fragment.fields = holder.fields;
    fragment.methods = holder.methods;

    node.onClause?.accept(this);
    node.implementsClause?.accept(this);
    elementBuilder.addFragment(fragment);
  }

  @override
  void visitMixinOnClause(MixinOnClause node) {
    node.superclassConstraints.accept(this);
  }

  @override
  void visitNamedType(NamedType node) {
    node.typeArguments?.accept(this);
  }

  @override
  void visitPartDirective(PartDirective node) {
    var index = _partDirectiveIndex++;
    var partElement = _unitElement.parts[index];
    partElement.metadata = _buildAnnotations(node.metadata);
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    var libraryElement = _libraryBuilder.element;
    libraryElement.hasPartOfDirective = true;
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    node.positionalFields.accept(this);
    node.namedFields?.accept(this);
  }

  @override
  void visitRecordTypeAnnotationNamedField(
    RecordTypeAnnotationNamedField node,
  ) {
    node.type.accept(this);
  }

  @override
  void visitRecordTypeAnnotationNamedFields(
    RecordTypeAnnotationNamedFields node,
  ) {
    node.fields.accept(this);
  }

  @override
  void visitRecordTypeAnnotationPositionalField(
    RecordTypeAnnotationPositionalField node,
  ) {
    node.type.accept(this);
  }

  @override
  void visitSimpleFormalParameter(
    covariant SimpleFormalParameterImpl node,
  ) {
    var nameToken = node.name;
    var name2 = nameToken?.lexeme.nullIfEmpty;
    var nameOffset2 = nameToken?.offset;

    ParameterElementImpl fragment;
    var parent = node.parent;
    if (parent is DefaultFormalParameterImpl &&
        _enclosingContext.hasDefaultFormalParameters) {
      fragment = DefaultParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      )..constantInitializer = parent.defaultValue;
      _linker.elementNodes[fragment] = parent;
      var refName = node.isNamed ? name2 : null;
      _enclosingContext.addParameter(refName, fragment);
    } else {
      fragment = ParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      );
      _linker.elementNodes[fragment] = node;
      _enclosingContext.addParameter(null, fragment);
    }

    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.hasImplicitType = node.type == null;
    fragment.isExplicitlyCovariant = node.covariantKeyword != null;
    fragment.isFinal = node.isFinal;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);

    node.declaredFragment = fragment;

    _buildType(node.type);
  }

  @override
  void visitSuperFormalParameter(
    covariant SuperFormalParameterImpl node,
  ) {
    var nameToken = node.name;
    var name2 = nameToken.lexeme.nullIfEmpty;
    var nameOffset2 = nameToken.offset.nullIfNegative;

    SuperFormalParameterElementImpl fragment;
    var parent = node.parent;
    if (parent is DefaultFormalParameterImpl) {
      fragment = DefaultSuperFormalParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      )..constantInitializer = parent.defaultValue;
      _linker.elementNodes[fragment] = parent;
      var refName = node.isNamed ? name2 : null;
      _enclosingContext.addParameter(refName, fragment);
    } else {
      fragment = SuperFormalParameterElementImpl(
        name: name2 ?? '',
        nameOffset: nameOffset2 ?? -1,
        name2: name2,
        nameOffset2: nameOffset2,
        parameterKind: node.kind,
      );
      _linker.elementNodes[fragment] = node;
      _enclosingContext.addParameter(null, fragment);
    }
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.hasImplicitType = node.type == null && node.parameters == null;
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);

    node.declaredFragment = fragment;

    // TODO(scheglov): check that we don't set reference for parameters
    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
    );
    _withEnclosing(holder, () {
      var formalParameters = node.parameters;
      if (formalParameters != null) {
        formalParameters.accept(this);
        fragment.parameters = holder.parameters;
      }

      var typeParameters = node.typeParameters;
      if (typeParameters != null) {
        typeParameters.accept(this);
        fragment.typeParameters = holder.typeParameters;
      }
    });

    _buildType(node.type);
  }

  @override
  void visitTopLevelVariableDeclaration(
    covariant TopLevelVariableDeclarationImpl node,
  ) {
    var enclosingRef = _enclosingContext.fragmentReference;

    var metadata = _buildAnnotations(node.metadata);
    for (var variable in node.variables.variables) {
      var nameToken = variable.name;
      var name = nameToken.lexeme;
      var nameOffset = nameToken.offset;

      TopLevelVariableElementImpl fragment;
      if (node.variables.isConst) {
        fragment = ConstTopLevelVariableElementImpl(name, nameOffset)
          ..constantInitializer = variable.initializer;
      } else {
        fragment = TopLevelVariableElementImpl(name, nameOffset);
      }

      fragment.name2 = _getFragmentName(nameToken);
      fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
      fragment.hasInitializer = variable.initializer != null;
      fragment.isAugmentation = node.augmentKeyword != null;
      fragment.isConst = node.variables.isConst;
      fragment.isExternal = node.externalKeyword != null;
      fragment.isFinal = node.variables.isFinal;
      fragment.isLate = node.variables.isLate;
      fragment.metadata = metadata;
      _setCodeRange(fragment, variable);
      _setDocumentation(fragment, node);

      if (node.variables.type == null) {
        fragment.hasImplicitType = true;
      }

      var refName = fragment.name2 ?? '${_nextUnnamedId++}';
      _enclosingContext.addTopLevelVariable(refName, fragment);

      if (!fragment.isAugmentation) {
        {
          var ref = enclosingRef.getChild('@getter').addChild(refName);
          var getter = fragment.createImplicitGetter(ref);
          _enclosingContext.addPropertyAccessorSynthetic(getter);
          _libraryBuilder.declare(name, ref);

          var elementBuilder = GetterElementBuilder(
            element: GetterElementImpl(getter),
            firstFragment: getter,
          );
          _libraryBuilder.elementBuilderGetters[name] = elementBuilder;
        }

        if (fragment.hasSetter) {
          var ref = enclosingRef.getChild('@setter').addChild(refName);
          var setter = fragment.createImplicitSetter(ref);
          _enclosingContext.addPropertyAccessorSynthetic(setter);
          _libraryBuilder.declare('$name=', ref);

          var elementBuilder = SetterElementBuilder(
            element: SetterElementImpl(setter),
            firstFragment: setter,
          );
          _libraryBuilder.elementBuilderSetters[name] = elementBuilder;
        }
      }

      _linker.elementNodes[fragment] = variable;
      variable.declaredFragment = fragment;

      var elementBuilder = _libraryBuilder.elementBuilderVariables[name];
      elementBuilder ??= _libraryBuilder.elementBuilderGetters[name];
      elementBuilder?.setPreviousFor(fragment);
      if (fragment.isAugmentation &&
          elementBuilder is TopLevelVariableElementBuilder) {
        elementBuilder.addFragment(fragment);
      } else {
        var elementReference = _libraryBuilder.reference
            .getChild('@topLevelVariable')
            .addChild(refName);
        var element = TopLevelVariableElementImpl2(
          elementReference,
          fragment,
        );
        _libraryBuilder.element.topLevelVariables.add(element);

        elementBuilder = TopLevelVariableElementBuilder(
          element: element,
          firstFragment: fragment,
        );
        _libraryBuilder.elementBuilderVariables[name] = elementBuilder;
      }
    }

    _buildType(node.variables.type);
  }

  @override
  void visitTypeArgumentList(TypeArgumentList node) {
    node.arguments.accept(this);
  }

  @override
  void visitTypeParameter(covariant TypeParameterImpl node) {
    var nameToken = node.name;
    var name = nameToken.lexeme;

    var fragment = TypeParameterElementImpl(name, nameToken.offset);
    fragment.name2 = _getFragmentName(nameToken);
    fragment.nameOffset2 = _getFragmentNameOffset(nameToken);
    fragment.metadata = _buildAnnotations(node.metadata);
    _setCodeRange(fragment, node);

    node.declaredFragment = fragment;
    _linker.elementNodes[fragment] = node;
    _enclosingContext.addTypeParameter(name, fragment);

    _buildType(node.bound);
  }

  @override
  void visitTypeParameterList(TypeParameterList node) {
    node.typeParameters.accept(this);
  }

  @override
  void visitWithClause(WithClause node) {
    node.mixinTypes.accept(this);
  }

  List<ElementAnnotationImpl> _buildAnnotations(List<Annotation> nodeList) {
    return _buildAnnotationsWithUnit(_unitElement, nodeList);
  }

  void _buildExecutableElementChildren({
    required Reference reference,
    required ExecutableElementImpl fragment,
    FormalParameterList? formalParameters,
    TypeParameterList? typeParameters,
  }) {
    var holder = _EnclosingContext(
      instanceElementBuilder: null,
      fragment: fragment,
      hasDefaultFormalParameters: true,
    );
    _withEnclosing(holder, () {
      if (formalParameters != null) {
        formalParameters.accept(this);
        fragment.parameters = holder.parameters;
      }
      if (typeParameters != null) {
        typeParameters.accept(this);
        fragment.typeParameters = holder.typeParameters;
      }
    });
  }

  /// The [accessorElement] should not be an augmentation.
  PropertyInducingElementImpl _buildSyntheticVariable({
    required String name,
    required PropertyAccessorElementImpl accessorElement,
  }) {
    var refName = accessorElement.name2 ?? '${_nextUnnamedId++}';
    var enclosingRef = _enclosingContext.fragmentReference;
    var enclosingElement = _enclosingContext.fragment;

    bool canUseExisting(PropertyInducingElementImpl property) {
      return property.isSynthetic ||
          accessorElement.isSetter && property.setter == null;
    }

    PropertyInducingElementImpl? property;
    if (enclosingElement is CompilationUnitElementImpl) {
      // Try to find the variable to attach the accessor.
      var containerRef = enclosingRef.getChild('@topLevelVariable');
      for (var reference in containerRef.getChildrenByName(name)) {
        var existing = reference.element;
        if (existing is TopLevelVariableElementImpl &&
            canUseExisting(existing)) {
          property = existing;
          break;
        }
      }

      // If no variable, add a new one.
      // In error cases could be a duplicate.
      if (property == null) {
        var reference = containerRef.addChild(name);
        var variable = property = TopLevelVariableElementImpl(name, -1)
          ..isSynthetic = true
          ..name2 = accessorElement.name2;
        _enclosingContext.addTopLevelVariableSynthetic(reference, variable);

        var variableElementReference = _libraryBuilder.reference
            .getChild('@topLevelVariable')
            .addChild(refName);
        var variableElement = TopLevelVariableElementImpl2(
          variableElementReference,
          variable,
        );
        _libraryBuilder.element.topLevelVariables.add(variableElement);
      }
    } else {
      // Try to find the variable to attach the accessor.
      var containerRef = enclosingRef.getChild('@field');
      for (var reference in containerRef.getChildrenByName(name)) {
        var existing = reference.element;
        if (existing is FieldElementImpl && canUseExisting(existing)) {
          property = existing;
          break;
        }
      }

      // If no variable, add a new one.
      // In error cases could be a duplicate.
      if (property == null) {
        var reference = containerRef.addChild(name);
        var field = property = FieldElementImpl(name, -1)
          ..isStatic = accessorElement.isStatic
          ..isSynthetic = true
          ..name2 = accessorElement.name2;
        _enclosingContext.addFieldSynthetic(reference, field);
      }
    }

    accessorElement.variable2 = property;
    switch (accessorElement) {
      case GetterFragmentImpl():
        property.getter = accessorElement;
      case SetterFragmentImpl():
        property.setter = accessorElement;
    }
    return property;
  }

  // TODO(scheglov): Maybe inline?
  void _buildType(TypeAnnotation? node) {
    node?.accept(this);
  }

  void _builtRepresentationDeclaration({
    required ExtensionTypeElementImpl extensionFragment,
    required ExtensionTypeDeclarationImpl extensionNode,
    required RepresentationDeclarationImpl representation,
  }) {
    var fieldNameToken = representation.fieldName;
    var fieldName = fieldNameToken.lexeme.ifNotEmptyOrElse('<empty>');

    var fieldFragment = FieldElementImpl(
      fieldName,
      fieldNameToken.offset,
    );
    fieldFragment.name2 = _getFragmentName(fieldNameToken);
    fieldFragment.nameOffset2 = _getFragmentNameOffset(fieldNameToken);
    fieldFragment.isFinal = true;
    fieldFragment.metadata = _buildAnnotations(representation.fieldMetadata);

    var fieldBeginToken =
        representation.fieldMetadata.beginToken ?? representation.fieldType;
    var fieldCodeRangeOffset = fieldBeginToken.offset;
    var fieldCodeRangeLength = fieldNameToken.end - fieldCodeRangeOffset;
    fieldFragment.setCodeRange(fieldCodeRangeOffset, fieldCodeRangeLength);

    representation.fieldFragment = fieldFragment;
    _linker.elementNodes[fieldFragment] = representation;
    _enclosingContext.addNonSyntheticField(fieldName, fieldFragment);

    var nameOffset2 = fieldNameToken.offset.nullIfNegative;

    var formalParameterElement = FieldFormalParameterElementImpl(
      name: fieldName,
      nameOffset: nameOffset2 ?? -1,
      name2: fieldName.nullIfEmpty,
      nameOffset2: nameOffset2,
      parameterKind: ParameterKind.REQUIRED,
    )
      ..field = fieldFragment
      ..hasImplicitType = true;
    formalParameterElement.name2 = _getFragmentName(fieldNameToken);
    formalParameterElement.nameOffset2 = _getFragmentNameOffset(fieldNameToken);
    formalParameterElement.setCodeRange(
      fieldCodeRangeOffset,
      fieldCodeRangeLength,
    );

    {
      String name;
      int? periodOffset;
      int nameOffset;
      int? nameEnd;
      var constructorNameNode = representation.constructorName;
      if (constructorNameNode != null) {
        var nameToken = constructorNameNode.name;
        name = nameToken.lexeme.ifEqualThen('new', '');
        periodOffset = constructorNameNode.period.offset;
        nameOffset = nameToken.offset;
        nameEnd = nameToken.end;
      } else {
        name = '';
        nameOffset = extensionNode.name.offset;
        nameEnd = extensionNode.name.end;
      }

      var constructorFragment = ConstructorElementImpl(name, nameOffset)
        ..isAugmentation = extensionNode.augmentKeyword != null
        ..isConst = extensionNode.constKeyword != null
        ..periodOffset = periodOffset
        ..nameEnd = nameEnd
        ..parameters = [formalParameterElement];
      constructorFragment.typeName = extensionFragment.name2;
      constructorFragment.typeNameOffset = extensionFragment.nameOffset2;
      if (representation.constructorName case var constructorName?) {
        constructorFragment.name2 = constructorName.name.lexeme;
        constructorFragment.nameOffset2 = constructorName.name.offset;
      } else {
        constructorFragment.name2 = 'new';
      }
      _setCodeRange(constructorFragment, representation);

      representation.constructorFragment = constructorFragment;
      _linker.elementNodes[constructorFragment] = representation;
      _enclosingContext.addConstructor(constructorFragment);
    }

    representation.fieldType.accept(this);
  }

  String? _getFragmentName(Token? nameToken) {
    if (nameToken == null || nameToken.isSynthetic) {
      return null;
    }
    return nameToken.lexeme;
  }

  int? _getFragmentNameOffset(Token? nameToken) {
    if (nameToken == null || nameToken.isSynthetic) {
      return null;
    }
    return nameToken.offset;
  }

  void _visitPropertyFirst<T extends AstNode>(List<AstNode> nodes) {
    // When loading from bytes, we read fields first.
    // There is no particular reason for this - we just have to store
    // either non-synthetic fields first, or non-synthetic property
    // accessors first. And we arbitrary decided to store fields first.
    for (var node in nodes) {
      if (node is T) {
        node.accept(this);
      }
    }

    // ...then we load non-synthetic accessors.
    for (var node in nodes) {
      if (node is! T) {
        node.accept(this);
      }
    }
  }

  /// Make the given [context] be the current one while running [f].
  void _withEnclosing(_EnclosingContext context, void Function() f) {
    var previousContext = _enclosingContext;
    _enclosingContext = context;
    try {
      f();
    } finally {
      _enclosingContext = previousContext;
    }
  }

  static List<ElementAnnotationImpl> _buildAnnotationsWithUnit(
    CompilationUnitElementImpl unitElement,
    List<Annotation> nodeList,
  ) {
    var length = nodeList.length;
    if (length == 0) {
      return const <ElementAnnotationImpl>[];
    }

    return List<ElementAnnotationImpl>.generate(length, (index) {
      var ast = nodeList[index] as AnnotationImpl;
      var element = ElementAnnotationImpl(unitElement);
      element.annotationAst = ast;
      ast.elementAnnotation = element;
      return element;
    }, growable: false);
  }

  static void _setCodeRange(ElementImpl element, AstNode node) {
    var parent = node.parent;
    if (node is FormalParameter && parent is DefaultFormalParameter) {
      node = parent;
    }

    if (node is VariableDeclaration && parent is VariableDeclarationList) {
      var fieldDeclaration = parent.parent;
      if (fieldDeclaration != null && parent.variables.first == node) {
        var offset = fieldDeclaration.offset;
        element.setCodeRange(offset, node.end - offset);
        return;
      }
    }

    element.setCodeRange(node.offset, node.length);
  }

  static void _setDocumentation(ElementImpl element, AnnotatedNode node) {
    element.documentationComment =
        getCommentNodeRawText(node.documentationComment);
  }
}

class _EnclosingContext {
  final InstanceElementBuilder? instanceElementBuilder;
  final ElementImpl fragment;
  final List<ClassElementImpl> _classes = [];
  final List<ConstructorElementImpl> _constructors = [];
  final List<EnumElementImpl> _enums = [];
  final List<ExtensionElementImpl> _extensions = [];
  final List<ExtensionTypeElementImpl> _extensionTypes = [];
  final List<FieldElementImpl> _fields = [];
  final List<TopLevelFunctionFragmentImpl> _functions = [];
  final List<MethodElementImpl> _methods = [];
  final List<MixinElementImpl> _mixins = [];
  final List<ParameterElementImpl> _parameters = [];
  final List<PropertyAccessorElementImpl> _propertyAccessors = [];
  final List<TopLevelVariableElementImpl> _topLevelVariables = [];
  final List<TypeAliasElementImpl> _typeAliases = [];
  final List<TypeParameterElementImpl> _typeParameters = [];

  /// A class can have `const` constructors, and if it has we need values
  /// of final instance fields.
  final bool constFieldsForFinalInstance;

  /// Not all optional formal parameters can have default values.
  /// For example, formal parameters of methods can, but formal parameters
  /// of function types - not. This flag specifies if we should create
  /// [ParameterElementImpl]s or [DefaultParameterElementImpl]s.
  final bool hasDefaultFormalParameters;

  _EnclosingContext({
    required this.instanceElementBuilder,
    required this.fragment,
    this.constFieldsForFinalInstance = false,
    this.hasDefaultFormalParameters = false,
  });

  List<ClassElementImpl> get classes {
    return _classes.toFixedList();
  }

  List<ConstructorElementImpl> get constructors {
    return _constructors.toFixedList();
  }

  List<EnumElementImpl> get enums {
    return _enums.toFixedList();
  }

  List<ExtensionElementImpl> get extensions {
    return _extensions.toFixedList();
  }

  List<ExtensionTypeElementImpl> get extensionTypes {
    return _extensionTypes.toFixedList();
  }

  List<FieldElementImpl> get fields {
    return _fields.toFixedList();
  }

  Reference get fragmentReference {
    return fragment.reference!;
  }

  List<TopLevelFunctionFragmentImpl> get functions {
    return _functions.toFixedList();
  }

  bool get isDartCoreEnum {
    var fragment = this.fragment;
    return fragment is ClassElementImpl && fragment.isDartCoreEnum;
  }

  List<MethodElementImpl> get methods {
    return _methods.toFixedList();
  }

  List<MixinElementImpl> get mixins {
    return _mixins.toFixedList();
  }

  List<ParameterElementImpl> get parameters {
    return _parameters.toFixedList();
  }

  List<PropertyAccessorElementImpl> get propertyAccessors {
    return _propertyAccessors.toFixedList();
  }

  List<TopLevelVariableElementImpl> get topLevelVariables {
    return _topLevelVariables.toFixedList();
  }

  List<TypeAliasElementImpl> get typeAliases {
    return _typeAliases.toFixedList();
  }

  List<TypeParameterElementImpl> get typeParameters {
    return _typeParameters.toFixedList();
  }

  Reference addClass(String name, ClassElementImpl element) {
    _classes.add(element);
    var containerName =
        element.isAugmentation ? '@classAugmentation' : '@class';
    return _addReference(containerName, name, element);
  }

  Reference addConstructor(ConstructorElementImpl element) {
    _constructors.add(element);

    var containerName =
        element.isAugmentation ? '@constructorAugmentation' : '@constructor';
    var referenceName = element.name.ifNotEmptyOrElse('new');
    return _addReference(containerName, referenceName, element);
  }

  Reference addEnum(String name, EnumElementImpl element) {
    _enums.add(element);
    var containerName = element.isAugmentation ? '@enumAugmentation' : '@enum';
    return _addReference(containerName, name, element);
  }

  Reference addExtension(String name, ExtensionElementImpl element) {
    _extensions.add(element);
    var containerName =
        element.isAugmentation ? '@extensionAugmentation' : '@extension';
    return _addReference(containerName, name, element);
  }

  Reference addExtensionType(String name, ExtensionTypeElementImpl element) {
    _extensionTypes.add(element);
    var containerName = element.isAugmentation
        ? '@extensionTypeAugmentation'
        : '@extensionType';
    return _addReference(containerName, name, element);
  }

  Reference addField(String name, FieldElementImpl element) {
    _fields.add(element);
    var containerName =
        element.isAugmentation ? '@fieldAugmentation' : '@field';
    return _addReference(containerName, name, element);
  }

  void addFieldSynthetic(Reference reference, FieldElementImpl element) {
    _fields.add(element);
    _bindReference(reference, element);
  }

  Reference addFunction(String name, TopLevelFunctionFragmentImpl element) {
    _functions.add(element);
    var containerName =
        element.isAugmentation ? '@functionAugmentation' : '@function';
    return _addReference(containerName, name, element);
  }

  Reference addGetter(String name, PropertyAccessorElementImpl element) {
    _propertyAccessors.add(element);
    var containerName =
        element.isAugmentation ? '@getterAugmentation' : '@getter';
    return _addReference(containerName, name, element);
  }

  Reference addMethod(String name, MethodElementImpl element) {
    _methods.add(element);
    var containerName =
        element.isAugmentation ? '@methodAugmentation' : '@method';
    return _addReference(containerName, name, element);
  }

  Reference addMixin(String name, MixinElementImpl element) {
    _mixins.add(element);
    var containerName =
        element.isAugmentation ? '@mixinAugmentation' : '@mixin';
    return _addReference(containerName, name, element);
  }

  void addNonSyntheticField(String name, FieldElementImpl element) {
    addField(name, element);

    // Augmenting a variable with a variable only alters its initializer.
    // So, don't create getter and setter.
    if (element.isAugmentation) {
      return;
    }

    {
      var getterRef = fragmentReference.getChild('@getter').addChild(name);
      var getter = element.createImplicitGetter(getterRef);
      _propertyAccessors.add(getter);
    }

    if (element.hasSetter) {
      var setterRef = fragmentReference.getChild('@setter').addChild(name);
      var setter = element.createImplicitSetter(setterRef);
      _propertyAccessors.add(setter);
    }
  }

  Reference? addParameter(String? name, ParameterElementImpl element) {
    _parameters.add(element);
    if (name == null) {
      return null;
    }
    if (fragment.reference == null) {
      return null;
    }
    return _addReference('@parameter', name, element);
  }

  void addPropertyAccessorSynthetic(PropertyAccessorElementImpl element) {
    _propertyAccessors.add(element);
  }

  Reference addSetter(String name, PropertyAccessorElementImpl element) {
    _propertyAccessors.add(element);
    var containerName =
        element.isAugmentation ? '@setterAugmentation' : '@setter';
    return _addReference(containerName, name, element);
  }

  Reference addTopLevelVariable(
      String name, TopLevelVariableElementImpl element) {
    _topLevelVariables.add(element);
    var containerName = element.isAugmentation
        ? '@topLevelVariableAugmentation'
        : '@topLevelVariable';
    return _addReference(containerName, name, element);
  }

  void addTopLevelVariableSynthetic(
      Reference reference, TopLevelVariableElementImpl element) {
    _topLevelVariables.add(element);
    _bindReference(reference, element);
  }

  Reference addTypeAlias(String name, TypeAliasElementImpl element) {
    _typeAliases.add(element);
    var containerName =
        element.isAugmentation ? '@typeAliasAugmentation' : '@typeAlias';
    return _addReference(containerName, name, element);
  }

  void addTypeParameter(String name, TypeParameterElementImpl fragment) {
    _typeParameters.add(fragment);
    this.fragment.encloseElement(fragment);
  }

  Reference _addReference(
    String containerName,
    String name,
    ElementImpl element,
  ) {
    var containerRef = fragmentReference.getChild(containerName);
    var reference = containerRef.addChild(name);
    _bindReference(reference, element);
    return reference;
  }

  void _bindReference(Reference reference, ElementImpl fragment) {
    reference.element = fragment;
    fragment.reference = reference;
    this.fragment.encloseElement(fragment);
  }
}
