// Copyright 2013 The Flutter Authors. 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:typed_data';
import 'dart:ui';
import 'dart:isolate';
import 'dart:ffi' hide Size;

void main() {}

/// Mutiple tests use this to signal to the C++ side that they are ready for
/// validation.
@pragma('vm:external-name', 'Finish')
external void _finish();

@pragma('vm:entry-point')
void customOnErrorTrue() {
  PlatformDispatcher.instance.onError = (Object error, StackTrace? stack) {
    _finish();
    return true;
  };
  throw Exception('true');
}

@pragma('vm:entry-point')
void customOnErrorFalse() {
  PlatformDispatcher.instance.onError = (Object error, StackTrace? stack) {
    _finish();
    return false;
  };
  throw Exception('false');
}

@pragma('vm:entry-point')
void customOnErrorThrow() {
  PlatformDispatcher.instance.onError = (Object error, StackTrace? stack) {
    _finish();
    throw Exception('throw2');
  };
  throw Exception('throw1');
}

@pragma('vm:entry-point')
void setLatencyPerformanceMode() {
  PlatformDispatcher.instance.requestDartPerformanceMode(DartPerformanceMode.latency);
  _finish();
}

@pragma('vm:entry-point')
void validateSceneBuilderAndScene() {
  final SceneBuilder builder = SceneBuilder();
  builder.pushOffset(10, 10);
  _validateBuilderHasLayers(builder);
  final Scene scene = builder.build();
  _validateBuilderHasNoLayers();
  _captureScene(scene);
  scene.dispose();
  _validateSceneHasNoLayers();
}

@pragma('vm:external-name', 'ValidateBuilderHasLayers')
external _validateBuilderHasLayers(SceneBuilder builder);
@pragma('vm:external-name', 'ValidateBuilderHasNoLayers')
external _validateBuilderHasNoLayers();
@pragma('vm:external-name', 'CaptureScene')
external _captureScene(Scene scene);
@pragma('vm:external-name', 'ValidateSceneHasNoLayers')
external _validateSceneHasNoLayers();

@pragma('vm:entry-point')
void validateEngineLayerDispose() {
  final SceneBuilder builder = SceneBuilder();
  final EngineLayer layer = builder.pushOffset(10, 10);
  _captureRootLayer(builder);
  final Scene scene = builder.build();
  scene.dispose();
  _validateLayerTreeCounts();
  layer.dispose();
  _validateEngineLayerDispose();
}

@pragma('vm:external-name', 'CaptureRootLayer')
external _captureRootLayer(SceneBuilder sceneBuilder);
@pragma('vm:external-name', 'ValidateLayerTreeCounts')
external _validateLayerTreeCounts();
@pragma('vm:external-name', 'ValidateEngineLayerDispose')
external _validateEngineLayerDispose();

@pragma('vm:entry-point')
Future<void> createSingleFrameCodec() async {
  final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(Uint8List.fromList(List<int>.filled(4, 100)));
  final ImageDescriptor descriptor = ImageDescriptor.raw(
    buffer,
    width: 1,
    height: 1,
    pixelFormat: PixelFormat.rgba8888,
  );
  final Codec codec = await descriptor.instantiateCodec();
  _validateCodec(codec);
  final FrameInfo info = await codec.getNextFrame();
  info.image.dispose();
  _validateCodec(codec);
  codec.dispose();
  descriptor.dispose();
  buffer.dispose();
  assert(buffer.debugDisposed);
  _finish();
}

@pragma('vm:external-name', 'ValidateCodec')
external void _validateCodec(Codec codec);

@pragma('vm:entry-point')
void createVertices() {
  const int uint16max = 65535;

  final Int32List colors = Int32List(uint16max);
  final Float32List coords = Float32List(uint16max * 2);
  final Uint16List indices = Uint16List(uint16max);
  final Float32List positions = Float32List(uint16max * 2);
  colors[0] = const Color(0xFFFF0000).value;
  colors[1] = const Color(0xFF00FF00).value;
  colors[2] = const Color(0xFF0000FF).value;
  colors[3] = const Color(0xFF00FFFF).value;
  indices[1] = indices[3] = 1;
  indices[2] = indices[5] = 3;
  indices[4] = 2;
  positions[2] = positions[4] = positions[5] = positions[7] = 250.0;

  final Vertices vertices = Vertices.raw(
    VertexMode.triangles,
    positions,
    textureCoordinates: coords,
    colors: colors,
    indices: indices,
  );
  _validateVertices(vertices);
}

@pragma('vm:external-name', 'ValidateVertices')
external void _validateVertices(Vertices vertices);

@pragma('vm:entry-point')
void sendSemanticsUpdate() {
  final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder();
  final String label = "label";
  final List<StringAttribute> labelAttributes = <StringAttribute> [
    SpellOutStringAttribute(range: TextRange(start: 1, end: 2)),
  ];

  final String value = "value";
  final List<StringAttribute> valueAttributes = <StringAttribute> [
    SpellOutStringAttribute(range: TextRange(start: 2, end: 3)),
  ];

  final String increasedValue = "increasedValue";
  final List<StringAttribute> increasedValueAttributes = <StringAttribute> [
    SpellOutStringAttribute(range: TextRange(start: 4, end: 5)),
  ];

  final String decreasedValue = "decreasedValue";
  final List<StringAttribute> decreasedValueAttributes = <StringAttribute> [
    SpellOutStringAttribute(range: TextRange(start: 5, end: 6)),
  ];

  final String hint = "hint";
  final List<StringAttribute> hintAttributes = <StringAttribute> [
    LocaleStringAttribute(
      locale: Locale('en', 'MX'), range: TextRange(start: 0, end: 1),
    ),
  ];

  final Float64List transform = Float64List(16);
  final Int32List childrenInTraversalOrder = Int32List(0);
  final Int32List childrenInHitTestOrder = Int32List(0);
  final Int32List additionalActions = Int32List(0);
  transform[0] = 1;
  transform[1] = 0;
  transform[2] = 0;
  transform[3] = 0;

  transform[4] = 0;
  transform[5] = 1;
  transform[6] = 0;
  transform[7] = 0;

  transform[8] = 0;
  transform[9] = 0;
  transform[10] = 1;
  transform[11] = 0;

  transform[12] = 0;
  transform[13] = 0;
  transform[14] = 0;
  transform[15] = 0;
  builder.updateNode(
    id: 0,
    flags: 0,
    actions: 0,
    maxValueLength: 0,
    currentValueLength: 0,
    textSelectionBase: -1,
    textSelectionExtent: -1,
    platformViewId: -1,
    scrollChildren: 0,
    scrollIndex: 0,
    scrollPosition: 0,
    scrollExtentMax: 0,
    scrollExtentMin: 0,
    rect: Rect.fromLTRB(0, 0, 10, 10),
    elevation: 0,
    thickness: 0,
    label: label,
    labelAttributes: labelAttributes,
    value: value,
    valueAttributes: valueAttributes,
    increasedValue: increasedValue,
    increasedValueAttributes: increasedValueAttributes,
    decreasedValue: decreasedValue,
    decreasedValueAttributes: decreasedValueAttributes,
    hint: hint,
    hintAttributes: hintAttributes,
    textDirection: TextDirection.ltr,
    transform: transform,
    childrenInTraversalOrder: childrenInTraversalOrder,
    childrenInHitTestOrder: childrenInHitTestOrder,
    additionalActions: additionalActions);
  _semanticsUpdate(builder.build());
}

@pragma('vm:external-name', 'SemanticsUpdate')
external void _semanticsUpdate(SemanticsUpdate update);

@pragma('vm:entry-point')
void createPath() {
  final Path path = Path()..lineTo(10, 10);
  _validatePath(path);
  // Arbitrarily hold a reference to the path to make sure it does not get
  // garbage collected.
  Future<void>.delayed(const Duration(days: 100)).then((_) {
    path.lineTo(100, 100);
  });
}

@pragma('vm:external-name', 'ValidatePath')
external void _validatePath(Path path);

@pragma('vm:entry-point')
void frameCallback(Object? image, int durationMilliseconds, String decodeError) {
  validateFrameCallback(image, durationMilliseconds, decodeError);
}

@pragma('vm:external-name', 'ValidateFrameCallback')
external void validateFrameCallback(Object? image, int durationMilliseconds, String decodeError);

@pragma('vm:entry-point')
void platformMessagePortResponseTest() async {
  ReceivePort receivePort = ReceivePort();
  _callPlatformMessageResponseDartPort(receivePort.sendPort.nativePort);
  List<dynamic> resultList = await receivePort.first;
  int identifier = resultList[0] as int;
  Uint8List? bytes = resultList[1] as Uint8List?;
  ByteData result = ByteData.sublistView(bytes!);
  if (result.lengthInBytes == 100) {
    _finishCallResponse(true);
  } else {
    _finishCallResponse(false);
  }
}

@pragma('vm:entry-point')
void platformMessageResponseTest() {
  _callPlatformMessageResponseDart((ByteData? result) {
    if (result is UnmodifiableByteDataView &&
        result.lengthInBytes == 100) {
      _finishCallResponse(true);
    } else {
      _finishCallResponse(false);
    }
  });
}

@pragma('vm:external-name', 'CallPlatformMessageResponseDartPort')
external void _callPlatformMessageResponseDartPort(int port);
@pragma('vm:external-name', 'CallPlatformMessageResponseDart')
external void _callPlatformMessageResponseDart(void Function(ByteData? result) callback);
@pragma('vm:external-name', 'FinishCallResponse')
external void _finishCallResponse(bool didPass);

@pragma('vm:entry-point')
void messageCallback(dynamic data) {}

@pragma('vm:entry-point')
@pragma('vm:external-name', 'ValidateConfiguration')
external void validateConfiguration();

// Draw a circle on a Canvas that has a PictureRecorder. Take the image from
// the PictureRecorder, and encode it as png. Check that the png data is
// backed by an external Uint8List.
@pragma('vm:entry-point')
Future<void> encodeImageProducesExternalUint8List() async {
  final PictureRecorder pictureRecorder = PictureRecorder();
  final Canvas canvas = Canvas(pictureRecorder);
  final Paint paint = Paint()
    ..color = Color.fromRGBO(255, 255, 255, 1.0)
    ..style = PaintingStyle.fill;
  final Offset c = Offset(50.0, 50.0);
  canvas.drawCircle(c, 25.0, paint);
  final Picture picture = pictureRecorder.endRecording();
  final Image image = await picture.toImage(100, 100);
  _encodeImage(image, ImageByteFormat.png.index, (Uint8List result) {
    // The buffer should be non-null and writable.
    result[0] = 0;
    // The buffer should be external typed data.
    _validateExternal(result);
  });
}

@pragma('vm:external-name', 'EncodeImage')
external void _encodeImage(Image i, int format, void Function(Uint8List result));
@pragma('vm:external-name', 'ValidateExternal')
external void _validateExternal(Uint8List result);

@pragma('vm:entry-point')
Future<void> pumpImage() async {
  const int width = 60;
  const int height = 60;
  final Completer<Image> completer = Completer<Image>();
  decodeImageFromPixels(
    Uint8List.fromList(List<int>.filled(width * height * 4, 0xFF)),
    width,
    height,
    PixelFormat.rgba8888,
    (Image image) => completer.complete(image),
  );
  final Image image = await completer.future;
  late Picture picture;
  late OffsetEngineLayer layer;

  void renderBlank(Duration duration) {
    image.dispose();
    picture.dispose();
    layer.dispose();

    final PictureRecorder recorder = PictureRecorder();
    final Canvas canvas = Canvas(recorder);
    canvas.drawPaint(Paint());
    picture = recorder.endRecording();
    final SceneBuilder builder = SceneBuilder();
    layer = builder.pushOffset(0, 0);
    builder.addPicture(Offset.zero, picture);

    final Scene scene = builder.build();
    window.render(scene);
    scene.dispose();

    _finish();
  }

  void renderImage(Duration duration) {
    final PictureRecorder recorder = PictureRecorder();
    final Canvas canvas = Canvas(recorder);
    canvas.drawImage(image, Offset.zero, Paint());
    picture = recorder.endRecording();

    final SceneBuilder builder = SceneBuilder();
    layer = builder.pushOffset(0, 0);
    builder.addPicture(Offset.zero, picture);

    _captureImageAndPicture(image, picture);

    final Scene scene = builder.build();
    window.render(scene);
    scene.dispose();

    window.onBeginFrame = renderBlank;
    window.scheduleFrame();
  }

  window.onBeginFrame = renderImage;
  window.scheduleFrame();
}

@pragma('vm:external-name', 'CaptureImageAndPicture')
external void _captureImageAndPicture(Image image, Picture picture);

@pragma('vm:entry-point')
void convertPaintToDlPaint() {
  Paint paint = Paint();
  paint.blendMode = BlendMode.modulate;
  paint.color = Color.fromARGB(0x11, 0x22, 0x33, 0x44);
  paint.colorFilter = ColorFilter.mode(Color.fromARGB(0x55, 0x66, 0x77, 0x88), BlendMode.xor);
  paint.maskFilter = MaskFilter.blur(BlurStyle.inner, .75);
  paint.style = PaintingStyle.stroke;
  _convertPaintToDlPaint(paint);
}
@pragma('vm:external-name',  'ConvertPaintToDlPaint')
external void _convertPaintToDlPaint(Paint paint);

@pragma('vm:entry-point')
void hooksTests() async {
  Future<void> test(String name, FutureOr<void> Function() testFunction) async {
    try {
      await testFunction();
    } catch (e) {
      print('Test "$name" failed!');
      rethrow;
    }
  }

  void expectEquals(Object? value, Object? expected) {
    if (value != expected) {
      throw 'Expected $value to be $expected.';
    }
  }

  void expectIdentical(Object a, Object b) {
    if (!identical(a, b)) {
      throw 'Expected $a to be identical to $b.';
    }
  }

  void expectNotEquals(Object? value, Object? expected) {
    if (value == expected) {
      throw 'Expected $value to not be $expected.';
    }
  }

  await test('onMetricsChanged preserves callback zone', () {
    late Zone originalZone;
    late Zone callbackZone;
    late double devicePixelRatio;

    runZoned(() {
      originalZone = Zone.current;
      window.onMetricsChanged = () {
        callbackZone = Zone.current;
        devicePixelRatio = window.devicePixelRatio;
      };
    });

    window.onMetricsChanged!();
    _callHook(
      '_updateWindowMetrics',
      21,
      0, // window Id
      0.1234, // device pixel ratio
      0.0,    // width
      0.0,    // height
      0.0,    // padding top
      0.0,    // padding right
      0.0,    // padding bottom
      0.0,    // padding left
      0.0,    // inset top
      0.0,    // inset right
      0.0,    // inset bottom
      0.0,    // inset left
      0.0,    // system gesture inset top
      0.0,    // system gesture inset right
      0.0,    // system gesture inset bottom
      0.0,    // system gesture inset left
      22.0,   // physicalTouchSlop
      <double>[],  // display features bounds
      <int>[],     // display features types
      <int>[],     // display features states
      0, // Display ID
    );

    expectIdentical(originalZone, callbackZone);
    if (devicePixelRatio != 0.1234) {
      throw 'Expected devicePixelRatio to be 0.1234 but got $devicePixelRatio.';
    }
  });

  await test('onError preserves the callback zone', () {
    late Zone originalZone;
    late Zone callbackZone;
    final Object error = Exception('foo');
    StackTrace? stackTrace;

    runZoned(() {
      originalZone = Zone.current;
      PlatformDispatcher.instance.onError = (Object exception, StackTrace? stackTrace) {
        callbackZone = Zone.current;
        expectIdentical(exception, error);
        expectNotEquals(stackTrace, null);
        return true;
      };
    });

    _callHook('_onError', 2, error, StackTrace.current);
    PlatformDispatcher.instance.onError = null;
    expectIdentical(originalZone, callbackZone);
  });

  await test('updateUserSettings can handle an empty object', () {
    _callHook('_updateUserSettingsData', 1, '{}');
  });

  await test('PlatformDispatcher.locale returns unknown locale when locales is set to empty list', () {
    late Locale locale;
    int callCount = 0;
    runZoned(() {
      window.onLocaleChanged = () {
        locale = PlatformDispatcher.instance.locale;
        callCount += 1;
      };
    });

    const Locale fakeLocale = Locale.fromSubtags(languageCode: '1', countryCode: '2', scriptCode: '3');
    _callHook('_updateLocales', 1, <String>[fakeLocale.languageCode, fakeLocale.countryCode!, fakeLocale.scriptCode!, '']);
    if (callCount != 1) {
      throw 'Expected 1 call, have $callCount';
    }
    if (locale != fakeLocale) {
      throw 'Expected $locale to match $fakeLocale';
    }
    _callHook('_updateLocales', 1, <String>[]);
    if (callCount != 2) {
      throw 'Expected 2 calls, have $callCount';
    }

    if (locale != const Locale.fromSubtags()) {
      throw '$locale did not equal ${Locale.fromSubtags()}';
    }
    if (locale.languageCode != 'und') {
      throw '${locale.languageCode} did not equal "und"';
    }
  });

  await test('deprecated region equals', () {
    // These are equal because ZR is deprecated and was mapped to CD.
    const Locale x = Locale('en', 'ZR');
    const Locale y = Locale('en', 'CD');
    expectEquals(x, y);
    expectEquals(x.countryCode, y.countryCode);
  });

  await test('PlatformDispatcher.view getter returns view with provided ID', () {
    const int viewId = 0;
    expectEquals(PlatformDispatcher.instance.view(id: viewId)?.viewId, viewId);
  });

  await test('View padding/insets/viewPadding/systemGestureInsets', () {
    _callHook(
      '_updateWindowMetrics',
      21,
      0, // window Id
      1.0, // devicePixelRatio
      800.0, // width
      600.0, // height
      50.0, // paddingTop
      0.0, // paddingRight
      40.0, // paddingBottom
      0.0, // paddingLeft
      0.0, // insetTop
      0.0, // insetRight
      0.0, // insetBottom
      0.0, // insetLeft
      0.0, // systemGestureInsetTop
      0.0, // systemGestureInsetRight
      0.0, // systemGestureInsetBottom
      0.0, // systemGestureInsetLeft
      22.0, // physicalTouchSlop
      <double>[],  // display features bounds
      <int>[],     // display features types
      <int>[],     // display features states
      0, // Display ID
    );

    expectEquals(window.viewInsets.bottom, 0.0);
    expectEquals(window.viewPadding.bottom, 40.0);
    expectEquals(window.padding.bottom, 40.0);
    expectEquals(window.systemGestureInsets.bottom, 0.0);

    _callHook(
      '_updateWindowMetrics',
      21,
      0, // window Id
      1.0, // devicePixelRatio
      800.0, // width
      600.0, // height
      50.0, // paddingTop
      0.0, // paddingRight
      40.0, // paddingBottom
      0.0, // paddingLeft
      0.0, // insetTop
      0.0, // insetRight
      400.0, // insetBottom
      0.0, // insetLeft
      0.0, // systemGestureInsetTop
      0.0, // systemGestureInsetRight
      44.0, // systemGestureInsetBottom
      0.0, // systemGestureInsetLeft
      22.0, // physicalTouchSlop
      <double>[],  // display features bounds
      <int>[],     // display features types
      <int>[],     // display features states
      0, // Display ID
    );

    expectEquals(window.viewInsets.bottom, 400.0);
    expectEquals(window.viewPadding.bottom, 40.0);
    expectEquals(window.padding.bottom, 0.0);
    expectEquals(window.systemGestureInsets.bottom, 44.0);
  });

  await test('Window physical touch slop', () {
    _callHook(
      '_updateWindowMetrics',
      21,
      0, // window Id
      1.0, // devicePixelRatio
      800.0, // width
      600.0, // height
      50.0, // paddingTop
      0.0, // paddingRight
      40.0, // paddingBottom
      0.0, // paddingLeft
      0.0, // insetTop
      0.0, // insetRight
      0.0, // insetBottom
      0.0, // insetLeft
      0.0, // systemGestureInsetTop
      0.0, // systemGestureInsetRight
      0.0, // systemGestureInsetBottom
      0.0, // systemGestureInsetLeft
      11.0, // physicalTouchSlop
      <double>[],  // display features bounds
      <int>[],     // display features types
      <int>[],     // display features states
      0, // Display ID
    );

    expectEquals(window.gestureSettings,
      GestureSettings(physicalTouchSlop: 11.0));

    _callHook(
      '_updateWindowMetrics',
      21,
      0, // window Id
      1.0, // devicePixelRatio
      800.0, // width
      600.0, // height
      50.0, // paddingTop
      0.0, // paddingRight
      40.0, // paddingBottom
      0.0, // paddingLeft
      0.0, // insetTop
      0.0, // insetRight
      400.0, // insetBottom
      0.0, // insetLeft
      0.0, // systemGestureInsetTop
      0.0, // systemGestureInsetRight
      44.0, // systemGestureInsetBottom
      0.0, // systemGestureInsetLeft
      -1.0, // physicalTouchSlop
      <double>[],  // display features bounds
      <int>[],     // display features types
      <int>[],     // display features states
      0, // Display ID
    );

    expectEquals(window.gestureSettings,
      GestureSettings(physicalTouchSlop: null));

    _callHook(
      '_updateWindowMetrics',
      21,
      0, // window Id
      1.0, // devicePixelRatio
      800.0, // width
      600.0, // height
      50.0, // paddingTop
      0.0, // paddingRight
      40.0, // paddingBottom
      0.0, // paddingLeft
      0.0, // insetTop
      0.0, // insetRight
      400.0, // insetBottom
      0.0, // insetLeft
      0.0, // systemGestureInsetTop
      0.0, // systemGestureInsetRight
      44.0, // systemGestureInsetBottom
      0.0, // systemGestureInsetLeft
      22.0, // physicalTouchSlop
      <double>[],  // display features bounds
      <int>[],     // display features types
      <int>[],     // display features states
      0, // Display ID
    );

    expectEquals(window.gestureSettings,
      GestureSettings(physicalTouchSlop: 22.0));
  });

  await test('onLocaleChanged preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    Locale? locale;

    runZoned(() {
      innerZone = Zone.current;
      window.onLocaleChanged = () {
        runZone = Zone.current;
        locale = window.locale;
      };
    });

    _callHook('_updateLocales', 1, <String>['en', 'US', '', '']);
    expectIdentical(runZone, innerZone);
    expectEquals(locale, const Locale('en', 'US'));
  });

  await test('onBeginFrame preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late Duration start;

    runZoned(() {
      innerZone = Zone.current;
      window.onBeginFrame = (Duration value) {
        runZone = Zone.current;
        start = value;
      };
    });

    _callHook('_beginFrame', 2, 1234, 1);
    expectIdentical(runZone, innerZone);
    expectEquals(start, const Duration(microseconds: 1234));
  });

  await test('onDrawFrame preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;

    runZoned(() {
      innerZone = Zone.current;
      window.onDrawFrame = () {
        runZone = Zone.current;
      };
    });

    _callHook('_drawFrame');
    expectIdentical(runZone, innerZone);
  });

  await test('onReportTimings preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;

    runZoned(() {
      innerZone = Zone.current;
      window.onReportTimings = (List<FrameTiming> timings) {
        runZone = Zone.current;
      };
    });

    _callHook('_reportTimings', 1, <int>[]);
    expectIdentical(runZone, innerZone);
  });

  await test('onPointerDataPacket preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late PointerDataPacket data;

    runZoned(() {
      innerZone = Zone.current;
      window.onPointerDataPacket = (PointerDataPacket value) {
        runZone = Zone.current;
        data = value;
      };
    });

    final ByteData testData = ByteData.view(Uint8List(0).buffer);
    _callHook('_dispatchPointerDataPacket', 1, testData);
    expectIdentical(runZone, innerZone);
    expectEquals(data.data.length, 0);
  });

  await test('onSemanticsEnabledChanged preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late bool enabled;

    runZoned(() {
      innerZone = Zone.current;
      window.onSemanticsEnabledChanged = () {
        runZone = Zone.current;
        enabled = window.semanticsEnabled;
      };
    });

    final bool newValue = !window.semanticsEnabled; // needed?
    _callHook('_updateSemanticsEnabled', 1, newValue);
    expectIdentical(runZone, innerZone);
    expectEquals(enabled, newValue);
  });

  await test('onSemanticsActionEvent preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late SemanticsActionEvent action;

    runZoned(() {
      innerZone = Zone.current;
      PlatformDispatcher.instance.onSemanticsActionEvent = (SemanticsActionEvent actionEvent) {
        runZone = Zone.current;
        action = actionEvent;
      };
    });

    _callHook('_dispatchSemanticsAction', 3, 1234, 4, null);
    expectIdentical(runZone, innerZone);
    expectEquals(action.nodeId, 1234);
    expectEquals(action.type.index, 4);
  });

  await test('onPlatformMessage preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late String name;

    runZoned(() {
      innerZone = Zone.current;
      window.onPlatformMessage = (String value, _, __) {
        runZone = Zone.current;
        name = value;
      };
    });

    _callHook('_dispatchPlatformMessage', 3, 'testName', null, 123456789);
    expectIdentical(runZone, innerZone);
    expectEquals(name, 'testName');
  });

  await test('onTextScaleFactorChanged preserves callback zone', () {
    late Zone innerZone;
    late Zone runZoneTextScaleFactor;
    late Zone runZonePlatformBrightness;
    late double? textScaleFactor;
    late Brightness? platformBrightness;

    runZoned(() {
      innerZone = Zone.current;
      window.onTextScaleFactorChanged = () {
        runZoneTextScaleFactor = Zone.current;
        textScaleFactor = window.textScaleFactor;
      };
      window.onPlatformBrightnessChanged = () {
        runZonePlatformBrightness = Zone.current;
        platformBrightness = window.platformBrightness;
      };
    });

    window.onTextScaleFactorChanged!();

    _callHook('_updateUserSettingsData', 1, '{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}');
    expectIdentical(runZoneTextScaleFactor, innerZone);
    expectEquals(textScaleFactor, 0.5);

    textScaleFactor = null;
    platformBrightness = null;

    window.onPlatformBrightnessChanged!();
    _callHook('_updateUserSettingsData', 1, '{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}');
    expectIdentical(runZonePlatformBrightness, innerZone);
    expectEquals(platformBrightness, Brightness.dark);
  });

  await test('onFrameDataChanged preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late int frameNumber;

    runZoned(() {
      innerZone = Zone.current;
      window.onFrameDataChanged = () {
        runZone = Zone.current;
        frameNumber = window.frameData.frameNumber;
      };
    });

    _callHook('_beginFrame', 2, 0, 2);
    expectNotEquals(runZone, null);
    expectIdentical(runZone, innerZone);
    expectEquals(frameNumber, 2);
  });

  await test('_updateDisplays preserves callback zone', () {
    late Zone innerZone;
    late Zone runZone;
    late Display display;

    runZoned(() {
      innerZone = Zone.current;
      window.onMetricsChanged = () {
        runZone = Zone.current;
        display = PlatformDispatcher.instance.displays.first;
      };
    });

    _callHook('_updateDisplays', 5, <int>[0], <double>[800], <double>[600], <double>[1.5], <double>[65]);
    expectNotEquals(runZone, null);
    expectIdentical(runZone, innerZone);
    expectEquals(display.id, 0);
    expectEquals(display.size, const Size(800, 600));
    expectEquals(display.devicePixelRatio, 1.5);
    expectEquals(display.refreshRate, 65);
  });

  await test('_futureize handles callbacker sync error', () async {
    String? callbacker(void Function(Object? arg) cb) {
      return 'failure';
    }
    Object? error;
    try {
      await _futurize(callbacker);
    } catch (err) {
      error = err;
    }
    expectNotEquals(error, null);
  });

  await test('_futureize does not leak sync uncaught exceptions into the zone', () async {
    String? callbacker(void Function(Object? arg) cb) {
      cb(null); // indicates failure
    }
    Object? error;
    try {
      await _futurize(callbacker);
    } catch (err) {
      error = err;
    }
    expectNotEquals(error, null);
  });

  await test('_futureize does not leak async uncaught exceptions into the zone', () async {
    String? callbacker(void Function(Object? arg) cb) {
      Timer.run(() {
        cb(null); // indicates failure
      });
    }
    Object? error;
    try {
      await _futurize(callbacker);
    } catch (err) {
      error = err;
    }
    expectNotEquals(error, null);
  });

  await test('_futureize successfully returns a value sync', () async {
    String? callbacker(void Function(Object? arg) cb) {
      cb(true);
    }
    final Object? result = await _futurize(callbacker);

    expectEquals(result, true);
  });

  await test('_futureize successfully returns a value async', () async {
    String? callbacker(void Function(Object? arg) cb) {
      Timer.run(() {
        cb(true);
      });
    }
    final Object? result = await _futurize(callbacker);

    expectEquals(result, true);
  });

  await test('root isolate token', () async {
    if (RootIsolateToken.instance == null) {
      throw Exception('We should have a token on a root isolate.');
    }
    ReceivePort receivePort = ReceivePort();
    Isolate.spawn(_backgroundRootIsolateTestMain, receivePort.sendPort);
    bool didPass = await receivePort.first as bool;
    if (!didPass) {
      throw Exception('Background isolate found a root isolate id.');
    }
  });

  await test('send port message without registering', () async {
    ReceivePort receivePort = ReceivePort();
    Isolate.spawn(_backgroundIsolateSendWithoutRegistering, receivePort.sendPort);
    bool didError = await receivePort.first as bool;
    if (!didError) {
      throw Exception('Expected an error when not registering a root isolate and sending port messages.');
    }
  });

  _finish();
}

/// Sends `true` on [port] if the isolate executing the function is not a root
/// isolate.
void _backgroundRootIsolateTestMain(SendPort port) {
  port.send(RootIsolateToken.instance == null);
}

/// Sends `true` on [port] if [PlatformDispatcher.sendPortPlatformMessage]
/// throws an exception without calling
/// [PlatformDispatcher.registerBackgroundIsolate].
void _backgroundIsolateSendWithoutRegistering(SendPort port) {
  bool didError = false;
  ReceivePort messagePort = ReceivePort();
  try {
    PlatformDispatcher.instance.sendPortPlatformMessage(
      'foo',
      null,
      1,
      messagePort.sendPort,
    );
  } catch (_) {
    didError = true;
  }
  port.send(didError);
}

typedef _Callback<T> = void Function(T result);
typedef _Callbacker<T> = String? Function(_Callback<T?> callback);

// This is an exact copy of the function defined in painting.dart. If you change either
// then you must change both.
Future<T> _futurize<T>(_Callbacker<T> callbacker) {
  final Completer<T> completer = Completer<T>.sync();
  // If the callback synchronously throws an error, then synchronously
  // rethrow that error instead of adding it to the completer. This
  // prevents the Zone from receiving an uncaught exception.
  bool sync = true;
  final String? error = callbacker((T? t) {
    if (t == null) {
      if (sync) {
        throw Exception('operation failed');
      } else {
        completer.completeError(Exception('operation failed'));
      }
    } else {
      completer.complete(t);
    }
  });
  sync = false;
  if (error != null)
    throw Exception(error);
  return completer.future;
}

@pragma('vm:external-name', 'CallHook')
external void _callHook(
  String name, [
  int argCount = 0,
  Object? arg0,
  Object? arg1,
  Object? arg2,
  Object? arg3,
  Object? arg4,
  Object? arg5,
  Object? arg6,
  Object? arg8,
  Object? arg9,
  Object? arg10,
  Object? arg11,
  Object? arg12,
  Object? arg13,
  Object? arg14,
  Object? arg15,
  Object? arg16,
  Object? arg17,
  Object? arg18,
  Object? arg19,
  Object? arg20,
  Object? arg21,
]);
