// 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.

// ignore_for_file: empty_catches

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/yield_positions_with_finally_test.dart
//
const LINE_A = 33;
const LINE_B = 47;
const LINE_C = 59;
const LINE_D = 74;
const LINE_E = 92;
const LINE_F = 108;
const LINE_G = 122;
// AUTOGENERATED END

// break statement
Stream<int> testBreak() async* {
  for (int t = 0; t < 10; t++) {
    try {
      if (t == 1) break;
      await throwException(); // LINE_A
    } catch (e) {
    } finally {
      yield t;
    }
  }
}

// return statement
Stream<int> testReturn() async* {
  for (int t = 0; t < 10; t++) {
    try {
      yield t;
      if (t == 1) return;
      await throwException(); // LINE_B
    } catch (e) {
    } finally {
      yield t;
    }
  }
}

// Multiple functions
Stream<int> testMultipleFunctions() async* {
  try {
    yield 0;
    await throwException(); // LINE_C
  } catch (e) {
  } finally {
    yield 1;
  }
}

// continue statement
Stream<int> testContinueSwitch() async* {
  final int currentState = 0;
  switch (currentState) {
    case 0:
      {
        try {
          if (currentState == 1) continue label;
          await throwException(); // LINE_D
        } catch (e) {
        } finally {
          yield 0;
        }
        yield 1;
        break;
      }
    label:
    case 1:
      break;
  }
}

Stream<int> testNestFinally() async* {
  final int i = 0;
  try {
    if (i == 1) return;
    await throwException(); // LINE_E
  } catch (e) {
  } finally {
    try {
      yield i;
    } finally {
      yield 1;
    }
    yield 1;
  }
}

Stream<int> testAsyncClosureInFinally() async* {
  final int i = 0;
  try {
    if (i == 1) return;
    await throwException(); // LINE_F
  } catch (e) {
  } finally {
    Future<void> inner() async {
      await Future.delayed(Duration(milliseconds: 10));
    }

    await inner();
    yield 1;
  }
}

Future<void> throwException() async {
  await Future.delayed(Duration(milliseconds: 10));
  throw Exception(''); // LINE_G
}

Future<void> code() async {
  await for (var _ in testBreak()) {}
  await for (var _ in testReturn()) {}
  await for (var _ in testMultipleFunctions()) {}
  await for (var _ in testContinueSwitch()) {}
  await for (var _ in testNestFinally()) {}
  await for (var _ in testAsyncClosureInFinally()) {}
}

final tests = <IsolateTest>[
  hasPausedAtStart,
  setBreakpointAtLine(LINE_G),
  resumeIsolate,
  for (final line in [LINE_A, LINE_B, LINE_C, LINE_D, LINE_E, LINE_F]) ...[
    hasStoppedAtBreakpoint,
    stoppedAtLine(LINE_G),
    _expectSecondFrameFromTheTopToBeAt(line),
    resumeIsolate,
  ],
  hasStoppedAtExit,
];

Future<void> Function(VmService, IsolateRef) _expectSecondFrameFromTheTopToBeAt(
  int line,
) {
  return (VmService service, IsolateRef isolateRef) async {
    final isolateId = isolateRef.id!;
    final stack = await service.getStack(isolateId);
    final frames = stack.asyncCausalFrames!;
    expect(frames.length, greaterThanOrEqualTo(3));

    // Check second top frame contains correct line number.
    expect(frames[0].kind, FrameKind.kRegular);
    final frame0Location = frames[0].location!;
    final script0 = await service.getObject(
      isolateId,
      frame0Location.script!.id!,
    ) as Script;
    expect(
      script0.getLineNumberFromTokenPos(frame0Location.tokenPos!),
      LINE_G,
    );
    expect(frames[1].kind, FrameKind.kAsyncSuspensionMarker);
    expect(frames[2].location, isNotNull);
    expect(frames[2].kind, FrameKind.kAsyncCausal);
    final frame2Location = frames[2].location!;
    final script2 = await service.getObject(
      isolateId,
      frame2Location.script!.id!,
    ) as Script;
    expect(
      script2.getLineNumberFromTokenPos(frames[2].location!.tokenPos!),
      line,
    );
  };
}

void main([args = const <String>[]]) => runIsolateTests(
      args,
      tests,
      'yield_positions_with_finally_test.dart',
      testeeConcurrent: code,
      pauseOnStart: true,
      pauseOnExit: true,
    );
