// 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:isolate';
import 'dart:nativewrappers';

import 'package:expect/async_helper.dart';
import 'package:expect/expect.dart';

class Foo {
  int i = 42;
}

class Bar {
  Foo foo = Foo();
}

@pragma('vm:entry-point') // prevent obfuscation
base class NativeClass extends NativeFieldWrapperClass1 {}

class Baz {
  @pragma('vm:entry-point') // prevent tree-shaking of the field.
  NativeClass? nativeClass;
  Baz();
}

@pragma('vm:entry-point') // prevent obfuscation
class Fu {
  static final f = 42;
  String label;
  Bar bar = Bar();
  @pragma('vm:entry-point') // prevent tree-shaking of the field.
  Baz baz = Baz();

  Fu(this.label);
  Fu.unsendable(this.label) {
    baz.nativeClass = NativeClass();
  }
}

@pragma('vm:entry-point') // prevent obfuscation
@pragma("vm:isolate-unsendable")
class Locked {}

@pragma('vm:entry-point') // prevent obfuscation
class ExtendsLocked extends Locked {}

@pragma('vm:entry-point') // prevent obfuscation
class ImplementsLocked implements Locked {}

Future<T> sendAndReceive<T>(T object) async {
  final rp = ReceivePort();
  rp.sendPort.send(object);
  return await rp.first;
}

bool checkForRetainingPath(Object? e, List<String> list) {
  if (e is! ArgumentError) {
    return false;
  }
  final msg = e.toString();
  return list.every((s) => msg.contains(s));
}

main() async {
  asyncStart();

  final rp = ReceivePort();

  for (final pair in [
    [
      () => Fu.unsendable('fu'),
      [
        "Class: NativeClass",
        "nativeClass in Instance of 'Baz'",
        "baz in Instance of 'Fu'",
      ],
    ],
    [
      () => Future.value(123),
      ["Future"],
    ],
    [
      Locked.new,
      ["Locked"],
    ],
    [
      ExtendsLocked.new,
      ["ExtendsLocked"],
    ],
    [
      ImplementsLocked.new,
      ["ImplementsLocked"],
    ],
  ]) {
    Expect.throws(
      () => rp.sendPort.send((pair[0] as Function)()),
      (e) => checkForRetainingPath(e, pair[1] as List<String>),
    );
  }

  try {
    await Isolate.spawn((_) {}, Locked());
    Expect.fail('spawn should have failed');
  } catch (e) {
    Expect.isTrue(e is ArgumentError && e.toString().contains("Locked"));
  }

  runZoned(() {
    Expect.throws(() {
      final z = Zone.current;
      rp.sendPort.send(Zone.current);
    }, (e) => e is ArgumentError);
  }, zoneValues: {0: 1});

  rp.close();
  asyncEnd();
}
