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

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/valid_source_locations_test.dart
//
const LINE_A = 23;
// AUTOGENERATED END

void testFunction() {
  debugger(); // LINE_A
}

final scriptCache = <String, Script>{};

Future<void> validateLocation(SourceLocation? location, Object object) async {
  if (location == null) return;
  if (location.tokenPos! < 0) return;
  if (location.script!.uri == 'dart:_internal-patch/class_id_fasta.dart') {
    // Injected fields from this script cannot be reloaded.
    return;
  }

  // Ensure the script is loaded, but don't repeatedly request script objects
  // otherwise the test will time out.
  final scriptId = location.script!.id!;
  final script = scriptCache.containsKey(scriptId)
      ? scriptCache[scriptId]!
      : scriptCache[scriptId] =
          await rootService.getObject(isolateId, scriptId) as Script;

  // Use the more low-level functions.
  final line = script.getLineNumberFromTokenPos(location.tokenPos!);
  if (line == null) {
    throw 'missing location for $object in script ${script.uri}';
  }
  script.getColumnNumberFromTokenPos(location.tokenPos!);
}

Future<void> validateFieldLocation(FieldRef fieldRef) async {
  await validateLocation(fieldRef.location, fieldRef);
}

Future<void> validateFunctionLocation(FuncRef funcRef) async {
  await validateLocation(funcRef.location, funcRef);
}

Future<void> validateClassLocation(ClassRef klassRef) async {
  final klass = await rootService.getObject(
    isolateId,
    klassRef.id!,
  ) as Class;
  await validateLocation(klass.location, klass);

  for (final field in klass.fields!) {
    await validateFieldLocation(field);
  }
  for (final func in klass.functions!) {
    await validateFunctionLocation(func);
  }
}

late final String isolateId;
late final VmService rootService;

final tests = <IsolateTest>[
  hasStoppedAtBreakpoint,
  stoppedAtLine(LINE_A),
  (VmService service, IsolateRef isolateRef) async {
    rootService = service;
    isolateId = isolateRef.id!;
    // Force everything to be compiled.
    await service.getSourceReport(
      isolateId,
      [SourceReportKind.kCoverage],
      forceCompile: true,
    );

    final isolate = await service.getIsolate(isolateId);

    // Loop over all libraries, classes, functions and fields to ensure source
    // locations are present.
    for (final libRef in isolate.libraries!) {
      // Printing the current library name to give some indication of progress.
      // This test is quite slow and appears to hang without any output.
      print('Verifying ${libRef.name ?? '<unknown name>'} (id: ${libRef.id})');
      final lib = await service.getObject(
        isolateId,
        libRef.id!,
      ) as Library;

      for (final field in lib.variables!) {
        await validateFieldLocation(field);
      }
      for (final fun in lib.functions!) {
        await validateFunctionLocation(fun);
      }
      for (final klass in lib.classes!) {
        await validateClassLocation(klass);
      }
    }
  },
];

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