// 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/element/element.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../context_collection_resolution.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(TearOffTest);
  });
}

@reflectiveTest
class TearOffTest extends PubPackageResolutionTest {
  test_empty_contextNotInstantiated() async {
    await assertErrorsInCode('''
T f<T>(T x) => x;

void test() {
  U Function<U>(U) context;
  context = f; // 1
}
''', [
      error(WarningCode.UNUSED_LOCAL_VARIABLE, 52, 7),
    ]);
    _assertTearOff(
      'f; // 1',
      findElement.topFunction('f'),
      'T Function<T>(T)',
    );
  }

  test_empty_notGeneric() async {
    await assertErrorsInCode('''
int f(int x) => x;

void test() {
  int Function(int) context;
  context = f; // 1
}
''', [
      error(WarningCode.UNUSED_LOCAL_VARIABLE, 54, 7),
    ]);
    _assertTearOff(
      'f; // 1',
      findElement.topFunction('f'),
      'int Function(int)',
    );
  }

  test_notEmpty_instanceMethod() async {
    await assertNoErrorsInCode('''
class C {
  T f<T>(T x) => x;
}

int Function(int) test() {
  return new C().f;
}
''');
    _assertGenericFunctionInstantiation(
      'f;',
      findElement.method('f', of: 'C'),
      'int Function(int)',
      ['int'],
    );
  }

  test_notEmpty_localFunction() async {
    await assertNoErrorsInCode('''
int Function(int) test() {
  T f<T>(T x) => x;
  return f;
}
''');
    _assertGenericFunctionInstantiation(
      'f;',
      findElement.localFunction('f'),
      'int Function(int)',
      ['int'],
    );
  }

  test_notEmpty_staticMethod() async {
    await assertNoErrorsInCode('''
class C {
  static T f<T>(T x) => x;
}

int Function(int) test() {
  return C.f;
}
''');
    _assertGenericFunctionInstantiation(
      'f;',
      findElement.method('f', of: 'C'),
      'int Function(int)',
      ['int'],
    );
  }

  test_notEmpty_superMethod() async {
    await assertNoErrorsInCode('''
class C {
  T f<T>(T x) => x;
}

class D extends C {
  int Function(int) test() {
    return super.f;
  }
}
''');
    _assertGenericFunctionInstantiation(
      'f;',
      findElement.method('f', of: 'C'),
      'int Function(int)',
      ['int'],
    );
  }

  test_notEmpty_topLevelFunction() async {
    await assertNoErrorsInCode('''
T f<T>(T x) => x;

int Function(int) test() {
  return f;
}
''');
    _assertGenericFunctionInstantiation(
      'f;',
      findElement.topFunction('f'),
      'int Function(int)',
      ['int'],
    );
  }

  test_null_notTearOff() async {
    await assertNoErrorsInCode('''
T f<T>(T x) => x;

void test() {
  f(0);
}
''');

    var node = findNode.singleMethodInvocation;
    assertResolvedNodeText(node, r'''
MethodInvocation
  methodName: SimpleIdentifier
    token: f
    staticElement: <testLibraryFragment>::@function::f
    element: <testLibraryFragment>::@function::f#element
    staticType: T Function<T>(T)
  argumentList: ArgumentList
    leftParenthesis: (
    arguments
      IntegerLiteral
        literal: 0
        parameter: ParameterMember
          base: <testLibraryFragment>::@function::f::@parameter::x
          substitution: {T: int}
        staticType: int
    rightParenthesis: )
  staticInvokeType: int Function(int)
  staticType: int
  typeArgumentTypes
    int
''');
  }

  void _assertGenericFunctionInstantiation(
    String search,
    ExecutableElement element,
    String type,
    List<String>? typeArguments,
  ) {
    var id = findNode.functionReference(search);
    assertElement(id, element);
    assertType(id, type);
    if (typeArguments != null) {
      assertElementTypes(id.typeArgumentTypes, typeArguments);
    } else {
      expect(id.typeArgumentTypes, isNull);
    }
  }

  void _assertTearOff(
    String search,
    ExecutableElement element,
    String type,
  ) {
    var id = findNode.simple(search);
    assertElement(id, element);
    assertType(id, type);
    expect(id.tearOffTypeArgumentTypes, isNull);
  }
}
