// Copyright (c) 2022, 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.

// Machinery for async and await.
//
// The implementation is based on two mechanisms in the JS Promise integration:
//
// The export wrapper: Allocates a new stack and calls the wrapped export on the
// new stack, passing a suspender object as an extra first argument that
// represents the new stack.
//
// The import wrapper: Takes a suspender object as an extra first argument and
// calls the wrapped import. If the wrapped import returns a `Promise`, the
// current stack is suspended, and the `Promise` is forwarded to the
// corresponding call of the export wrapper, where execution resumes on the
// original stack. When the `Promise` is resolved, execution resumes on the
// suspended stack, with the call to the import wrapper returning the value the
// `Promise` was resolved with.
//
// The call sequence when calling an async function is:
//
// Caller
//  -> Outer (function specific, generated by `generateAsyncWrapper`)
//  -> `_asyncHelper`
//  -> `_callAsyncBridge` (imported JS function)
//  -> `_asyncBridge` (via the Promise integration export wrapper)
//  -> `_asyncBridge2` (intrinsic function)
//  -> Stub (function specific, generated by `generateAsyncWrapper`)
//  -> Inner (contains implementation, generated from async inner reference)
//
// The call sequence on await is:
//
// Function containing await
//  -> `_awaitHelper`
//  -> `_futurePromise` (via the Promise integration import wrapper)
//  -> `new Promise`
//  -> `Promise` constructor callback
//  -> `_awaitCallback`
//  -> `Future.then`
// `futurePromise` returns the newly created `Promise`, suspending the
// current execution.
//
// When the `Future` completes:
//
// `Future.then` callback
//  -> `_callResolve` (imported JS function)
//  -> `Promise` resolve function
// Resolving the `Promise` causes the suspended execution to resume.

import 'dart:_internal' show patch, scheduleCallback, unsafeCastOpaque;

import 'dart:wasm';

@pragma("wasm:entry-point")
Future<T> _asyncHelper<T>(WasmEqRef args) {
  Completer<T> completer = Completer();
  _callAsyncBridge(args, completer);
  return completer.future;
}

@pragma("wasm:import", "dart2wasm.callAsyncBridge")
external void _callAsyncBridge(WasmEqRef args, Completer<Object?> completer);

@pragma("wasm:export", "\$asyncBridge")
WasmAnyRef? _asyncBridge(
    WasmExternRef? stack, WasmExternRef? argsRef, WasmExternRef? completerRef) {
  WasmDataRef args = unsafeCastOpaque(argsRef!.internalize());
  Completer<Object?> completer = unsafeCastOpaque(completerRef!.internalize());
  try {
    Object? result = _asyncBridge2(args, stack);
    completer.complete(result);
  } catch (e, s) {
    completer.completeError(e, s);
  }
}

external Object? _asyncBridge2(WasmDataRef args, WasmExternRef? stack);

class _FutureError {
  final Object exception;
  final StackTrace stackTrace;

  _FutureError(this.exception, this.stackTrace);
}

@pragma("wasm:entry-point")
Object? _awaitHelper(Object? operand, WasmExternRef? stack) {
  if (operand is! Future) return operand;
  WasmExternRef futureRef = WasmAnyRef.fromObject(operand).externalize();
  Object? result =
      unsafeCastOpaque(_futurePromise(stack, futureRef).internalize());
  if (result is _FutureError) {
    // TODO(askesc): Combine stack traces
    throw result.exception;
  }
  return result;
}

@pragma("wasm:import", "dart2wasm.futurePromise")
external WasmExternRef? _futurePromise(
    WasmExternRef? stack, WasmExternRef? future);

@pragma("wasm:export", "\$awaitCallback")
void _awaitCallback(Future<Object?> future, WasmAnyRef resolve) {
  future.then((value) {
    _callResolve(resolve, value);
  }, onError: (exception, stackTrace) {
    _callResolve(resolve, _FutureError(exception, stackTrace));
  });
}

@pragma("wasm:import", "dart2wasm.callResolve")
external void _callResolve(WasmAnyRef resolve, Object? result);
