// Copyright (c) 2020, 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:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../../generated/type_system_base.dart';

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

@reflectiveTest
class ReplaceTopBottomTest extends AbstractTypeSystemTest {
  test_contravariant_bottom() {
    // Not contravariant.
    _check(neverNone, 'Never');

    void checkContravariant(TypeImpl type, String expectedStr) {
      _check(
        functionTypeNone(returnType: intNone, formalParameters: [
          requiredParameter(type: type),
        ]),
        'int Function($expectedStr)',
      );
    }

    checkContravariant(neverNone, 'Object?');

    checkContravariant(
      typeParameterTypeNone(
        typeParameter('T', bound: neverNone),
      ),
      'Object?',
    );
  }

  test_notContravariant_covariant_top() {
    _check(objectQuestion, 'Never');
    _check(dynamicType, 'Never');
    _check(voidNone, 'Never');

    _check(listNone(objectQuestion), 'List<Never>');
    _check(listNone(dynamicType), 'List<Never>');
    _check(listNone(voidNone), 'List<Never>');

    _check(futureOrNone(objectQuestion), 'Never');
    _check(futureOrNone(dynamicType), 'Never');
    _check(futureOrNone(voidNone), 'Never');
    _check(futureOrNone(futureOrNone(voidNone)), 'Never');

    _check(
      functionTypeNone(returnType: intNone, formalParameters: [
        requiredParameter(
          type: functionTypeNone(returnType: intNone, formalParameters: [
            requiredParameter(type: objectQuestion),
          ]),
        ),
      ]),
      'int Function(int Function(Never))',
      typeStr: 'int Function(int Function(Object?))',
    );

    _check(intNone, 'int');
    _check(intQuestion, 'int?');

    _check(listNone(intNone), 'List<int>');
    _check(listNone(intQuestion), 'List<int?>');
    _check(listQuestion(intNone), 'List<int>?');
    _check(listQuestion(intQuestion), 'List<int?>?');
  }

  test_notContravariant_invariant() {
    // typedef F<T> = T Function(T);
    var T = typeParameter('T', variance: Variance.invariant);
    var T_none = typeParameterTypeNone(T);
    var F = typeAlias(
      name: 'F',
      typeParameters: [T],
      aliasedType: functionTypeNone(
        returnType: T_none,
        formalParameters: [requiredParameter(type: T_none)],
      ),
    );

    var F_dynamic = F.instantiateImpl(
      typeArguments: [dynamicType],
      nullabilitySuffix: NullabilitySuffix.none,
    );
    _check(F_dynamic, 'Never Function(Never)');
  }

  void _check(TypeImpl type, String expectedStr, {String? typeStr}) {
    if (typeStr != null) {
      expect(_typeString(type), typeStr);
    }

    var result = typeSystem.replaceTopAndBottom(type);
    var resultStr = _typeString(result);
    expect(resultStr, expectedStr);
  }

  String _typeString(DartType type) {
    return type.getDisplayString();
  }
}
