// 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 'dart:collection';
import 'dart:developer';

import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';

import 'common/service_test_common.dart';
import 'common/test_helper.dart';

// AUTOGENERATED START
//
// Update these constants by running:
//
// dart pkg/vm_service/test/update_line_numbers.dart pkg/vm_service/test/evaluate_type_arguments_test.dart
//
const LINE_A = 39;
const LINE_B = 44;
const LINE_C = 50;
// AUTOGENERATED END

class A {}

class B extends A {}

class C extends Object with ListMixin<C> implements List<C> {
  @override
  int length = 0;
  @override
  C operator [](int index) => throw UnimplementedError();
  @override
  void operator []=(int index, C value) {}
}

void testFunction4<T4 extends List<T4>>() {
  debugger(); // LINE_A
  print('T4 = $T4');
}

void testFunction3<T3, S3 extends T3>() {
  debugger(); // LINE_B
  print('T3 = $T3');
  print('S3 = $S3');
}

void testFunction2<E extends String>(List<E> x) {
  debugger(); // LINE_C
  print('x = $x');
}

void testFunction() {
  testFunction2<String>(<String>['a', 'b', 'c']);
  testFunction3<A, B>();
  testFunction4<C>();
}

final tests = <IsolateTest>[
  hasStoppedAtBreakpoint,
  stoppedAtLine(LINE_C),
  (VmService service, IsolateRef isolateRef) async {
    final isolateId = isolateRef.id!;
    {
      // Can add List<E extends String> to List<String> directly.
      final expression = '''
      () {
        List<E> y = List<E>.from(["hello"]);
        x.addAll(y);
        return x.last;
      }()
      ''';
      await evaluateInFrameAndExpect(
        service,
        isolateId,
        expression,
        'hello',
        kind: InstanceKind.kString,
      );
    }
    {
      final expression = '''
      () {
        List<E> y = [];
        y.addAll(x);
        return y.last;
      }()
      ''';
      // Can't add List<String> to List<E extends String> directly.
      try {
        await service.evaluateInFrame(isolateId, 0, expression);
        fail("Can't add List<String> to List<E extends String> directly.");
      } on RPCError catch (e) {
        expect(e.code, RPCErrorKind.kExpressionCompilationError.code);
        expect(
          e.details,
          contains(
            "The argument type '_GrowableList<String>' can't be assigned "
            "to the parameter type 'Iterable<E>'",
          ),
        );
      }
    }
    {
      // Can add List<String> to List<E extends String> via cast.
      final expression = '''
      () {
        List<E> y = [];
        y.addAll(x.cast());
        return y.toString();
      }()
      ''';
      await evaluateInFrameAndExpect(
        service,
        isolateId,
        expression,
        // Notice how "hello" was added a few evaluations back.
        '[a, b, c, hello]',
        kind: InstanceKind.kString,
      );
    }
    {
      // Can create List<String> from List<E extends String>.
      final expression = '''
      () {
        List<E> y = List<E>.from(x);
        return y.toString();
      }()
      ''';
      await evaluateInFrameAndExpect(
        service,
        isolateId,
        expression,
        // Notice how "hello" was added a few evaluations back.
        '[a, b, c, hello]',
        kind: InstanceKind.kString,
      );
    }
  },
  resumeIsolate,
  hasStoppedAtBreakpoint,
  stoppedAtLine(LINE_B),
  (VmService service, IsolateRef isolateRef) async {
    // This is just to make sure the VM doesn't crash.
    final isolateId = isolateRef.id!;
    await evaluateInFrameAndExpect(
      service,
      isolateId,
      'S3.toString()',
      'B',
      kind: InstanceKind.kString,
    );
  },
  resumeIsolate,
  hasStoppedAtBreakpoint,
  stoppedAtLine(LINE_A),
  (VmService service, IsolateRef isolateRef) async {
    // This is just to make sure the VM doesn't crash.
    final isolateId = isolateRef.id!;
    await evaluateInFrameAndExpect(
      service,
      isolateId,
      'T4.toString()',
      'C',
      kind: InstanceKind.kString,
    );
  },
];

void main([args = const <String>[]]) => runIsolateTests(
      args,
      tests,
      'evaluate_type_arguments_test.dart',
      testeeConcurrent: testFunction,
    );
