///|
/// Promise jobs and resolving functions use the executor activation seam while
/// keeping Promise state transitions in this runtime package.  The generic
/// microtask queue remains responsible for observation, selection, and
/// consumption; these private callables only own the selected job's lifetime.

///|
priv enum PromiseResolutionKind {
  PromiseResolutionResolve
  PromiseResolutionReject
}

///|
priv struct PromiseResolutionExecutorCode {
  owner : Interpreter
  promise_data : PromiseData
  already_resolved : Ref[Bool]
  kind : PromiseResolutionKind
}

///|
fn PromiseResolutionExecutorCode::PromiseResolutionExecutorCode(
  owner~ : Interpreter,
  promise_data~ : PromiseData,
  already_resolved~ : Ref[Bool],
  kind~ : PromiseResolutionKind,
) -> PromiseResolutionExecutorCode {
  { owner, promise_data, already_resolved, kind, }
}

///|
priv enum PromiseResolutionPhase {
  PromiseResolutionNeedStart
  PromiseResolutionAwaitThenGet
  PromiseResolutionNeedThenableJob
  PromiseResolutionNeedFulfill(Value)
  PromiseResolutionNeedReject(Value)
  PromiseResolutionComplete
}

///|
priv struct PromiseResolutionExecutorFrame {
  owner : Interpreter
  promise_data : PromiseData
  already_resolved : Ref[Bool]
  kind : PromiseResolutionKind
  global : Environment
  resolution : Value
  mut then_value : Value
  mut phase : PromiseResolutionPhase
}

///|
fn PromiseResolutionExecutorFrame::PromiseResolutionExecutorFrame(
  owner~ : Interpreter,
  promise_data~ : PromiseData,
  already_resolved~ : Ref[Bool],
  kind~ : PromiseResolutionKind,
  global~ : Environment,
  resolution~ : Value,
) -> PromiseResolutionExecutorFrame {
  {
    owner,
    promise_data,
    already_resolved,
    kind,
    global,
    resolution,
    then_value: Undefined,
    phase: PromiseResolutionNeedStart,
  }
}

///|
// Only JavaScript guest errors become promise rejection reasons.  Internal
// errors and execution-control failures must cross the checkpoint boundary as
// runtime failures instead of being silently converted into guest values.
fn promise_guest_error_value(error : Error, global : Environment) -> Value? {
  match error {
    JsException(value) => Some(value)
    error =>
      match @errors.name_message_if_js_error(error) {
        Some(("InternalError", _)) => None
        Some(_) => Some(js_error_to_value_with_env(error, Some(global)))
        None => None
      }
  }
}

///|
fn PromiseResolutionExecutorFrame::step_start(
  self : PromiseResolutionExecutorFrame,
  interp : Interpreter,
) -> ExecutorActivationStep raise Error {
  match self.kind {
    PromiseResolutionReject => {
      if self.already_resolved.val {
        self.phase = PromiseResolutionComplete
      } else {
        self.already_resolved.val = true
        self.phase = PromiseResolutionNeedReject(self.resolution)
      }
      self.step_frame(interp)
    }
    PromiseResolutionResolve =>
      if self.already_resolved.val {
        self.phase = PromiseResolutionComplete
        self.step_frame(interp)
      } else {
        // First-call-wins is committed before any property lookup or call.
        self.already_resolved.val = true
        match self.resolution {
          Promise(pd) if physical_equal(pd, self.promise_data) => {
            let error = js_error_to_value_with_env(
              @errors.TypeError(message="Chaining cycle detected for promise"),
              Some(self.global),
            )
            self.phase = PromiseResolutionNeedReject(error)
            self.step_frame(interp)
          }
          _ if is_thenable_object_candidate(self.resolution) => {
            self.phase = PromiseResolutionAwaitThenGet
            executor_activation_managed_property_get(
              self.resolution,
              "then",
              @token.Loc::default(),
            )
          }
          _ => {
            self.phase = PromiseResolutionNeedFulfill(self.resolution)
            self.step_frame(interp)
          }
        }
      }
  }
}

///|
fn PromiseResolutionExecutorFrame::step_frame(
  self : PromiseResolutionExecutorFrame,
  interp : Interpreter,
) -> ExecutorActivationStep raise Error {
  match self.phase {
    PromiseResolutionNeedStart => self.step_start(interp)
    PromiseResolutionNeedFulfill(value) => {
      fulfill_promise(self.owner, self.promise_data, value)
      self.phase = PromiseResolutionComplete
      ExecutorActivationReturn(Undefined)
    }
    PromiseResolutionNeedReject(reason) => {
      reject_promise(self.owner, self.promise_data, reason)
      self.phase = PromiseResolutionComplete
      ExecutorActivationReturn(Undefined)
    }
    PromiseResolutionNeedThenableJob => {
      enqueue_promise_thenable_job(
        self.owner,
        self.promise_data,
        self.resolution,
        self.then_value,
      )
      self.phase = PromiseResolutionComplete
      ExecutorActivationReturn(Undefined)
    }
    PromiseResolutionComplete => ExecutorActivationReturn(Undefined)
    PromiseResolutionAwaitThenGet =>
      raise @errors.InternalError(
        message="Promise resolution frame stepped while awaiting a child",
      )
  }
}

///|
impl ExecutorActivationFrame for PromiseResolutionExecutorFrame with fn step(
  self,
  interp,
) {
  self.step_frame(interp)
}

///|
impl ExecutorActivationFrame for PromiseResolutionExecutorFrame with fn deliver_activation_completion(
  self,
  completion,
) {
  match completion {
    ExecutorActivationCompletionReference(_) =>
      raise @errors.InternalError(
        message="Promise resolution frame received an unexpected binding reference completion",
      )
    ExecutorActivationCompletionNormal(value) =>
      match self.phase {
        PromiseResolutionAwaitThenGet =>
          if is_callable(value) {
            self.then_value = value
            self.phase = PromiseResolutionNeedThenableJob
          } else {
            self.phase = PromiseResolutionNeedFulfill(self.resolution)
          }
        PromiseResolutionNeedStart
        | PromiseResolutionNeedThenableJob
        | PromiseResolutionNeedFulfill(_)
        | PromiseResolutionNeedReject(_)
        | PromiseResolutionComplete =>
          raise @errors.InternalError(
            message="Promise resolution frame received an unexpected completion",
          )
      }
    ExecutorActivationCompletionAbrupt(error) =>
      match self.phase {
        PromiseResolutionAwaitThenGet =>
          match promise_guest_error_value(error, self.global) {
            Some(reason) => self.phase = PromiseResolutionNeedReject(reason)
            None => raise error
          }
        PromiseResolutionNeedStart
        | PromiseResolutionNeedThenableJob
        | PromiseResolutionNeedFulfill(_)
        | PromiseResolutionNeedReject(_)
        | PromiseResolutionComplete =>
          raise @errors.InternalError(
            message="Promise resolution frame received an unexpected abrupt completion",
          )
      }
  }
}

///|
impl ExecutorCode for PromiseResolutionExecutorCode with fn start(
  self,
  _interp,
  prepared,
) {
  let args = prepared.arguments()
  let resolution = if args.length() > 0 { args[0] } else { Undefined }
  PromiseResolutionExecutorFrame(
    owner=self.owner,
    promise_data=self.promise_data,
    already_resolved=self.already_resolved,
    kind=self.kind,
    global=self.owner.global,
    resolution~,
  )
  as &ExecutorActivationFrame
}

///|
// Both capabilities capture one shared alreadyResolved cell.  Thenable
// assimilation creates a fresh pair for the managed then call, as required by
// the Promise Resolve Thenable Job algorithm.
fn make_promise_resolving_functions(
  interp : Interpreter,
  promise_data : PromiseData,
) -> (Value, Value) {
  let already_resolved : Ref[Bool] = { val: false, }
  let resolve = make_executor_function(
    Some(""),
    ["resolution"],
    interp.global,
    true,
    PromiseResolutionExecutorCode(
      owner=interp,
      promise_data~,
      already_resolved~,
      kind=PromiseResolutionResolve,
    ),
    constructable=false,
    define_arguments_object=false,
  )
  let reject = make_executor_function(
    Some(""),
    ["reason"],
    interp.global,
    true,
    PromiseResolutionExecutorCode(
      owner=interp,
      promise_data~,
      already_resolved~,
      kind=PromiseResolutionReject,
    ),
    constructable=false,
    define_arguments_object=false,
  )
  (resolve, reject)
}

///|
fn promise_thenable_job_realm_protos(
  interp : Interpreter,
  then_method : Value,
) -> FunctionRealmProtos {
  let selected = get_function_realm_protos(then_method) catch { _ => None }
  match selected {
    Some(protos) => protos
    None => active_realm_protos(interp.realm_state)
  }
}

///|
priv struct PromiseThenableJobExecutorCode {
  promise_data : PromiseData
  thenable : Value
  then_method : Value
  realm_protos : FunctionRealmProtos
  consumed : Ref[Bool]
}

///|
fn PromiseThenableJobExecutorCode::PromiseThenableJobExecutorCode(
  promise_data~ : PromiseData,
  thenable~ : Value,
  then_method~ : Value,
  realm_protos~ : FunctionRealmProtos,
  consumed~ : Ref[Bool],
) -> PromiseThenableJobExecutorCode {
  { promise_data, thenable, then_method, realm_protos, consumed, }
}

///|
priv enum PromiseThenableJobPhase {
  PromiseThenableJobNeedStart
  PromiseThenableJobAwaitThenCall
  PromiseThenableJobNeedReject(Value)
  PromiseThenableJobAwaitReject
  PromiseThenableJobComplete
}

///|
priv struct PromiseThenableJobExecutorFrame {
  promise_data : PromiseData
  thenable : Value
  then_method : Value
  realm_protos : FunctionRealmProtos
  global : Environment
  mut reject : Value
  mut phase : PromiseThenableJobPhase
}

///|
fn PromiseThenableJobExecutorFrame::PromiseThenableJobExecutorFrame(
  promise_data~ : PromiseData,
  thenable~ : Value,
  then_method~ : Value,
  realm_protos~ : FunctionRealmProtos,
  global~ : Environment,
) -> PromiseThenableJobExecutorFrame {
  {
    promise_data,
    thenable,
    then_method,
    realm_protos,
    global,
    reject: Undefined,
    phase: PromiseThenableJobNeedStart,
  }
}

///|
fn PromiseThenableJobExecutorFrame::step_frame(
  self : PromiseThenableJobExecutorFrame,
  interp : Interpreter,
) -> ExecutorActivationStep raise Error {
  match self.phase {
    PromiseThenableJobNeedStart => {
      // The resolving pair is created at job activation, not when the
      // original resolve function merely schedules this job.
      let (resolve, reject) = with_active_realm_protos(
        interp.realm_state,
        self.realm_protos,
        () => make_promise_resolving_functions(interp, self.promise_data),
      )
      self.reject = reject
      self.phase = PromiseThenableJobAwaitThenCall
      executor_activation_call(
        self.then_method,
        self.thenable,
        [resolve, reject],
        @token.Loc::default(),
      )
    }
    PromiseThenableJobNeedReject(reason) => {
      self.phase = PromiseThenableJobAwaitReject
      executor_activation_call(
        self.reject,
        Undefined,
        [reason],
        @token.Loc::default(),
      )
    }
    PromiseThenableJobComplete => ExecutorActivationReturn(Undefined)
    PromiseThenableJobAwaitThenCall | PromiseThenableJobAwaitReject =>
      raise @errors.InternalError(
        message="Promise thenable job stepped while awaiting a child",
      )
  }
}

///|
impl ExecutorActivationFrame for PromiseThenableJobExecutorFrame with fn step(
  self,
  interp,
) {
  self.step_frame(interp)
}

///|
impl ExecutorActivationFrame for PromiseThenableJobExecutorFrame with fn deliver_activation_completion(
  self,
  completion,
) {
  match completion {
    ExecutorActivationCompletionReference(_) =>
      raise @errors.InternalError(
        message="Promise thenable job received an unexpected binding reference completion",
      )
    ExecutorActivationCompletionNormal(_) =>
      match self.phase {
        PromiseThenableJobAwaitThenCall | PromiseThenableJobAwaitReject =>
          self.phase = PromiseThenableJobComplete
        PromiseThenableJobNeedStart
        | PromiseThenableJobNeedReject(_)
        | PromiseThenableJobComplete =>
          raise @errors.InternalError(
            message="Promise thenable job received an unexpected completion",
          )
      }
    ExecutorActivationCompletionAbrupt(error) =>
      match self.phase {
        PromiseThenableJobAwaitThenCall =>
          match promise_guest_error_value(error, self.global) {
            Some(reason) => self.phase = PromiseThenableJobNeedReject(reason)
            None => raise error
          }
        PromiseThenableJobAwaitReject => raise error
        PromiseThenableJobNeedStart
        | PromiseThenableJobNeedReject(_)
        | PromiseThenableJobComplete =>
          raise @errors.InternalError(
            message="Promise thenable job received an unexpected abrupt completion",
          )
      }
  }
}

///|
impl ExecutorCode for PromiseThenableJobExecutorCode with fn start(
  self,
  interp,
  _prepared,
) {
  guard !self.consumed.val else {
    raise @errors.InternalError(
      message="Promise thenable job executor callable was invoked twice",
    )
  }
  self.consumed.val = true
  PromiseThenableJobExecutorFrame(
    promise_data=self.promise_data,
    thenable=self.thenable,
    then_method=self.then_method,
    realm_protos=self.realm_protos,
    global=interp.global,
  )
  as &ExecutorActivationFrame
}

///|
fn make_promise_thenable_job(
  interp : Interpreter,
  promise_data : PromiseData,
  thenable : Value,
  then_method : Value,
) -> Value {
  let realm_protos = promise_thenable_job_realm_protos(interp, then_method)
  make_executor_function(
    Some("PromiseResolveThenableJob"),
    [],
    interp.global,
    true,
    PromiseThenableJobExecutorCode(
      promise_data~,
      thenable~,
      then_method~,
      realm_protos~,
      consumed={ val: false, },
    ),
    constructable=false,
    define_arguments_object=false,
  )
}

///|
fn enqueue_promise_thenable_job(
  interp : Interpreter,
  promise_data : PromiseData,
  thenable : Value,
  then_method : Value,
) -> Unit {
  let job = make_promise_thenable_job(
    interp, promise_data, thenable, then_method,
  )
  interp.enqueue_microtask(job, [])
}

///|
priv struct PromiseReactionExecutorCode {
  reaction : PromiseReaction
  consumed : Ref[Bool]
}

///|
fn PromiseReactionExecutorCode::PromiseReactionExecutorCode(
  reaction~ : PromiseReaction,
  consumed~ : Ref[Bool],
) -> PromiseReactionExecutorCode {
  { reaction, consumed, }
}

///|
priv enum PromiseReactionPhase {
  PromiseReactionNeedHandler
  PromiseReactionAwaitHandler
  PromiseReactionNeedResolve(Value)
  PromiseReactionAwaitResolve
  PromiseReactionNeedReject(Value)
  PromiseReactionAwaitReject
  PromiseReactionComplete
}

///|
priv struct PromiseReactionExecutorFrame {
  reaction : PromiseReaction
  argument : Value
  global : Environment
  mut phase : PromiseReactionPhase
}

///|
fn PromiseReactionExecutorFrame::PromiseReactionExecutorFrame(
  reaction~ : PromiseReaction,
  argument~ : Value,
  global~ : Environment,
) -> PromiseReactionExecutorFrame {
  { reaction, argument, global, phase: PromiseReactionNeedHandler, }
}

///|
impl ExecutorActivationFrame for PromiseReactionExecutorFrame with fn step(
  self,
  _interp,
) {
  match self.phase {
    PromiseReactionNeedHandler =>
      match self.reaction.handler {
        Some(handler) => {
          self.phase = PromiseReactionAwaitHandler
          executor_activation_call(
            handler,
            Undefined,
            [self.argument],
            @token.Loc::default(),
          )
        }
        None =>
          match self.reaction.reaction_type {
            Fulfill => {
              self.phase = PromiseReactionAwaitResolve
              executor_activation_call(
                self.reaction.resolve,
                Undefined,
                [self.argument],
                @token.Loc::default(),
              )
            }
            Reject => {
              self.phase = PromiseReactionAwaitReject
              executor_activation_call(
                self.reaction.reject,
                Undefined,
                [self.argument],
                @token.Loc::default(),
              )
            }
          }
      }
    PromiseReactionNeedResolve(value) => {
      self.phase = PromiseReactionAwaitResolve
      executor_activation_call(
        self.reaction.resolve,
        Undefined,
        [value],
        @token.Loc::default(),
      )
    }
    PromiseReactionNeedReject(reason) => {
      self.phase = PromiseReactionAwaitReject
      executor_activation_call(
        self.reaction.reject,
        Undefined,
        [reason],
        @token.Loc::default(),
      )
    }
    PromiseReactionComplete => ExecutorActivationReturn(Undefined)
    PromiseReactionAwaitHandler
    | PromiseReactionAwaitResolve
    | PromiseReactionAwaitReject =>
      raise @errors.InternalError(
        message="Promise reaction frame stepped while awaiting a child",
      )
  }
}

///|
impl ExecutorActivationFrame for PromiseReactionExecutorFrame with fn deliver_activation_completion(
  self,
  completion,
) {
  match completion {
    ExecutorActivationCompletionReference(_) =>
      raise @errors.InternalError(
        message="Promise reaction frame received an unexpected binding reference completion",
      )
    ExecutorActivationCompletionNormal(value) =>
      match self.phase {
        PromiseReactionAwaitHandler =>
          self.phase = PromiseReactionNeedResolve(value)
        PromiseReactionAwaitResolve | PromiseReactionAwaitReject =>
          self.phase = PromiseReactionComplete
        PromiseReactionNeedHandler
        | PromiseReactionNeedResolve(_)
        | PromiseReactionNeedReject(_)
        | PromiseReactionComplete =>
          raise @errors.InternalError(
            message="Promise reaction frame received an unexpected completion",
          )
      }
    ExecutorActivationCompletionAbrupt(error) =>
      match self.phase {
        PromiseReactionAwaitHandler =>
          match promise_guest_error_value(error, self.global) {
            Some(reason) => self.phase = PromiseReactionNeedReject(reason)
            None => raise error
          }
        PromiseReactionAwaitResolve | PromiseReactionAwaitReject => raise error
        PromiseReactionNeedHandler
        | PromiseReactionNeedResolve(_)
        | PromiseReactionNeedReject(_)
        | PromiseReactionComplete =>
          raise @errors.InternalError(
            message="Promise reaction frame received an unexpected abrupt completion",
          )
      }
  }
}

///|
impl ExecutorCode for PromiseReactionExecutorCode with fn start(
  self,
  interp,
  prepared,
) {
  guard !self.consumed.val else {
    raise @errors.InternalError(
      message="Promise reaction executor callable was invoked twice",
    )
  }
  self.consumed.val = true
  let args = prepared.arguments()
  let argument = if args.length() > 0 { args[0] } else { Undefined }
  PromiseReactionExecutorFrame(
    reaction=self.reaction,
    argument~,
    global=interp.global,
  )
  as &ExecutorActivationFrame
}

///|
fn make_promise_reaction_job(
  interp : Interpreter,
  reaction : PromiseReaction,
) -> Value {
  make_executor_function(
    Some("PromiseReactionJob"),
    [],
    interp.global,
    true,
    PromiseReactionExecutorCode(reaction~, consumed={ val: false, }),
    constructable=false,
    define_arguments_object=false,
  )
}