// 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:analysis_server/lsp_protocol/protocol.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 '../utils/test_code_extensions.dart';
import 'server_abstract.dart';

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

@reflectiveTest
class SuperTest extends AbstractLspAnalysisServerTest {
  /// Class augmentations do not affect super behaviour.
  Future<void> test_augmentation_class() async {
    await verifyGoToSuper(
      TestCode.parse('''
class [!A!] {}

augment class A {}

class B^ extends A {}
'''),
    );
  }

  Future<void> test_augmentation_constructor() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  A();
}

augment class A {
  augment A();
}

augment class A {
  augment [!A!]();
}

class B extends A {
  B^();
}
'''),
    );
  }

  /// "Go to Super" for method goes to the last item in the augmentation chain
  /// of the super method since that's what `super.x()` would invoke.
  Future<void> test_augmentation_method() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void foo() {}
}

augment class A {
  augment void foo();
}

augment class A {
  augment void [!foo!]();
}

class B extends A {}

class C extends B {
  @override
  void fo^o() {}
}
'''),
    );
  }

  Future<void> test_className() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {}

class [!B!] extends A {}

class C^ extends B {}
'''),
    );
  }

  Future<void> test_className_underscore() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {}

class [!_!] extends A {}

class C^ extends _ {}
'''),
    );
  }

  Future<void> test_constructor_named_callsSuperNamed() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  A();
  A.foo();
  A.[!bar!]();
}

class B extends A {
  B.foo(): super.bar() {
    ^
  }
}
'''),
    );
  }

  Future<void> test_constructor_named_callsSuperUnnamed() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
  A.foo();
}

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

  Future<void> test_constructor_unnamed_callsSuperNamed() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  A();
  A.[!foo!]();
}

class B extends A {
  B^() : super.foo();
}

'''),
    );
  }

  Future<void> test_constructor_unnamed_callsSuperUnnamed() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
}

class B extends A {
  B^();
}

'''),
    );
  }

  Future<void> test_field_field() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String? [!foo!];
}

class B extends A {}

class C extends B {
  @override
  String? fo^o;
}
'''),
    );
  }

  Future<void> test_field_getter() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String? get [!foo!] => null;
}

class B extends A {}

class C extends B {
  @override
  String? fo^o;
}
'''),
    );
  }

  @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/60469')
  Future<void> test_field_setter() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  set [!foo!](String? value) {}
}

class B extends A {}

class C extends B {
  @override
  String? fo^o;
}
'''),
    );
  }

  Future<void> test_getter_field() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String [!foo!] = '';
}

class B extends A {}

class C extends B {
  @override
  String get fo^o => '';
}
'''),
    );
  }

  Future<void> test_getter_getter() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String get [!foo!] => '';
}

class B extends A {}

class C extends B {
  @override
  String get fo^o => '';
}
'''),
    );
  }

  @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/60469')
  Future<void> test_getter_setter() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  set [!foo!](String value) {}
}

class B extends A {}

class C extends B {
  @override
  String get fo^o => '';
}
'''),
    );
  }

  Future<void> test_getter_underscore() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String get [!_!] => '';
}

class B extends A {}

class C extends B {
  @override
  String get ^_ => '';
}
'''),
    );
  }

  Future<void> test_insideClass() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {}

class [!B!] extends A {}

class C extends B {
  ^
}
'''),
    );
  }

  Future<void> test_insideMethod() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]() {}
}

class B extends A {}

class C extends B {
  @override
  void foo() {
    ^
  }
}
'''),
    );
  }

  Future<void>
  test_method_parameter_defaultValue_inNullAwareElement_inList() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]([List<int> arg = const []]) {}
}

class B extends A {
  @override
  void foo([List<int> arg = const [?nu^ll]]) {}
}
'''),
    );
  }

  Future<void>
  test_method_parameter_defaultValue_inNullAwareElement_inSet() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]([Set<int> arg = const {}]) {}
}

class B extends A {
  @override
  void foo([Set<int> arg = const {?nu^ll}]) {}
}
'''),
    );
  }

  Future<void> test_method_parameter_defaultValue_inNullAwareKey_inMap() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]([Map<int, String> arg = const {}]) {}
}

class B extends A {
  @override
  void foo([Map<int, String> arg = const {?nu^ll: "value"}]) {}
}
'''),
    );
  }

  Future<void>
  test_method_parameter_defaultValue_inNullAwareValue_inMap() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]([Map<String, int> arg = const {}]) {}
}

class B extends A {
  @override
  void foo([Map<String, int> arg = const {"key": ?nu^ll}]) {}
}
'''),
    );
  }

  Future<void> test_methodName() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]() {}
}

class B extends A {}

class C extends B {
  @override
  void fo^o() {}
}
'''),
    );
  }

  Future<void> test_methodName_startOfParameterList() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]() {}
}

class B extends A {}

class C extends B {
  @override
  void foo^() {}
}
'''),
    );
  }

  Future<void> test_methodName_startOfTypeParameterList() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]<T>() {}
}

class B extends A {}

class C extends B {
  @override
  void foo^<T>() {}
}
'''),
    );
  }

  Future<void> test_methodName_underscore() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!_!]() {}
}

class B extends A {}

class C extends B {
  @override
  void ^_() {}
}
'''),
    );
  }

  Future<void> test_methodReturnType() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  void [!foo!]() {}
}

class B extends A {}

class C extends B {
  @override
  vo^id foo() {}
}
'''),
    );
  }

  Future<void> test_primaryConstructor_body_toPrimary_named() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A.[!named!]();

class B() extends A {
  this : super.named() {
    ^
  }
}
'''),
    );
  }

  Future<void> test_primaryConstructor_body_toPrimary_withbody() async {
    await verifyGoToSuper(
      TestCode.parse('''
class [!A!]() {
  this {}
}

class B() extends A {
  this {
    ^
  }
}
'''),
    );
  }

  Future<void> test_primaryConstructor_body_toPrimary_withoutBody() async {
    await verifyGoToSuper(
      TestCode.parse('''
class [!A!]();

class B() extends A {
  this {
    ^
  }
}
'''),
    );
  }

  Future<void> test_primaryConstructor_body_toSecondary() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
}

class B() extends A {
  this {
    ^
  }
}
'''),
    );
  }

  Future<void> test_primaryConstructor_body_toSecondary_named() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  A.[!named!]();
}

class B() extends A {
  this : super.named() {
    ^
  }
}
'''),
    );
  }

  Future<void>
  test_primaryConstructor_declaration_inConstructorName_toConstructor() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
}

class B.nam^ed() extends A {}
'''),
    );
  }

  Future<void> test_primaryConstructor_declaration_inParam_name() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
}

class B.named(int f^oo) extends A {}
'''),
    );
  }

  Future<void> test_primaryConstructor_declaration_inParam_type() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
}

class B.named(in^t foo) extends A {}
'''),
    );
  }

  Future<void>
  test_primaryConstructor_declaration_inParams_toConstructor() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  [!A!]();
}

class B.named(^) extends A {}
'''),
    );
  }

  Future<void> test_primaryConstructor_declaration_inTypeName_toType() async {
    await verifyGoToSuper(
      TestCode.parse('''
class [!A!] {
  A();
}

class B^.named() extends A {}
'''),
    );
  }

  Future<void> test_primaryConstructor_declaration_toPrimary_named() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A.[!named!]();

class B(^) extends A {
  this : super.named();
}
'''),
    );
  }

  Future<void> test_primaryConstructor_declaration_toPrimary_withBody() async {
    await verifyGoToSuper(
      TestCode.parse('''
class [!A!]() {
  this {}
}

class B^() extends A;
'''),
    );
  }

  Future<void>
  test_primaryConstructor_declaration_toPrimary_withoutBody() async {
    await verifyGoToSuper(
      TestCode.parse('''
class [!A!]();

class B^() extends A;
'''),
    );
  }

  Future<void> test_primaryConstructor_declaration_toSecondary_named() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  A.[!named!]();
}

class B(^) extends A {
  this : super.named();
}
'''),
    );
  }

  Future<void> test_setter_field() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String [!foo!] = '';
}

class B extends A {}

class C extends B {
  @override
  set fo^o(String value) {}
}
'''),
    );
  }

  @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/60469')
  Future<void> test_setter_getter() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  String get [!foo!] => '';
}

class B extends A {}

class C extends B {
  @override
  set fo^o(String value) {}
}
'''),
    );
  }

  Future<void> test_setter_setter() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  set [!foo!](String value) {}
}

class B extends A {}

class C extends B {
  @override
  set fo^o(String value) {}
}
'''),
    );
  }

  Future<void> test_setter_underscore() async {
    await verifyGoToSuper(
      TestCode.parse('''
class A {
  set [!_!](String value) {}
}

class B extends A {}

class C extends B {
  @override
  set ^_(String value) {}
}
'''),
    );
  }

  Future<void> verifyGoToSuper(TestCode code) async {
    await initialize();
    await openFile(mainFileUri, code.code);
    var res = await getSuper(mainFileUri, code.position.position);

    expect(res, equals(Location(uri: mainFileUri, range: code.range.range)));
  }
}
