// Copyright (c) 2023, 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_testing/package_config_file_builder.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../../../client/completion_driver_test.dart';

void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(PropertyAccessTest);
  });
}

@reflectiveTest
class PropertyAccessTest extends AbstractCompletionDriverTest
    with PropertyAccessTestCases {
  @failingTest
  Future<void> test_afterIdentifier_partial_if() async {
    allowedIdentifiers = {'always', 'ifPresent'};
    await computeSuggestions('''
enum E {
  always, ifPresent
}
void f() {
  E.if^;
}
''');
    assertResponse(r'''
replacement
  left: 2
suggestions
  ifPresent
    kind: enumConstant
''');
  }
}

mixin PropertyAccessTestCases on AbstractCompletionDriverTest {
  Future<void> test_afterGetter() async {
    await computeSuggestions('''
class A { int x; foo() {x.^}}
''');
    assertResponse(r'''
suggestions
''');
  }

  Future<void> test_afterIdentifier() async {
    await computeSuggestions('''
class A { foo() {bar.^}}
''');
    assertResponse(r'''
suggestions
''');
  }

  Future<void> test_afterIdentifier_beforeAwait() async {
    await computeSuggestions('''
void f(A a) async {
  a.^
  await a.foo();
}

class A {
  void m01() {}
}
''');
    assertResponse(r'''
suggestions
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_afterIdentifier_beforeAwait_partial() async {
    await computeSuggestions('''
void f(A a) async {
  a.m0^
  await 0;
}

class A {
  void m01() {}
}
''');
    assertResponse(r'''
replacement
  left: 2
suggestions
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_afterIdentifier_beforeIdentifier_partial() async {
    allowedIdentifiers = {'length'};
    await computeSuggestions('''
void f(String x) {
  x.len^
  foo();
}
''');
    assertResponse(r'''
replacement
  left: 3
suggestions
  length
    kind: getter
''');
  }

  Future<void>
  test_afterIdentifier_beforeIdentifier_partial_importPrefix() async {
    newFile('$testPackageLibPath/a.dart', r'''
void v01() {}
void g01() {}
''');

    // There should be no `void`, we use `v` to verify this.
    await computeSuggestions('''
import 'a.dart' as prefix;

void f() {
  prefix.v^
  print(0);
}
''');

    assertResponse(r'''
replacement
  left: 1
suggestions
  v01
    kind: functionInvocation
''');
  }

  Future<void> test_afterIdentifier_partial() async {
    await computeSuggestions('''
class A { foo() {bar.as^}}
''');
    assertResponse(r'''
replacement
  left: 2
suggestions
''');
  }

  Future<void> test_afterInstanceCreation() async {
    await computeSuggestions('''
class A { get x => 7; foo() {new A().^}}
''');
    assertResponse(r'''
suggestions
''');
  }

  Future<void> test_afterLibraryPrefix() async {
    await computeSuggestions('''
import "b" as b; class A { foo() {b.^}}
''');
    assertResponse(r'''
suggestions
''');
  }

  Future<void> test_afterLocalVariable() async {
    await computeSuggestions('''
class A { foo() {int x; x.^}}
''');
    assertResponse(r'''
suggestions
''');
  }

  Future<void> test_betweenCascadeDots() async {
    await computeSuggestions('''
class A {
  A m01() => this;
  A get f01 => this;
}

void foo(A a) {
  a.f01.^.f01;
}
''');
    assertResponse(r'''
suggestions
  f01
    kind: getter
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_betweenCascadeDots_static() async {
    await computeSuggestions('''
class A {
  static A m01() => A();
  static A get f01 => A();
}

void foo() {
  A.^.f01;
}
''');
    assertResponse(r'''
suggestions
  f01
    kind: getter
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_betweenNullableCascadeDots() async {
    await computeSuggestions('''
class A {
  A m01() => this;
  A? get f01 => this;
}

void foo(A a) {
  a.f01?.^.f01;
}
''');
    assertResponse(r'''
suggestions
  f01
    kind: getter
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_isInternal_method_otherPackage() async {
    var otherRoot = getFolder('$packagesRootPath/other');
    newFile('${otherRoot.path}/lib/src/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @internal
  void f02() {}
}
''');

    writeTestPackageConfig(
      config: PackageConfigFileBuilder()
        ..add(name: 'other', rootFolder: otherRoot),
      meta: true,
    );

    await computeSuggestions('''
import 'package:other/src/a.dart''

void f() {
  A().^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
''');
  }

  Future<void> test_isInternal_method_samePackage() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/src/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @internal
  void f02() {}
}
''');

    await computeSuggestions('''
import 'src/a.dart''

void f() {
  A().^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
  f02
    kind: methodInvocation
''');
  }

  Future<void> test_isProtected_field_otherLibrary_function() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  int f01 = 0;

  @protected
  int f02 = 0;
}
''');

    await computeSuggestions('''
import 'a.dart';

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: field
''');
  }

  Future<void> test_isProtected_field_sameLibrary_function() async {
    writeTestPackageConfig(meta: true);

    await computeSuggestions('''
import 'package:meta/meta.dart';

class A {
  int f01 = 0;

  @protected
  int f02 = 0;
}

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: field
  f02
    kind: field
''');
  }

  Future<void> test_isProtected_getter_otherLibrary_function() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  int get f01 => 0;

  @protected
  int get f02 => 0;
}
''');

    await computeSuggestions('''
import 'a.dart';

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: getter
''');
  }

  Future<void> test_isProtected_getter_sameLibrary_function() async {
    writeTestPackageConfig(meta: true);

    await computeSuggestions('''
import 'package:meta/meta.dart';

class A {
  int get f01 => 0;

  @protected
  int get f02 => 0;
}

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: getter
  f02
    kind: getter
''');
  }

  Future<void> test_isProtected_method_otherLibrary_class_notSubtype() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @protected
  void f02() {}
}
''');

    await computeSuggestions('''
import 'a.dart';

class B {
  void foo(A a) {
    a.^
  }
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
''');
  }

  Future<void> test_isProtected_method_otherLibrary_class_subtype() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @protected
  void f02() {}
}
''');

    await computeSuggestions('''
import 'a.dart';

class B extends A {
  void foo() {
    this.^
  }
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
  f02
    kind: methodInvocation
''');
  }

  Future<void> test_isProtected_method_otherLibrary_function() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @protected
  void f02() {}
}
''');

    await computeSuggestions('''
import 'a.dart';

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
''');
  }

  Future<void> test_isProtected_method_sameLibrary_function() async {
    writeTestPackageConfig(meta: true);

    await computeSuggestions('''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @protected
  void f02() {}
}

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
  f02
    kind: methodInvocation
''');
  }

  Future<void> test_isProtected_setter_otherLibrary_function() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  set f01(int _) {}

  @protected
  set f02(int _) {}
}
''');

    await computeSuggestions('''
import 'a.dart';

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: setter
''');
  }

  Future<void> test_isProtected_setter_sameLibrary_function() async {
    writeTestPackageConfig(meta: true);

    await computeSuggestions('''
import 'package:meta/meta.dart';

class A {
  set f01(int _) {}

  @protected
  set f02(int _) {}
}

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: setter
  f02
    kind: setter
''');
  }

  Future<void> test_isVisibleForTesting_method_otherPackage() async {
    var otherRoot = getFolder('$packagesRootPath/other');
    newFile('${otherRoot.path}/lib/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @visibleForTesting
  void f02() {}
}
''');

    writeTestPackageConfig(
      config: PackageConfigFileBuilder()
        ..add(name: 'other', rootFolder: otherRoot),
      meta: true,
    );

    await computeSuggestions('''
import 'package:other/a.dart''

void f() {
  A().^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
''');
  }

  Future<void> test_isVisibleForTesting_method_otherPackage_test() async {
    var otherRoot = getFolder('$packagesRootPath/other');
    newFile('${otherRoot.path}/lib/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @visibleForTesting
  void f02() {}
}
''');

    writeTestPackageConfig(
      config: PackageConfigFileBuilder()
        ..add(name: 'other', rootFolder: otherRoot),
      meta: true,
    );

    testFilePath = '$testPackageTestPath/test.dart';
    await computeSuggestions('''
import 'package:other/a.dart''

void f() {
  A().^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
''');
  }

  Future<void> test_isVisibleForTesting_method_sameLibrary() async {
    writeTestPackageConfig(meta: true);

    await computeSuggestions('''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @visibleForTesting
  void f02() {}
}

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
  f02
    kind: methodInvocation
''');
  }

  Future<void>
  test_isVisibleForTesting_method_samePackage_otherLibrary() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @visibleForTesting
  void f02() {}
}
''');

    await computeSuggestions('''
import 'a.dart';

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
''');
  }

  Future<void>
  test_isVisibleForTesting_method_samePackage_otherLibrary_test() async {
    writeTestPackageConfig(meta: true);

    newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';

class A {
  void f01() {}

  @visibleForTesting
  void f02() {}
}
''');

    testFilePath = '$testPackageTestPath/test.dart';
    await computeSuggestions('''
import 'package:test/a.dart';

void f(A a) {
  a.^
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: methodInvocation
  f02
    kind: methodInvocation
''');
  }

  Future<void> test_property_assignmentLeft_newLine() async {
    await computeSuggestions('''
class A {
  int? f01;
  void m01() {}
}

void f(A? a, Object? v01) {
  a.^
  v01 = null;
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: field
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_property_assignmentLeft_newLine2() async {
    await computeSuggestions('''
class A {
  int? f01;
  void m01() {}
}

void f(A? a, Object? v01) {
  (a).^
  v01 = null;
}
''');

    assertResponse(r'''
suggestions
  f01
    kind: field
  m01
    kind: methodInvocation
''');
  }

  Future<void> test_target_assignmentLeft() async {
    await computeSuggestions('''
void f(Object v01) {
  v0^.foo = 0;
}
''');
    assertResponse(r'''
replacement
  left: 2
suggestions
  v01
    kind: parameter
''');
  }
}
