// Copyright (c) 2024, 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:developer' show debugger;
import 'dart:io' show Directory, File;
import 'dart:isolate' as i;

import 'package:path/path.dart' show join;
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/set_reload_and_reset_breakpoints_test.dart
//
const LINE_A = 44;
// AUTOGENERATED END

const v0Contents = '''
import 'dart:developer';

void f() {}

void main() {
  debugger();
  f();
}
''';

Future<void> testeeMain() async {
  // Spawn the child isolate.
  final tempDir = Directory.systemTemp.createTempSync();
  try {
    final rootLib = File(join(tempDir.path, 'main.dart'));
    rootLib.writeAsStringSync(v0Contents);

    await i.Isolate.spawnUri(rootLib.uri, [], null);
    debugger(); // LINE_A
    tempDir.deleteSync(recursive: true);
  } catch (_) {
    tempDir.deleteSync(recursive: true);
    rethrow;
  }
}

final tests = <IsolateTest>[
  // Ensure that the main isolate has stopped at the [debugger] statement at the
  // end of [testeeMain].
  hasStoppedAtBreakpoint,
  stoppedAtLine(LINE_A),
  (VmService service, IsolateRef isolateRef) async {
    final tempDir = Directory.systemTemp.createTempSync();
    try {
      const numberOfBreakpoints = 20;

      final v1Contents = StringBuffer();
      v1Contents.write('''
import 'dart:developer';

void f() {
''');

      for (int i = 0; i < numberOfBreakpoints; i++) {
        // Add closure definitions to [v1Contents]. The test will set
        // breakpoints at the [print] calls in these closures to make the
        // debugger eagerly compile the closures.
        v1Contents.write('''
  (() {
    print($i);
  })();
  ''');
      }

      v1Contents.write('''
}

void main() {
  debugger();
  f();
}
''');

      final spawnedIsolateRootLib = File(join(tempDir.path, 'main.dart'));

      // Find the spawned isolate.
      final vm = await service.getVM();
      final isolates = vm.isolates!;
      expect(isolates.length, 2);
      final spawnedIsolateRef = isolates.firstWhere(
        (i) => i != isolateRef,
      );
      final spawnedIsolateId = spawnedIsolateRef.id!;

      // Load [v1Contents] into the spawned isolate.
      spawnedIsolateRootLib.writeAsStringSync(v1Contents.toString());
      await service.reloadSources(
        spawnedIsolateId,
        rootLibUri: spawnedIsolateRootLib.uri.toString(),
        force: true,
      );

      Isolate spawnedIsolate = await service.getIsolate(spawnedIsolateId);
      Library rootLib = await service.getObject(
        spawnedIsolateId,
        spawnedIsolate.rootLib!.id!,
      ) as Library;
      String scriptId = rootLib.scripts![0].id!;

      final testStartTime = DateTime.now();

      // Add a breakpoints at all of the `print` calls.
      for (int i = 0; i < numberOfBreakpoints; i++) {
        expect(
          (await service.addBreakpoint(spawnedIsolateId, scriptId, 5 + 3 * i))
              .resolved,
          true,
        );
      }

      // Force a reload of the sources.
      await service.reloadSources(
        spawnedIsolateId,
        rootLibUri: spawnedIsolateRootLib.uri.toString(),
        force: true,
      );

      spawnedIsolate = await service.getIsolate(spawnedIsolateId);
      rootLib = await service.getObject(
        spawnedIsolateId,
        spawnedIsolate.rootLib!.id!,
      ) as Library;
      scriptId = rootLib.scripts![0].id!;

      // Remove the [numberOfBreakpoints] existing breakpoints.
      for (int i = 1; i <= numberOfBreakpoints; i++) {
        await service.removeBreakpoint(spawnedIsolateId, 'breakpoints/$i');
      }
      // Add [numberOfBreakpoints] new breakpoints.
      for (int i = 0; i < numberOfBreakpoints; i++) {
        expect(
          (await service.addBreakpoint(spawnedIsolateId, scriptId, 5 + 3 * i))
              .resolved,
          true,
        );
      }
      // Remove the [numberOfBreakpoints] new breakpoints.
      for (int i = numberOfBreakpoints + 1; i <= numberOfBreakpoints * 2; i++) {
        await service.removeBreakpoint(spawnedIsolateId, 'breakpoints/$i');
      }

      print(
        'Took ${DateTime.now().difference(testStartTime).inMilliseconds}ms',
      );

      await resumeIsolate(service, spawnedIsolateRef);
      tempDir.deleteSync(recursive: true);
    } catch (_) {
      tempDir.deleteSync(recursive: true);
      rethrow;
    }
  },
];

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