// Copyright (c) 2022, 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 'dart:io';

import 'package:analysis_server_plugin/src/utilities/selection.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../abstract_single_unit.dart';
import '../../services/completion/dart/text_expectations.dart';

// TODO(srawlins): This needs to move to the analysis_server_plugin package, or
// more likely, an analysis_server "testing" package, as it requires a lot of
// test-related baggage.
void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(SelectionTest);
  });
}

@reflectiveTest
class SelectionTest extends AbstractSingleUnitTest {
  Future<void> assertMetadata({
    String prefix = '',
    required String postfix,
  }) async {
    var selection = await _computeSelection('''
$prefix
@a
[!@b
@c!]
@d
$postfix
const a = 1;
const b = 2;
const c = 3;
const d = 4;
''');
    _assertSelection(selection, r'''
nodesInRange
  AnnotationImpl: @b
  AnnotationImpl: @c
''');
  }

  Future<void> test_adjacentStrings_strings() async {
    var selection = await _computeSelection('''
var s = 'a' [!'b' 'c'!] 'd';
''');
    _assertSelection(selection, r'''
nodesInRange
  SimpleStringLiteralImpl: 'b'
  SimpleStringLiteralImpl: 'c'
''');
  }

  Future<void> test_argumentList_arguments() async {
    var selection = await _computeSelection('''
var v = f('0', [!1, 2.0!], '3');
int f(a, b, c, d) => 0;
''');
    _assertSelection(selection, r'''
nodesInRange
  IntegerLiteralImpl: 1
  DoubleLiteralImpl: 2.0
''');
  }

  /// B01: between 0 and 1, no touch
  Future<void> test_block_statements_B01_B01() async {
    var selection = await _computeSelection('''
void f() {
  000;
 [! !] 111;
  222;
}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  /// B01: between 0 and 1, no touch
  /// TE2: touch end of 2
  Future<void> test_block_statements_B01_E2() async {
    var selection = await _computeSelection('''
void f() {
  000;
 [! 111;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// B01: between 0 and 1, no touch
  /// TB1: touch begin of 1
  Future<void> test_block_statements_B01_TB1() async {
    var selection = await _computeSelection('''
void f() {
  000;
 [! !]111;
  222;
}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  /// B0: before 0
  Future<void> test_block_statements_B0_B0() async {
    var selection = await _computeSelection('''
void f() {
 [! !] 000;
  111;
}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  /// B12: between 1 and 2
  /// TE2: touch end of 2
  Future<void> test_block_statements_B12_TE2() async {
    var selection = await _computeSelection('''
void f() {
  000;
  111;
  [! 222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 222;
''');
  }

  /// BB0: before begin of 0
  /// TE2: touch end of 2
  Future<void> test_block_statements_BB0_TE2() async {
    var selection = await _computeSelection('''
void f() {
[!  000;
  111;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 000;
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// I0: inside 0
  /// TE2: touch end of 2
  Future<void> test_block_statements_I0_TE2() async {
    var selection = await _computeSelection('''
void f() {
  0[!00;
  111;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 000;
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// I1: inside 1
  /// TE2: touch end of 2
  Future<void> test_block_statements_I1_TE2() async {
    var selection = await _computeSelection('''
void f() {
  000;
  1[!11;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// I2: inside 2
  /// TE2: touch end of 2
  Future<void> test_block_statements_I2_TE2() async {
    var selection = await _computeSelection('''
void f() {
  000;
  111;
  2[!22;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  /// TB0: touch begin of 0
  /// TE2: touch end of 2
  Future<void> test_block_statements_TB0_TE2() async {
    var selection = await _computeSelection('''
void f() {
  [!000;
  111;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 000;
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// TB1: touch begin of 1
  /// A3: after 3
  Future<void> test_block_statements_TB1_A3() async {
    var selection = await _computeSelection('''
void f() {
  000;
  [!111;
  222;
  333; !]
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
  ExpressionStatementImpl: 333;
''');
  }

  /// TB1: touch begin of 1
  /// B23: before 2 and 3
  Future<void> test_block_statements_TB1_B23() async {
    var selection = await _computeSelection('''
void f() {
  000;
  [!111;
  222;
 !] 333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// TB1: touch begin of 1
  /// TE2: touch end of 2
  Future<void> test_block_statements_TB1_TE2() async {
    var selection = await _computeSelection('''
void f() {
  000;
  [!111;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// TB1: touch begin of 1
  /// TE2TB3: touch end of 2, touch begin of 3
  Future<void> test_block_statements_TB1_TE2TB3() async {
    var selection = await _computeSelection('''
void f() {
  000;
  [!111;
  222;!]333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// TB1: touch begin of 1
  /// TE2: touch end 3
  Future<void> test_block_statements_TB1_TE3() async {
    var selection = await _computeSelection('''
void f() {
  000;
  [!111;
  222;
  333;!]
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
  ExpressionStatementImpl: 333;
''');
  }

  /// TB2: touch begin of 2
  /// B23: between 2 and 3
  Future<void> test_block_statements_TB2_B23() async {
    var selection = await _computeSelection('''
void f() {
  000;
  111;
  [!222; !]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 222;
''');
  }

  /// TB2: touch begin of 2
  /// TE2: touch end of 2
  Future<void> test_block_statements_TB2_TE2() async {
    var selection = await _computeSelection('''
void f() {
  000;
  111;
  [!222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  /// TE0TB1: touch end of 0, touch begin of 1
  /// TE2: touch end of 2
  Future<void> test_block_statements_TE0TB1_TE2() async {
    var selection = await _computeSelection('''
void f() {
  000;[!111;
  222;!]
  333;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ExpressionStatementImpl: 111;
  ExpressionStatementImpl: 222;
''');
  }

  /// TE2: touch end of 2
  /// B12: between 1 and 2, no touch
  Future<void> test_block_statements_TE1_B12() async {
    var selection = await _computeSelection('''
void f() {
  000;
  111;[! !]
  222;
}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  Future<void> test_cascadeExpression_cascadeSections() async {
    var selection = await _computeSelection('''
void f(y) {
  var x = y..a()[!..b..c()!]..d;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  PropertyAccessImpl: ..b
  MethodInvocationImpl: ..c()
''');
  }

  Future<void> test_classTypeAlias_metadata() async {
    await assertMetadata(
      postfix: '''
class A = C with M;
class C {}
mixin M {}
''',
    );
  }

  Future<void> test_compilationUnit_declarations() async {
    var selection = await _computeSelection('''
typedef F = void Function();
[!var x = 0;
void f() {}!]
class C {}
''');
    _assertSelection(selection, r'''
nodesInRange
  TopLevelVariableDeclarationImpl: var x = 0;
  FunctionDeclarationImpl: void f() {}
''');
  }

  Future<void> test_compilationUnit_directives() async {
    newFile('$testPackageLibPath/a.dart', "part of 'test.dart';");
    var selection = await _computeSelection('''
library l;
[!import '';
export '';!]
part 'a.dart';
''');
    _assertSelection(selection, r'''
nodesInRange
  ImportDirectiveImpl: import '';
  ExportDirectiveImpl: export '';
''');
  }

  Future<void> test_constructorDeclaration_initializers() async {
    var selection = await _computeSelection('''
class C {
  int a, b, c, d;
  C() : a = 0, [!b = 1, c = 2,!] d = 4;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ConstructorFieldInitializerImpl: b = 1
  ConstructorFieldInitializerImpl: c = 2
''');
  }

  Future<void> test_constructorDeclaration_metadata() async {
    await assertMetadata(
      prefix: '''
class C {
''',
      postfix: '''
  C();
}
''',
    );
  }

  Future<void> test_declaredIdentifier_metadata() async {
    await assertMetadata(
      prefix: '''
void f(List l) {
  for (
''',
      postfix: '''
var e in l) {}
}
''',
    );
  }

  Future<void> test_defaultFormalParameter_metadata() async {
    await assertMetadata(
      prefix: '''
void f([
''',
      postfix: '''
int x = 0]) {}
''',
    );
  }

  Future<void> test_dottedName_tokens() async {
    var selection = await _computeSelection('''
library a.[!b.c!].d;
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  Future<void> test_enumConstantDeclaration_metadata() async {
    await assertMetadata(
      prefix: '''
enum E {
''',
      postfix: '''
a }
''',
    );
  }

  Future<void> test_enumDeclaration_constants() async {
    var selection = await _computeSelection('''
enum E { a, [!b, c!], d }
''');
    _assertSelection(selection, r'''
nodesInRange
  EnumConstantDeclarationImpl: b
  EnumConstantDeclarationImpl: c
''');
  }

  Future<void> test_enumDeclaration_members() async {
    var selection = await _computeSelection('''
enum E {
  a;
  final int x = 0;
  [!void m1() {}
  final int y = 1;!]
  void m2() {}
}
''');
    _assertSelection(selection, r'''
nodesInRange
  MethodDeclarationImpl: void m1() {}
  FieldDeclarationImpl: final int y = 1;
''');
  }

  Future<void> test_enumDeclaration_metadata() async {
    await assertMetadata(
      postfix: '''
enum E { a }
''',
    );
  }

  Future<void> test_exportDirective_combinators() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    var selection = await _computeSelection('''
// ignore: multiple_combinators
export 'a.dart' show a [!hide b show c!] hide d;
''');
    _assertSelection(selection, r'''
nodesInRange
  HideCombinatorImpl: hide b
  ShowCombinatorImpl: show c
''');
  }

  Future<void> test_exportDirective_configurations() async {
    var selection = await _computeSelection('''
export '' if (a) '' [!if (b) '' if (c) ''!] if (d) '';
''');
    _assertSelection(selection, r'''
nodesInRange
  ConfigurationImpl: if (b) ''
  ConfigurationImpl: if (c) ''
''');
  }

  Future<void> test_exportDirective_metadata() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    await assertMetadata(
      postfix: '''
export 'a.dart';
''',
    );
  }

  Future<void> test_extensionDeclaration_members() async {
    var selection = await _computeSelection('''
extension on int {
  static int x = 0;
  [!void m1() {}
  static int y = 1;!]
  void m2() {}
}
''');
    _assertSelection(selection, r'''
nodesInRange
  MethodDeclarationImpl: void m1() {}
  FieldDeclarationImpl: static int y = 1;
''');
  }

  Future<void> test_extensionDeclaration_metadata() async {
    await assertMetadata(
      postfix: '''
extension on int {}
''',
    );
  }

  Future<void> test_fieldDeclaration_metadata() async {
    await assertMetadata(
      prefix: '''
class C {
''',
      postfix: '''
  int? f;
}
''',
    );
  }

  Future<void> test_fieldFormalParameter_metadata() async {
    await assertMetadata(
      prefix: '''
class C {
  int x = 0;
  C(
''',
      postfix: '''
this.x);
}
''',
    );
  }

  Future<void> test_forEachPartsWithPattern_metadata() async {
    await assertMetadata(
      prefix: '''
void f(List<(int, int)> r) {
  for (
''',
      postfix: '''
  var (x, y) in r) {}
}
''',
    );
  }

  Future<void> test_formalParameterList_parameters_mixed() async {
    var selection = await _computeSelection('''
void f(a, [!b, {c!], d}) {}
''');
    _assertSelection(selection, r'''
nodesInRange
''');
  }

  Future<void> test_formalParameterList_parameters_named() async {
    var selection = await _computeSelection('''
void f({a, [!b, c!], d}) {}
''');
    _assertSelection(selection, r'''
nodesInRange
  RegularFormalParameterImpl: b
  RegularFormalParameterImpl: c
''');
  }

  Future<void> test_formalParameterList_parameters_positional() async {
    var selection = await _computeSelection('''
void f(a, [!b, c!], d) {}
''');
    _assertSelection(selection, r'''
nodesInRange
  RegularFormalParameterImpl: b
  RegularFormalParameterImpl: c
''');
  }

  Future<void> test_forPartsWithDeclarations_updaters() async {
    var selection = await _computeSelection('''
void f() {
  for (var x = 0; x < 0; x++, [!x--, ++x!], --x) {}
}
''');
    _assertSelection(selection, r'''
nodesInRange
  PostfixExpressionImpl: x--
  PrefixExpressionImpl: ++x
''');
  }

  Future<void> test_forPartsWithExpression_updaters() async {
    var selection = await _computeSelection('''
void f() {
  var x;
  for (x = 0; x < 0; x++, [!x--, ++x!], --x) {}
}
''');
    _assertSelection(selection, r'''
nodesInRange
  PostfixExpressionImpl: x--
  PrefixExpressionImpl: ++x
''');
  }

  Future<void> test_forPartsWithPattern_updaters() async {
    var selection = await _computeSelection('''
void f() {
  for (var (x, y) = (0, 0); x < 0; x++, [!x--, ++x!], --x) {}
}
''');
    _assertSelection(selection, r'''
nodesInRange
  PostfixExpressionImpl: x--
  PrefixExpressionImpl: ++x
''');
  }

  Future<void> test_functionDeclaration_metadata() async {
    await assertMetadata(
      postfix: '''
void f() {}
''',
    );
  }

  Future<void> test_functionTypeAlias_metadata() async {
    await assertMetadata(
      postfix: '''
typedef void F();
''',
    );
  }

  Future<void> test_functionTypedFormalParameter_metadata() async {
    await assertMetadata(
      prefix: '''
void f(
''',
      postfix: '''
int g(int)) {}
''',
    );
  }

  Future<void> test_genericTypeAlias_metadata() async {
    await assertMetadata(
      postfix: '''
typedef F = void Function();
''',
    );
  }

  Future<void> test_hideCombinator_hiddenNames() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    var selection = await _computeSelection('''
import 'a.dart' hide a, [!b, c!], d;
''');
    _assertSelection(selection, r'''
nodesInRange
  SimpleIdentifierImpl: b
  SimpleIdentifierImpl: c
''');
  }

  Future<void> test_implementsClause_interfaces() async {
    var selection = await _computeSelection('''
class A implements B, [!C, D!], E {}
class B {}
class C {}
class D {}
class E {}
''');
    _assertSelection(selection, r'''
nodesInRange
  NamedTypeImpl: C
  NamedTypeImpl: D
''');
  }

  Future<void> test_importDirective_combinators() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    var selection = await _computeSelection('''
// ignore: multiple_combinators
import 'a.dart' show a [!hide b show c!] hide d;
''');
    _assertSelection(selection, r'''
nodesInRange
  HideCombinatorImpl: hide b
  ShowCombinatorImpl: show c
''');
  }

  Future<void> test_importDirective_configurations() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    var selection = await _computeSelection('''
import 'a.dart' if (a) '' [!if (b) '' if (c) ''!] if (d) '';
''');
    _assertSelection(selection, r'''
nodesInRange
  ConfigurationImpl: if (b) ''
  ConfigurationImpl: if (c) ''
''');
  }

  Future<void> test_importDirective_metadata() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    await assertMetadata(
      postfix: '''
import 'a.dart';
''',
    );
  }

  Future<void> test_labeledStatement_labels() async {
    var selection = await _computeSelection('''
void f() {
  a: [!b: c:!] d: while (true) {
    if (1 < 2) {
      break a;
    } else if (2 < 3) {
      break b;
    } else if (3 < 4) {
      break c;
    } else if (4 < 5) {
      break d;
    }
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  LabelImpl: b:
  LabelImpl: c:
''');
  }

  Future<void> test_libraryDirective_metadata() async {
    await assertMetadata(
      postfix: '''
library l;
''',
    );
  }

  Future<void> test_listLiteral_elements() async {
    var selection = await _computeSelection('''
var l = ['0', [!1, 2.0!], '3'];
''');
    _assertSelection(selection, r'''
nodesInRange
  IntegerLiteralImpl: 1
  DoubleLiteralImpl: 2.0
''');
  }

  Future<void> test_listPattern_elements() async {
    var selection = await _computeSelection('''
void f(x) {
  switch (x) {
    case [1, [!2, 3!], 4]:
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  ConstantPatternImpl: 2
  ConstantPatternImpl: 3
''');
  }

  Future<void> test_mapPattern_entries() async {
    var selection = await _computeSelection('''
void f(x) {
  switch (x) {
    case {'a': 1, [!'b': 2, 'c': 3!], 'd': 4}:
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  MapPatternEntryImpl: 'b': 2
  MapPatternEntryImpl: 'c': 3
''');
  }

  Future<void> test_methodDeclaration_metadata() async {
    await assertMetadata(
      prefix: '''
class C {
''',
      postfix: '''
  void m() {}
}
''',
    );
  }

  Future<void> test_mixinDeclaration_members() async {
    var selection = await _computeSelection('''
mixin M {
  int x = 0;
  [!void m1() {}
  int y = 1;!]
  void m2() {}
}
''');
    _assertSelection(selection, r'''
nodesInRange
  MethodDeclarationImpl: void m1() {}
  FieldDeclarationImpl: int y = 1;
''');
  }

  Future<void> test_mixinDeclaration_metadata() async {
    await assertMetadata(
      postfix: '''
mixin M {}
''',
    );
  }

  Future<void> test_objectPattern_fields() async {
    var selection = await _computeSelection('''
void f(C x) {
  switch (x) {
    case C(a: 1, [!b: 2, c: 3!], d: 4):
      break;
  }
}
class C {
  int a = 1, b = 2, c = 3, d = 4;
}
''');
    _assertSelection(selection, r'''
nodesInRange
  PatternFieldImpl: b: 2
  PatternFieldImpl: c: 3
''');
  }

  Future<void> test_onClause_superclassConstraints() async {
    var selection = await _computeSelection('''
mixin M on A, [!B, C!], D {}
class A {}
class B {}
class C {}
class D {}
''');
    _assertSelection(selection, r'''
nodesInRange
  NamedTypeImpl: B
  NamedTypeImpl: C
''');
  }

  Future<void> test_partDirective_metadata() async {
    newFile('$testPackageLibPath/a.dart', "part of 'test.dart';");
    await assertMetadata(
      postfix: '''
part 'a.dart';
''',
    );
  }

  Future<void> test_partOfDirective_metadata() async {
    await assertMetadata(
      postfix: '''
part of '';
''',
    );
  }

  Future<void> test_patternVariableDeclaration_metadata() async {
    await assertMetadata(
      prefix: '''
void f((int, int) r) {
''',
      postfix: '''
  var (x, y) = r;
}
''',
    );
  }

  Future<void> test_recordLiteral_fields() async {
    var selection = await _computeSelection('''
var r = ('0', [!1, 2.0!], '3');
''');
    _assertSelection(selection, r'''
nodesInRange
  IntegerLiteralImpl: 1
  DoubleLiteralImpl: 2.0
''');
  }

  Future<void> test_recordPattern_fields() async {
    var selection = await _computeSelection('''
void f((String, int, double, String) x) {
  switch (x) {
    case ('0', [!1, 2.0!], '3'):
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  PatternFieldImpl: 1
  PatternFieldImpl: 2.0
''');
  }

  Future<void> test_recordTypeAnnotation_positionalFields() async {
    var selection = await _computeSelection('''
(int, [!String, int!], String) r = (0, '1', 2, '3');
''');
    _assertSelection(selection, r'''
nodesInRange
  RecordTypeAnnotationPositionalFieldImpl: String
  RecordTypeAnnotationPositionalFieldImpl: int
''');
  }

  Future<void> test_recordTypeAnnotationNamedField_metadata() async {
    await assertMetadata(
      prefix: '''
void f(({
''',
      postfix: '''
int x}) r) {}
''',
    );
  }

  Future<void> test_recordTypeAnnotationNamedFields_fields() async {
    var selection = await _computeSelection('''
({int a, [!String b, int c!], String d}) r = (a: 0, b: '1', c: 2, d:'3');
''');
    _assertSelection(selection, r'''
nodesInRange
  RecordTypeAnnotationNamedFieldImpl: String b
  RecordTypeAnnotationNamedFieldImpl: int c
''');
  }

  Future<void> test_recordTypeAnnotationPositionalField_metadata() async {
    await assertMetadata(
      prefix: '''
void f((int,
''',
      postfix: '''
int) r) {}
''',
    );
  }

  Future<void> test_setOrMapLiteral_elements() async {
    var selection = await _computeSelection('''
var s = {'0', [!1, 2.0!], '3'};
''');
    _assertSelection(selection, r'''
nodesInRange
  IntegerLiteralImpl: 1
  DoubleLiteralImpl: 2.0
''');
  }

  Future<void> test_showCombinator_shownNames() async {
    newFile('$testPackageLibPath/a.dart', '''
int a, b, c, d;
''');
    var selection = await _computeSelection('''
import 'a.dart' show a, [!b, c!], d;
''');
    _assertSelection(selection, r'''
nodesInRange
  SimpleIdentifierImpl: b
  SimpleIdentifierImpl: c
''');
  }

  Future<void> test_simpleFormalParameter_metadata() async {
    await assertMetadata(
      prefix: '''
void f(
''',
      postfix: '''
int x) {}
''',
    );
  }

  Future<void> test_stringInterpolation_elements() async {
    var selection = await _computeSelection(r'''
void f(cd, gh) {
  var s = 'ab${c[!d}e!]f${gh}';
}
''');
    _assertSelection(selection, r'''
nodesInRange
  InterpolationExpressionImpl: ${cd}
  InterpolationStringImpl: ef
''');
  }

  Future<void> test_superFormalParameter_metadata() async {
    await assertMetadata(
      prefix: '''
class C extends B {
  C({
''',
      postfix: '''
super.x});
}
class B {
  B({int? x});
}
''',
    );
  }

  Future<void> test_switchCase_labels() async {
    var selection = await _computeSelection('''
void f(int x) {
  switch (x) {
    a: [!b: c!]: d: case 3: continue a;
    case 4: continue b;
    case 5: continue c;
    case 6: continue d;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  LabelImpl: b:
  LabelImpl: c:
''');
  }

  Future<void> test_switchCase_statements() async {
    var selection = await _computeSelection('''
void f(int x) {
  switch (x) {
    case 3:
      var v = 0;
      [!if (v < 0) v++;
      while (v > 0) v--;!]
      print(v);
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  IfStatementImpl: if (v < 0) v++;
  WhileStatementImpl: while (v > 0) v--;
''');
  }

  Future<void> test_switchDefault_labels() async {
    var selection = await _computeSelection('''
void f(int x) {
  switch (x) {
    case 4: continue b;
    case 5: continue c;
    case 6: continue d;
    a: [!b: c!]: d: default: continue a;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  LabelImpl: b:
  LabelImpl: c:
''');
  }

  Future<void> test_switchDefault_statements() async {
    var selection = await _computeSelection('''
void f(int x) {
  switch (x) {
    default:
      var v = 0;
      [!if (v < 0) v++;
      while (v > 0) v--;!]
      print(v);
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  IfStatementImpl: if (v < 0) v++;
  WhileStatementImpl: while (v > 0) v--;
''');
  }

  Future<void> test_switchExpression_members() async {
    var selection = await _computeSelection('''
String f(int x) {
  return switch (x) {
    1 => '1',
    [!2 => '2',
    3 => '3'!],
    4 => '4',
    _ => '5',
  };
}
''');
    _assertSelection(selection, r'''
nodesInRange
  SwitchExpressionCaseImpl: 2 => '2'
  SwitchExpressionCaseImpl: 3 => '3'
''');
  }

  Future<void> test_switchPatternCase_labels() async {
    var selection = await _computeSelection('''
void f((int, int) x) {
  switch (x) {
    a: [!b: c!]: d: case (1, 2): continue a;
    case (3, 4): continue b;
    case (5, 6): continue c;
    case (7, 8): continue d;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  LabelImpl: b:
  LabelImpl: c:
''');
  }

  Future<void> test_switchPatternCase_statements() async {
    var selection = await _computeSelection('''
void f((int, int) r) {
  switch (r) {
    case (1, 2):
      var v = 0;
      [!if (v < 0) v++;
      while (v > 0) v--;!]
      print(v);
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  IfStatementImpl: if (v < 0) v++;
  WhileStatementImpl: while (v > 0) v--;
''');
  }

  Future<void> test_switchStatement_members() async {
    var selection = await _computeSelection('''
void f(int x) {
  switch (x) {
    case 1:
      break;
    [!case 2:
      break;
    case 3:
      break;!]
    case 4:
      break;
  }
}
''');
    _assertSelection(selection, r'''
nodesInRange
  SwitchPatternCaseImpl: case 2:\n      break;
  SwitchPatternCaseImpl: case 3:\n      break;
''');
  }

  Future<void> test_topLevelVariableDeclaration_metadata() async {
    await assertMetadata(
      postfix: '''
int x = 0;
''',
    );
  }

  Future<void> test_tryStatement_catchClauses() async {
    var selection = await _computeSelection('''
void f() {
  try {
  } on A {
  } [!on B {
  } on C {!]
  } on D {
  }
}
class A {}
class B {}
class C {}
class D {}
''');
    _assertSelection(selection, r'''
nodesInRange
  CatchClauseImpl: on B {\n  }
  CatchClauseImpl: on C {\n  }
''');
  }

  Future<void> test_typeArgumentList_arguments() async {
    var selection = await _computeSelection('''
C<A, [!B, C!], D> c = C();
class A {}
class B {}
class C<Q, R, S, T> {}
class D {}
''');
    _assertSelection(selection, r'''
nodesInRange
  NamedTypeImpl: B
  NamedTypeImpl: C
''');
  }

  Future<void> test_typeParameter_metadata() async {
    await assertMetadata(
      prefix: '''
class C<
''',
      postfix: '''
T> {}
''',
    );
  }

  Future<void> test_typeParameterList_typeParameters() async {
    var selection = await _computeSelection('''
class C<A, [!B, D!], E> {}
''');
    _assertSelection(selection, r'''
nodesInRange
  TypeParameterImpl: B
  TypeParameterImpl: D
''');
  }

  Future<void> test_variableDeclarationList_metadata() async {
    await assertMetadata(
      prefix: '''
void f() {
''',
      postfix: '''
  int x = 0;
}
''',
    );
  }

  Future<void> test_variableDeclarationList_variables() async {
    var selection = await _computeSelection('''
var a = 1, [!b = 2, c = 3!], d = 4;
''');
    _assertSelection(selection, r'''
nodesInRange
  VariableDeclarationImpl: b = 2
  VariableDeclarationImpl: c = 3
''');
  }

  Future<void> test_withClause_mixinTypes() async {
    var selection = await _computeSelection('''
class C with A, [!B, D!], E {}
mixin A {}
mixin B {}
mixin D {}
mixin E {}
''');
    _assertSelection(selection, r'''
nodesInRange
  NamedTypeImpl: B
  NamedTypeImpl: D
''');
  }

  void _assertSelection(_CodeSelection selection, String expected) {
    var buffer = StringBuffer();
    _writeSelectionToBuffer(buffer, selection);
    // Expectation descriptions are always written with literal newlines as '\n'
    // so on Windows we will need to update the expectation to '\r\n' before
    // comparing.
    expect(
      expected,
      isNot(contains(r'\r')),
      reason:
          r'Expectations should always be written with literal '
          "'\n' to indicate newlines regardless of platform",
    );
    if (Platform.isWindows) {
      expected = expected.replaceAll(r'\n', r'\r\n');
    }

    _assertTextExpectation(buffer.toString(), expected);
  }

  void _assertTextExpectation(String actual, String expected) {
    if (actual != expected) {
      print('-' * 64);
      print(actual.trimRight());
      print('-' * 64);
      TextExpectationsCollector.add(actual);
    }
    expect(actual, expected);
  }

  Future<_CodeSelection> _computeSelection(String annotatedCode) async {
    var testCode = TestCode.parseNormalized(annotatedCode);
    expect(testCode.positions, isEmpty);
    var range = testCode.range.sourceRange;

    await resolveTestCode(testCode.code);
    var selection = testUnit.select(
      offset: range.offset,
      length: range.length,
    )!;

    return _CodeSelection(testCode: testCode, selection: selection);
  }

  void _writeSelectionToBuffer(StringBuffer buffer, _CodeSelection selection) {
    var rawCode = selection.testCode.code;

    buffer.writeln('nodesInRange');
    var nodes = selection.selection.nodesInRange();
    for (var node in nodes) {
      var nodeCode = rawCode.substring(node.offset, node.end);
      var nodeCodeEscaped = escape(nodeCode);
      buffer.writeln('  ${node.runtimeType}: $nodeCodeEscaped');
    }
  }
}

class _CodeSelection {
  final TestCode testCode;
  final Selection selection;

  new({required this.testCode, required this.selection});
}
