// Copyright (c) 2019, 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/doc_comment.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:analyzer_utilities/testing/tree_string_sink.dart';
import 'package:test/test.dart';

import '../../util/element_printer.dart';

/// Prints AST as a tree, with properties and children.
class ResolvedAstPrinter extends ThrowingAstVisitor<void> {
  final TreeStringSink _sink;
  final ElementPrinter _elementPrinter;
  final ResolvedNodeTextConfiguration configuration;

  /// If `true`, selected tokens and nodes should be printed with offsets.
  final bool _withOffsets;

  /// If `true`, resolution should be printed.
  final bool _withResolution;

  final Map<Token, String> _tokenIdMap = Map.identity();

  ResolvedAstPrinter({
    required TreeStringSink sink,
    required ElementPrinter elementPrinter,
    required this.configuration,
    bool withOffsets = false,
    bool withResolution = true,
  })  : _sink = sink,
        _elementPrinter = elementPrinter,
        _withOffsets = withOffsets,
        _withResolution = withResolution;

  @override
  void visitAdjacentStrings(AdjacentStrings node) {
    _sink.writeln('AdjacentStrings');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
      _writeRaw('stringValue', node.stringValue);
    });
  }

  @override
  void visitAnnotation(Annotation node) {
    _sink.writeln('Annotation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
    });
  }

  @override
  void visitArgumentList(ArgumentList node) {
    _sink.writeln('ArgumentList');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitAsExpression(AsExpression node) {
    _sink.writeln('AsExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitAssertInitializer(AssertInitializer node) {
    _sink.writeln('AssertInitializer');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitAssertStatement(AssertStatement node) {
    _sink.writeln('AssertStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitAssignedVariablePattern(
    covariant AssignedVariablePatternImpl node,
  ) {
    _sink.writeln('AssignedVariablePattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        _writeElement2('element2', node.element2);
        _writePatternMatchedValueType(node);
      }
    });
  }

  @override
  void visitAssignmentExpression(AssignmentExpression node) {
    _sink.writeln('AssignmentExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeElement2('readElement2', node.readElement2);
      _writeType('readType', node.readType);
      _writeElement2('writeElement2', node.writeElement2);
      _writeType('writeType', node.writeType);
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitAugmentedExpression(AugmentedExpression node) {
    _sink.writeln('AugmentedExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeFragment('fragment', node.fragment);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitAugmentedInvocation(AugmentedInvocation node) {
    _sink.writeln('AugmentedInvocation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeFragment('fragment', node.fragment);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitAwaitExpression(AwaitExpression node) {
    _sink.writeln('AwaitExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitBinaryExpression(BinaryExpression node) {
    _sink.writeln('BinaryExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeElement2('element', node.element);
      _writeType('staticInvokeType', node.staticInvokeType);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitBlock(Block node) {
    _sink.writeln('Block');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitBlockFunctionBody(BlockFunctionBody node) {
    _sink.writeln('BlockFunctionBody');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitBooleanLiteral(BooleanLiteral node) {
    _sink.writeln('BooleanLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitBreakStatement(BreakStatement node) {
    _sink.writeln('BreakStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitCascadeExpression(CascadeExpression node) {
    _sink.writeln('CascadeExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitCaseClause(CaseClause node) {
    _sink.writeln('CaseClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitCastPattern(CastPattern node) {
    _sink.writeln('CastPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitCatchClause(CatchClause node) {
    _sink.writeln('CatchClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitCatchClauseParameter(CatchClauseParameter node) {
    _sink.writeln('CatchClauseParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitClassDeclaration(ClassDeclaration node) {
    _sink.writeln('ClassDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitClassTypeAlias(ClassTypeAlias node) {
    _sink.writeln('ClassTypeAlias');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitComment(Comment node) {
    _sink.writeln('Comment');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (node.codeBlocks.isNotEmpty) {
        _sink.writelnWithIndent('codeBlocks');
        _sink.withIndent(() {
          for (var codeBlock in node.codeBlocks) {
            _writeMdCodeBlock(codeBlock);
          }
        });
      }
      if (node.docImports.isNotEmpty) {
        _sink.writelnWithIndent('docImports');
        _sink.withIndent(() {
          for (var docImport in node.docImports) {
            _writeDocImport(docImport);
          }
        });
      }
      if (node.docDirectives.isNotEmpty) {
        _sink.writelnWithIndent('docDirectives');
        _sink.withIndent(() {
          for (var docDirective in node.docDirectives) {
            switch (docDirective) {
              case SimpleDocDirective():
                _sink.writelnWithIndent('SimpleDocDirective');
                _sink.withIndent(() {
                  _sink.writelnWithIndent('tag');
                  _writeDocDirectiveTag(docDirective.tag);
                });
              case BlockDocDirective(:var openingTag, :var closingTag):
                _sink.writelnWithIndent('BlockDocDirective');
                _sink.withIndent(() {
                  _sink.writelnWithIndent('openingTag');
                  _writeDocDirectiveTag(openingTag);
                  if (closingTag != null) {
                    _sink.writelnWithIndent('closingTag');
                    _writeDocDirectiveTag(closingTag);
                  }
                });
            }
          }
        });
      }
      if (node.hasNodoc) {
        _sink.writelnWithIndent('hasNodoc: true');
      }
    });
  }

  @override
  void visitCommentReference(CommentReference node) {
    _sink.writeln('CommentReference');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitCompilationUnit(CompilationUnit node) {
    _sink.writeln('CompilationUnit');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitConditionalExpression(ConditionalExpression node) {
    _sink.writeln('ConditionalExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitConfiguration(Configuration node) {
    _sink.writeln('Configuration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
    _sink.withIndent(() {
      _sink.writeWithIndent('resolvedUri: ');
      _elementPrinter.writeDirectiveUri(node.resolvedUri);
    });
  }

  @override
  void visitConstantPattern(ConstantPattern node) {
    _sink.writeln('ConstantPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitConstructorDeclaration(ConstructorDeclaration node) {
    _sink.writeln('ConstructorDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
    _sink.writeln('ConstructorFieldInitializer');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitConstructorName(ConstructorName node) {
    _sink.writeln('ConstructorName');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element', node.element);
    });
  }

  @override
  void visitConstructorReference(ConstructorReference node) {
    _sink.writeln('ConstructorReference');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitConstructorSelector(ConstructorSelector node) {
    _checkChildrenEntitiesLinking(node);
    _sink.writeln('ConstructorSelector');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitContinueStatement(ContinueStatement node) {
    _sink.writeln('ContinueStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitDeclaredIdentifier(DeclaredIdentifier node) {
    _sink.writeln('DeclaredIdentifier');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitDeclaredVariablePattern(
    covariant DeclaredVariablePatternImpl node,
  ) {
    _sink.writeln('DeclaredVariablePattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        var fragment = node.declaredFragment;
        if (fragment != null) {
          _sink.writeWithIndent('declaredElement: ');
          _sink.writeIf(fragment.hasImplicitType, 'hasImplicitType ');
          _sink.writeIf(fragment.isFinal, 'isFinal ');
          _sink.writeln('${fragment.name}@${fragment.nameOffset}');
          _sink.withIndent(() {
            _writeType('type', fragment.type);
          });
        }
        _writePatternMatchedValueType(node);
      }
    });
  }

  @override
  void visitDefaultFormalParameter(DefaultFormalParameter node) {
    _sink.writeln('DefaultFormalParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _assertFormalParameterDeclaredElement(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitDoStatement(DoStatement node) {
    _sink.writeln('DoStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitDotShorthandInvocation(DotShorthandInvocation node) {
    _sink.writeln('DotShorthandInvocation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticInvokeType', node.staticInvokeType);
      _writeType('staticType', node.staticType);
      _writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
    });
  }

  @override
  void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
    _sink.writeln('DotShorthandPropertyAccess');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitDottedName(DottedName node) {
    _sink.writeln('DottedName');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitDoubleLiteral(DoubleLiteral node) {
    _sink.writeln('DoubleLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitEmptyFunctionBody(EmptyFunctionBody node) {
    _sink.writeln('EmptyFunctionBody');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitEnumConstantArguments(EnumConstantArguments node) {
    _checkChildrenEntitiesLinking(node);
    _sink.writeln('EnumConstantArguments');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
    _checkChildrenEntitiesLinking(node);
    _sink.writeln('EnumConstantDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        _writeElement2('constructorElement2', node.constructorElement2);
        _writeDeclaredFragment(node.declaredFragment);
      }
    });
  }

  @override
  void visitEnumDeclaration(EnumDeclaration node) {
    _sink.writeln('EnumDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitExportDirective(ExportDirective node) {
    _sink.writeln('ExportDirective');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        _elementPrinter.writeLibraryExport('libraryExport', node.libraryExport);
      }
    });
  }

  @override
  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
    _sink.writeln('ExpressionFunctionBody');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitExpressionStatement(ExpressionStatement node) {
    _sink.writeln('ExpressionStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitExtendsClause(ExtendsClause node) {
    _sink.writeln('ExtendsClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitExtensionDeclaration(ExtensionDeclaration node) {
    _sink.writeln('ExtensionDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitExtensionOnClause(ExtensionOnClause node) {
    _sink.writeln('ExtensionOnClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitExtensionOverride(ExtensionOverride node) {
    _sink.writeln('ExtensionOverride');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
      _writeType('extendedType', node.extendedType);
      _writeType('staticType', node.staticType);
      _writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
    });
  }

  @override
  void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
    _sink.writeln('ExtensionTypeDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitFieldDeclaration(FieldDeclaration node) {
    _sink.writeln('FieldDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitFieldFormalParameter(FieldFormalParameter node) {
    _sink.writeln('FieldFormalParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _assertFormalParameterDeclaredElement(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
    _sink.writeln('ForEachPartsWithDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
    _sink.writeln('ForEachPartsWithIdentifier');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
    _sink.writeln('ForEachPartsWithPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForElement(ForElement node) {
    _sink.writeln('ForElement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitFormalParameterList(FormalParameterList node) {
    _sink.writeln('FormalParameterList');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
    _sink.writeln('ForPartsWithDeclarations');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForPartsWithExpression(ForPartsWithExpression node) {
    _sink.writeln('ForPartsWithExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForPartsWithPattern(ForPartsWithPattern node) {
    _sink.writeln('ForPartsWithPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitForStatement(ForStatement node) {
    _sink.writeln('ForStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitFunctionDeclaration(FunctionDeclaration node) {
    _sink.writeln('FunctionDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
    _sink.writeln('FunctionDeclarationStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitFunctionExpression(FunctionExpression node) {
    _sink.writeln('FunctionExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
    _sink.writeln('FunctionExpressionInvocation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element', node.element);
      _writeType('staticInvokeType', node.staticInvokeType);
      _writeType('staticType', node.staticType);
      _writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
    });
  }

  @override
  void visitFunctionReference(FunctionReference node) {
    _sink.writeln('FunctionReference');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
      _writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
    });
  }

  @override
  void visitFunctionTypeAlias(FunctionTypeAlias node) {
    _sink.writeln('FunctionTypeAlias');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
    _sink.writeln('FunctionTypedFormalParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _assertFormalParameterDeclaredElement(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitGenericFunctionType(covariant GenericFunctionTypeImpl node) {
    _sink.writeln('GenericFunctionType');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        _writeGenericFunctionTypeElement(
          'declaredElement',
          node.declaredFragment,
        );
      }
      _writeType('type', node.type);
    });
  }

  @override
  void visitGenericTypeAlias(GenericTypeAlias node) {
    _sink.writeln('GenericTypeAlias');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitGuardedPattern(GuardedPattern node) {
    _sink.writeln('GuardedPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitHideCombinator(HideCombinator node) {
    _sink.writeln('HideCombinator');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitIfElement(IfElement node) {
    _sink.writeln('IfElement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitIfStatement(IfStatement node) {
    _sink.writeln('IfStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitImplementsClause(ImplementsClause node) {
    _sink.writeln('ImplementsClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitImplicitCallReference(ImplicitCallReference node) {
    _sink.writeln('ImplicitCallReference');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
      _writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
    });
  }

  @override
  void visitImportDirective(ImportDirective node) {
    _sink.writeln('ImportDirective');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        _elementPrinter.writeLibraryImport('libraryImport', node.libraryImport);
      }
    });
  }

  @override
  void visitImportPrefixReference(ImportPrefixReference node) {
    _sink.writeln('ImportPrefixReference');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
    });
  }

  @override
  void visitIndexExpression(IndexExpression node) {
    _sink.writeln('IndexExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitInstanceCreationExpression(InstanceCreationExpression node) {
    _sink.writeln('InstanceCreationExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitIntegerLiteral(IntegerLiteral node) {
    _sink.writeln('IntegerLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitInterpolationExpression(InterpolationExpression node) {
    _sink.writeln('InterpolationExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitInterpolationString(InterpolationString node) {
    _sink.writeln('InterpolationString');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitIsExpression(IsExpression node) {
    _sink.writeln('IsExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitLabel(Label node) {
    _sink.writeln('Label');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitLabeledStatement(LabeledStatement node) {
    _sink.writeln('LabeledStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitLibraryDirective(LibraryDirective node) {
    _sink.writeln('LibraryDirective');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
    });
  }

  @override
  void visitLibraryIdentifier(LibraryIdentifier node) {
    _sink.writeln('LibraryIdentifier');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitListLiteral(ListLiteral node) {
    _sink.writeln('ListLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitListPattern(ListPattern node) {
    _sink.writeln('ListPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
      _writeType('requiredType', node.requiredType);
    });
  }

  @override
  void visitLogicalAndPattern(LogicalAndPattern node) {
    _sink.writeln('LogicalAndPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitLogicalOrPattern(LogicalOrPattern node) {
    _sink.writeln('LogicalOrPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitMapLiteralEntry(MapLiteralEntry node) {
    _sink.writeln('MapLiteralEntry');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitMapPattern(MapPattern node) {
    _sink.writeln('MapPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
      _writeType('requiredType', node.requiredType);
    });
  }

  @override
  void visitMapPatternEntry(MapPatternEntry node) {
    _sink.writeln('MapPatternEntry');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    _sink.writeln('MethodDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitMethodInvocation(MethodInvocation node) {
    _sink.writeln('MethodInvocation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticInvokeType', node.staticInvokeType);
      _writeType('staticType', node.staticType);
      _writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
    });
  }

  @override
  void visitMixinDeclaration(MixinDeclaration node) {
    _sink.writeln('MixinDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitMixinOnClause(MixinOnClause node) {
    _sink.writeln('MixinOnClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitNamedExpression(NamedExpression node) {
    _sink.writeln('NamedExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      // Types of the node and its expression must be the same.
      if (node.expression.staticType != node.staticType) {
        var nodeType = node.staticType;
        var expressionType = node.expression.staticType;
        fail(
          'Must be the same:\n'
          'nodeType: $nodeType\n'
          'expressionType: $expressionType',
        );
      }
    });
  }

  @override
  void visitNamedType(NamedType node) {
    _sink.writeln('NamedType');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
      _writeType('type', node.type);
    });
  }

  @override
  void visitNullAssertPattern(NullAssertPattern node) {
    _sink.writeln('NullAssertPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitNullAwareElement(NullAwareElement node) {
    _sink.writeln('NullAwareElement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitNullCheckPattern(NullCheckPattern node) {
    _sink.writeln('NullCheckPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitNullLiteral(NullLiteral node) {
    _sink.writeln('NullLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitObjectPattern(ObjectPattern node) {
    _sink.writeln('ObjectPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitParenthesizedExpression(ParenthesizedExpression node) {
    _sink.writeln('ParenthesizedExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitParenthesizedPattern(ParenthesizedPattern node) {
    _sink.writeln('ParenthesizedPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitPartDirective(PartDirective node) {
    _sink.writeln('PartDirective');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      if (_withResolution) {
        _elementPrinter.writePartInclude('partInclude', node.partInclude);
      }
    });
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    _sink.writeln('PartOfDirective');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitPatternAssignment(covariant PatternAssignmentImpl node) {
    _sink.writeln('PatternAssignment');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeType('patternTypeSchema', node.patternTypeSchema);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitPatternField(PatternField node) {
    _sink.writeln('PatternField');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
    });
  }

  @override
  void visitPatternFieldName(PatternFieldName node) {
    _sink.writeln('PatternFieldName');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitPatternVariableDeclaration(
    covariant PatternVariableDeclarationImpl node,
  ) {
    _sink.writeln('PatternVariableDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeType('patternTypeSchema', node.patternTypeSchema);
    });
  }

  @override
  void visitPatternVariableDeclarationStatement(
      PatternVariableDeclarationStatement node) {
    _sink.writeln('PatternVariableDeclarationStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitPostfixExpression(PostfixExpression node) {
    _sink.writeln('PostfixExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      if (node.operator.type.isIncrementOperator) {
        _writeElement2('readElement2', node.readElement2);
        _writeType('readType', node.readType);
        _writeElement2('writeElement2', node.writeElement2);
        _writeType('writeType', node.writeType);
      }
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitPrefixedIdentifier(PrefixedIdentifier node) {
    _sink.writeln('PrefixedIdentifier');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitPrefixExpression(PrefixExpression node) {
    _sink.writeln('PrefixExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      if (node.operator.type.isIncrementOperator) {
        _writeElement2('readElement2', node.readElement2);
        _writeType('readType', node.readType);
        _writeElement2('writeElement2', node.writeElement2);
        _writeType('writeType', node.writeType);
      }
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitPropertyAccess(PropertyAccess node) {
    _sink.writeln('PropertyAccess');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitRecordLiteral(RecordLiteral node) {
    _sink.writeln('RecordLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitRecordPattern(RecordPattern node) {
    _sink.writeln('RecordPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    _sink.writeln('RecordTypeAnnotation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeType('type', node.type);
    });
  }

  @override
  void visitRecordTypeAnnotationNamedField(
      RecordTypeAnnotationNamedField node) {
    _sink.writeln('RecordTypeAnnotationNamedField');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitRecordTypeAnnotationNamedFields(
      RecordTypeAnnotationNamedFields node) {
    _sink.writeln('RecordTypeAnnotationNamedFields');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitRecordTypeAnnotationPositionalField(
      RecordTypeAnnotationPositionalField node) {
    _sink.writeln('RecordTypeAnnotationPositionalField');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitRedirectingConstructorInvocation(
    RedirectingConstructorInvocation node,
  ) {
    _sink.writeln('RedirectingConstructorInvocation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element', node.element);
    });
  }

  @override
  void visitRelationalPattern(RelationalPattern node) {
    _sink.writeln('RelationalPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element2', node.element2);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitRepresentationConstructorName(RepresentationConstructorName node) {
    _sink.writeln('RepresentationConstructorName');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitRepresentationDeclaration(RepresentationDeclaration node) {
    _sink.writeln('RepresentationDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeFragment('fieldFragment', node.fieldFragment);
      _writeFragment('constructorFragment', node.constructorFragment);
    });
  }

  @override
  void visitRestPatternElement(RestPatternElement node) {
    _sink.writeln('RestPatternElement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitRethrowExpression(RethrowExpression node) {
    _sink.writeln('RethrowExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitReturnStatement(ReturnStatement node) {
    _sink.writeln('ReturnStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSetOrMapLiteral(SetOrMapLiteral node) {
    _sink.writeln('SetOrMapLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeRaw('isMap', node.isMap);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitShowCombinator(ShowCombinator node) {
    _sink.writeln('ShowCombinator');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSimpleFormalParameter(SimpleFormalParameter node) {
    _sink.writeln('SimpleFormalParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitSimpleIdentifier(SimpleIdentifier node) {
    _sink.writeln('SimpleIdentifier');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeElement2('element', node.element);
      _writeType('staticType', node.staticType);
      _writeTypeList(
        'tearOffTypeArgumentTypes',
        node.tearOffTypeArgumentTypes,
      );
    });
  }

  @override
  void visitSimpleStringLiteral(SimpleStringLiteral node) {
    _sink.writeln('SimpleStringLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSpreadElement(SpreadElement node) {
    _sink.writeln('SpreadElement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitStringInterpolation(StringInterpolation node) {
    _sink.writeln('StringInterpolation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
      _writeRaw('stringValue', node.stringValue);
    });
  }

  @override
  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
    _sink.writeln('SuperConstructorInvocation');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeElement2('element', node.element);
    });
  }

  @override
  void visitSuperExpression(SuperExpression node) {
    _sink.writeln('SuperExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitSuperFormalParameter(SuperFormalParameter node) {
    _checkChildrenEntitiesLinking(node);
    _sink.writeln('SuperFormalParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _assertFormalParameterDeclaredElement(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitSwitchCase(SwitchCase node) {
    _sink.writeln('SwitchCase');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSwitchDefault(SwitchDefault node) {
    _sink.writeln('SwitchDefault');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSwitchExpression(SwitchExpression node) {
    _sink.writeln('SwitchExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitSwitchExpressionCase(SwitchExpressionCase node) {
    _sink.writeln('SwitchExpressionCase');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSwitchPatternCase(SwitchPatternCase node) {
    _sink.writeln('SwitchPatternCase');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSwitchStatement(SwitchStatement node) {
    _sink.writeln('SwitchStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitSymbolLiteral(SymbolLiteral node) {
    _sink.writeln('SymbolLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
    });
  }

  @override
  void visitThisExpression(ThisExpression node) {
    _sink.writeln('ThisExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitThrowExpression(ThrowExpression node) {
    _sink.writeln('ThrowExpression');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
    _sink.writeln('TopLevelVariableDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitTryStatement(TryStatement node) {
    _sink.writeln('TryStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitTypeArgumentList(TypeArgumentList node) {
    _sink.writeln('TypeArgumentList');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitTypeLiteral(TypeLiteral node) {
    _sink.writeln('TypeLiteral');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeParameterElement(node);
      _writeType('staticType', node.staticType);
    });
  }

  @override
  void visitTypeParameter(TypeParameter node) {
    _sink.writeln('TypeParameter');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitTypeParameterList(TypeParameterList node) {
    _sink.writeln('TypeParameterList');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitVariableDeclaration(VariableDeclaration node) {
    _sink.writeln('VariableDeclaration');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writeDeclaredFragment(node.declaredFragment);
    });
  }

  @override
  void visitVariableDeclarationList(VariableDeclarationList node) {
    _sink.writeln('VariableDeclarationList');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
    _sink.writeln('VariableDeclarationStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitWhenClause(WhenClause node) {
    _sink.writeln('WhenClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitWhileStatement(WhileStatement node) {
    _sink.writeln('WhileStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitWildcardPattern(
    covariant WildcardPatternImpl node,
  ) {
    _sink.writeln('WildcardPattern');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
      _writePatternMatchedValueType(node);
    });
  }

  @override
  void visitWithClause(WithClause node) {
    _sink.writeln('WithClause');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  @override
  void visitYieldStatement(YieldStatement node) {
    _sink.writeln('YieldStatement');
    _sink.withIndent(() {
      _writeNamedChildEntities(node);
    });
  }

  void _assertFormalParameterDeclaredElement(FormalParameter node) {
    if (_withResolution) {
      var declaredFragment = node.declaredFragment;
      var expected = _expectedFormalParameterElements(node);
      _assertHasIdenticalElement(expected, declaredFragment);
    }
  }

  /// Check that children entities of the [node] link to each other.
  void _checkChildrenEntitiesLinking(AstNode node) {
    Token? lastEnd;
    for (var entity in node.childEntities) {
      if (lastEnd != null) {
        var begin = _entityBeginToken(entity);
        expect(lastEnd.next, begin);
        expect(begin.previous, lastEnd);
      }
      lastEnd = _entityEndToken(entity);
    }
  }

  /// Check that the actual parent of [child] is [parent].
  void _checkParentOfChild(AstNode parent, AstNode child) {
    var actualParent = child.parent;
    if (actualParent == null) {
      fail('''
No parent.
Child: (${child.runtimeType}) $child
Expected parent: (${parent.runtimeType}) $parent
''');
    } else if (actualParent != parent) {
      fail('''
Wrong parent.
Child: (${child.runtimeType}) $child
Actual parent: (${actualParent.runtimeType}) $actualParent
Expected parent: (${parent.runtimeType}) $parent
''');
    }
  }

  String _getTokenId(Token? token) {
    if (token == null) {
      return '<null>';
    }
    return _tokenIdMap[token] ??= 'T${_tokenIdMap.length}';
  }

  void _writeDeclaredFragment(Fragment? fragment) {
    if (_withResolution) {
      if (fragment is LocalVariableElementImpl) {
        _sink.writeWithIndent('declaredElement:');
        _sink.writeIf(fragment.hasImplicitType, ' hasImplicitType');
        _sink.writeIf(fragment.isConst, ' isConst');
        _sink.writeIf(fragment.isFinal, ' isFinal');
        _sink.writeIf(fragment.isLate, ' isLate');
        // TODO(scheglov): This crashes.
        // _writeIf(element.hasInitializer, ' hasInitializer');
        _sink.writeln(' ${fragment.name}@${fragment.nameOffset}');
        _sink.withIndent(() {
          _writeType('type', fragment.type);
        });
      } else {
        _writeFragment('declaredElement', fragment);
        if (fragment is ExecutableElementImpl) {
          _sink.withIndent(() {
            _writeType('type', fragment.type);
          });
        } else if (fragment is ParameterElementImpl) {
          _sink.withIndent(() {
            _writeType('type', fragment.type);
          });
        }
      }
    }
  }

  void _writeDocDirectiveTag(DocDirectiveTag docDirective) {
    _sink.withIndent(() {
      _sink.writelnWithIndent(
          'offset: [${docDirective.offset}, ${docDirective.end}]');
      _sink.writelnWithIndent('type: [${docDirective.type}]');
      if (docDirective.positionalArguments.isNotEmpty) {
        _sink.writelnWithIndent('positionalArguments');
        _sink.withIndent(() {
          for (var argument in docDirective.positionalArguments) {
            _sink.writelnWithIndent(argument.value);
          }
        });
      }
      if (docDirective.namedArguments.isNotEmpty) {
        _sink.writelnWithIndent('namedArguments');
        _sink.withIndent(() {
          for (var argument in docDirective.namedArguments) {
            _sink.writeWithIndent(argument.name);
            _sink.writeln('=${argument.value}');
          }
        });
      }
    });
  }

  void _writeDocImport(DocImport docImport) {
    _sink.writelnWithIndent('DocImport');
    _sink.withIndent(() {
      _sink.writelnWithIndent('offset: ${docImport.offset}');
      _writeNode('import', docImport.import);
    });
  }

  void _writeElement2(String name, Element2? element) {
    if (_withResolution) {
      _elementPrinter.writeNamedElement2(name, element);
    }
  }

  void _writeFragment(String name, Fragment? fragment) {
    if (_withResolution) {
      _elementPrinter.writeNamedFragment(name, fragment);
    }
  }

  void _writeGenericFunctionTypeElement(
    String name,
    GenericFunctionTypeElementImpl? element,
  ) {
    _sink.writeWithIndent('$name: ');
    if (element == null) {
      _sink.writeln('<null>');
    } else {
      _sink.withIndent(() {
        _sink.writeln('GenericFunctionTypeElement');
        _writeParameterElements(element.parameters);
        _writeType('returnType', element.returnType);
        _writeType('type', element.type);
      });
    }
  }

  void _writeMdCodeBlock(MdCodeBlock codeBlock) {
    _sink.writelnWithIndent('MdCodeBlock');
    _sink.withIndent(() {
      var infoString = codeBlock.infoString;
      _sink.writelnWithIndent('infoString: ${infoString ?? '<empty>'}');
      _sink.writelnWithIndent('type: ${codeBlock.type}');
      assert(codeBlock.lines.isNotEmpty);
      _sink.writelnWithIndent('lines');
      _sink.withIndent(() {
        for (var line in codeBlock.lines) {
          _sink.writelnWithIndent('MdCodeBlockLine');
          _sink.withIndent(() {
            _sink.writelnWithIndent('offset: ${line.offset}');
            _sink.writelnWithIndent('length: ${line.length}');
          });
        }
      });
    });
  }

  void _writeNamedChildEntities(AstNode node) {
    node as AstNodeImpl;
    for (var entity in node.namedChildEntities) {
      var value = entity.value;
      if (value is Token) {
        _writeToken(entity.name, value);
      } else if (value is AstNode) {
        _checkParentOfChild(node, value);
        if (value is ArgumentList && configuration.skipArgumentList) {
        } else {
          _writeNode(entity.name, value);
        }
      } else if (value is List<Token>) {
        _writeTokenList(entity.name, value);
      } else if (value is List<AstNode>) {
        _writeNodeList(node, entity.name, value);
      } else {
        throw UnimplementedError('(${value.runtimeType}) $value');
      }
    }
  }

  void _writeNode(String name, AstNode? node) {
    if (node != null) {
      _sink.writeWithIndent('$name: ');
      node.accept(this);
    }
  }

  void _writeNodeList(AstNode parent, String name, List<AstNode> nodeList) {
    if (nodeList.isNotEmpty) {
      _sink.writelnWithIndent(name);
      _sink.withIndent(() {
        for (var node in nodeList) {
          _checkParentOfChild(parent, node);
          _sink.writeIndent();
          node.accept(this);
        }
      });
    }
  }

  void _writeOffset(String name, int offset) {
    _sink.writelnWithIndent('$name: $offset');
  }

  /// If [node] is at a position where it is an argument for an invocation,
  /// writes the corresponding parameter element.
  void _writeParameterElement(Expression node) {
    if (configuration.withParameterElements) {
      var parent = node.parent;
      if (parent is ArgumentList ||
          parent is AssignmentExpression && parent.rightHandSide == node ||
          parent is BinaryExpression && parent.rightOperand == node ||
          parent is IndexExpression && parent.index == node) {
        _writeElement2('correspondingParameter', node.correspondingParameter);
      }
    }
  }

  void _writeParameterElements(List<ParameterElementImpl> parameters) {
    _sink.writelnWithIndent('parameters');
    _sink.withIndent(() {
      for (var parameter in parameters) {
        var name = parameter.name;
        _sink.writelnWithIndent(name.isNotEmpty ? name : '<empty>');
        _sink.withIndent(() {
          _writeParameterKind(parameter);
          _writeType('type', parameter.type);
        });
      }
    });
  }

  void _writeParameterKind(ParameterElementImpl parameter) {
    if (parameter.isOptionalNamed) {
      _sink.writelnWithIndent('kind: optional named');
    } else if (parameter.isOptionalPositional) {
      _sink.writelnWithIndent('kind: optional positional');
    } else if (parameter.isRequiredNamed) {
      _sink.writelnWithIndent('kind: required named');
    } else if (parameter.isRequiredPositional) {
      _sink.writelnWithIndent('kind: required positional');
    } else {
      throw StateError('Unknown kind: $parameter');
    }
  }

  void _writePatternMatchedValueType(DartPattern node) {
    if (_withResolution) {
      var matchedValueType = node.matchedValueType;
      if (matchedValueType != null) {
        _writeType('matchedValueType', matchedValueType);
      } else {
        fail('No matchedValueType: $node');
      }
    }
  }

  void _writeRaw(String name, Object? value) {
    _sink.writelnWithIndent('$name: $value');
  }

  void _writeToken(String name, Token? token) {
    if (token == null) {
      return;
    }

    _sink.writeIndentedLine(() {
      _sink.write('$name: ');
      if (configuration.withTokenPreviousNext) {
        _sink.write(_getTokenId(token));
        _sink.write(' ');
      }
      _sink.write(token.lexeme.ifNotEmptyOrElse('<empty>'));
      if (_withOffsets) {
        _sink.write(' @${token.offset}');
      }
      if (token.isSynthetic) {
        _sink.write(' <synthetic>');
      }
    });

    if (configuration.withTokenPreviousNext) {
      _sink.withIndent(() {
        if (token.previous case var previous?) {
          if (!previous.isEof) {
            if (_tokenIdMap[previous] == null) {
              _writeToken('previousX', previous);
            } else {
              _sink.writelnWithIndent(
                'previous: ${_getTokenId(previous)} |${previous.lexeme}|',
              );
            }
          }
        } else {
          _sink.writelnWithIndent('previous: <null>');
        }

        if (token.next case var next?) {
          _sink.writelnWithIndent(
            'next: ${_getTokenId(next)} |${next.lexeme}|',
          );
        } else {
          _sink.writelnWithIndent('next: <null>');
        }
      });
    }
  }

  void _writeTokenList(String name, List<Token> tokens) {
    if (tokens.isNotEmpty) {
      _sink.writelnWithIndent(name);
      _sink.withIndent(() {
        for (var token in tokens) {
          _sink.writelnWithIndent(token.lexeme);
          if (_withOffsets) {
            _sink.withIndent(() {
              _writeOffset('offset', token.offset);
            });
          }
        }
      });
    }
  }

  void _writeType(String name, DartType? type) {
    if (_withResolution) {
      _elementPrinter.writeNamedType(name, type);
    }
  }

  void _writeTypeList(String name, List<DartType>? types) {
    if (_withResolution) {
      _elementPrinter.writeTypeList(name, types);
    }
  }

  static void _assertHasIdenticalElement<T>(List<T> elements, T expected) {
    for (var element in elements) {
      if (identical(element, expected)) {
        return;
      }
    }
    fail('No $expected in $elements');
  }

  static Token _entityBeginToken(SyntacticEntity entity) {
    if (entity is Token) {
      return entity;
    } else if (entity is AstNode) {
      return entity.beginToken;
    } else {
      throw UnimplementedError('(${entity.runtimeType}) $entity');
    }
  }

  static Token _entityEndToken(SyntacticEntity entity) {
    if (entity is Token) {
      return entity;
    } else if (entity is AstNode) {
      return entity.endToken;
    } else {
      throw UnimplementedError('(${entity.runtimeType}) $entity');
    }
  }

  /// Every [FormalParameter] declares an element, and this element must be
  /// in the list of formal parameter elements of some declaration, e.g. of
  /// [ConstructorDeclaration], [MethodDeclaration], or a local
  /// [FunctionDeclaration].
  static List<FormalParameterFragment> _expectedFormalParameterElements(
    FormalParameter node,
  ) {
    var parametersParent = node.parentFormalParameterList.parent;
    if (parametersParent is ConstructorDeclaration) {
      var declaredFragment = parametersParent.declaredFragment!;
      return declaredFragment.formalParameters;
    } else if (parametersParent is FormalParameter) {
      var declaredFragment = parametersParent.declaredFragment!;
      declaredFragment as ParameterElementImpl;
      return declaredFragment.parameters;
    } else if (parametersParent is FunctionExpression) {
      var declaredFragment = parametersParent.declaredFragment!;
      return declaredFragment.formalParameters;
    } else if (parametersParent is GenericFunctionTypeImpl) {
      var declaredFragment = parametersParent.declaredFragment!;
      return declaredFragment.parameters;
    } else if (parametersParent is MethodDeclaration) {
      var declaredFragment = parametersParent.declaredFragment!;
      return declaredFragment.formalParameters;
    }
    throw UnimplementedError(
      '(${parametersParent.runtimeType}) $parametersParent',
    );
  }
}

class ResolvedNodeTextConfiguration {
  bool skipArgumentList = false;

  /// If `true`, elements of [InterfaceType] should be printed.
  bool withInterfaceTypeElements = false;

  /// If `true`, [Expression.correspondingParameter] should be printed.
  bool withParameterElements = true;

  /// If `true`, `redirectedConstructor` properties of [ConstructorElement2]s
  /// should be printer.
  bool withRedirectedConstructors = false;

  /// If `true`, `superConstructor` properties of [ConstructorElement2]s
  /// should be printer.
  bool withSuperConstructors = false;

  /// If `true`, print IDs of each token, `previous` and `next` tokens.
  bool withTokenPreviousNext = false;
}
