// This file covers abstract operations defined in
// https://tc39.es/ecma262/#sec-import-calls

import {
  AbstractModuleRecord,
  Assert,
  Call, CreateBuiltinFunction, CreateListIteratorRecord, GatherAsynchronousTransitiveDependencies, GetModuleNamespace, NewPromiseCapability, PerformPromiseThen, PromiseCapabilityRecord, surroundingAgent, Value,
  type Arguments,
  type PromiseObject,
} from '../index.mts';
import {
  AbruptCompletion, ValueOfNormalCompletion, X, type PlainCompletion,
} from '../completion.mts';
import { PerformPromiseAll } from '../intrinsics/Promise.mts';

/** https://tc39.es/ecma262/#sec-ContinueDynamicImport */
export function ContinueDynamicImport(
  promiseCapability: PromiseCapabilityRecord,
  moduleCompletion: PlainCompletion<AbstractModuleRecord>,
  phase: 'defer' | 'evaluation',
) {
  // 1. If moduleCompletion is an abrupt completion, then
  if (moduleCompletion instanceof AbruptCompletion) {
    // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »).
    X(Call(promiseCapability.Reject, Value.undefined, [moduleCompletion.Value]));
    // b. Return unused.
    return;
  }
  // 2. Let module be moduleCompletion.[[Value]].
  const module = ValueOfNormalCompletion(moduleCompletion);

  // 3. Let loadPromise be module.LoadRequestedModules().
  const loadPromise = module.LoadRequestedModules();

  // 4. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called:
  const rejectedClosure = ([reason = Value.undefined]: Arguments): void => {
    // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
    X(Call(promiseCapability.Reject, Value.undefined, [reason]));
    // b. Return unused.
  };
  // 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
  const onRejected = CreateBuiltinFunction(rejectedClosure, 1, Value(''), []);

  // 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, and onRejected and performs the following steps when called:
  function* linkAndEvaluateClosure() {
    // a. Let link be Completion(module.Link()).
    const link = module.Link();
    // b. If link is an abrupt completion, then
    if (link instanceof AbruptCompletion) {
      // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »).
      X(Call(promiseCapability.Reject, Value.undefined, [link.Value]));
      // ii. Return unused.
      return;
    }

    let evaluatePromise: PromiseObject;
    // c. Let evaluatePromise be module.Evaluate().
    evaluatePromise = yield* module.Evaluate();

    // d. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and promiseCapability and performs the following steps when called:
    const fulfilledClosure = () => {
      // i. Let namespace be GetModuleNamespace(module).
      const namespace = GetModuleNamespace(module, phase);
      // ii. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »).
      X(Call(promiseCapability.Resolve, Value.undefined, [namespace]));
      // iii. Return unused.
    };

    // e. If phase is "defer", then
    if (phase === 'defer') {
      // i. Let evaluationList be module.GatherAsynchronousTransitiveDependencies().
      const evaluationList = GatherAsynchronousTransitiveDependencies(module);
      // ii. If evaluationList is empty, then
      if (evaluationList.length === 0) {
        // 1. Perform fulfilledClosure().
        fulfilledClosure();
        // 2. Return unused.
        return;
      }
      // iii. Let asyncDepsEvaluationPromises be a new empty List.
      const asyncDepsEvaluationPromises = [];
      // iv. For each dep in evaluationList, append dep.Evaluate() to asyncDepsEvaluationPromises.
      for (const dep of evaluationList) {
        asyncDepsEvaluationPromises.push(yield* dep.Evaluate());
      }
      // v. Let iterator be CreateListIteratorRecord(asyncDepsEvaluationPromises).
      const iterator = CreateListIteratorRecord(asyncDepsEvaluationPromises);
      // vi. Let pc be ! NewPromiseCapability(%Promise%).
      const pc = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
      // vii. Let evaluatePromise be ! PerformPromiseAll(iterator, %Promise%, pc, %Promise.resolve%).
      evaluatePromise = X(PerformPromiseAll(iterator, surroundingAgent.intrinsic('%Promise%'), pc, surroundingAgent.intrinsic('%Promise.resolve%'))) as PromiseObject;
    } else { // f. Else,
      // i. Assert: phase is EVALUATION.
      Assert(phase === 'evaluation');
      // ii. Let evaluatePromise be module.Evaluate().
      evaluatePromise = yield* module.Evaluate();
    }

    // e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
    const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 0, Value(''), []);

    // f. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected).
    PerformPromiseThen(evaluatePromise!, onFulfilled, onRejected);
    // g. Return unused.
  }
  // 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »).
  const linkAndEvaluate = CreateBuiltinFunction(linkAndEvaluateClosure, 0, Value(''), []);

  // 8. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected).
  PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected);
  // 9. Return unused.
}
