/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function IteratorIdentity() {
  return this;
}

/* ECMA262 7.2.7 */
function IteratorNext(iteratorRecord, value) {
  // Steps 1-2.
  const result =
    ArgumentsLength() < 2
      ? callContentFunction(iteratorRecord.nextMethod, iteratorRecord.iterator)
      : callContentFunction(
          iteratorRecord.nextMethod,
          iteratorRecord.iterator,
          value
        );
  // Step 3.
  if (!IsObject(result)) {
    ThrowTypeError(JSMSG_OBJECT_REQUIRED, result);
  }
  // Step 4.
  return result;
}

/* ECMA262 7.4.6 */
function IteratorClose(iteratorRecord, value) {
  // Step 3.
  const iterator = iteratorRecord.iterator;
  // Step 4.
  const returnMethod = iterator.return;
  // Step 5.
  if (!IsNullOrUndefined(returnMethod)) {
    const result = callContentFunction(returnMethod, iterator);
    // Step 8.
    if (!IsObject(result)) {
      ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, result));
    }
  }
  // Step 5b & 9.
  return value;
}

/**
 * ES2022 draft rev c5f683e61d5dce703650f1c90d2309c46f8c157a
 *
 * GetIterator ( obj [ , hint [ , method ] ] )
 * https://tc39.es/ecma262/#sec-getiterator
 *
 * Optimized for single argument
 */
function GetIteratorSync(obj) {
  // Steps 1 & 2 skipped as we know we want the sync iterator method
  var method = GetMethod(obj, GetBuiltinSymbol("iterator"));

  // Step 3. Let iterator be ? Call(method, obj).
  var iterator = callContentFunction(method, obj);

  // Step 4. If Type(iterator) is not Object, throw a TypeError exception.
  if (!IsObject(iterator)) {
    ThrowTypeError(JSMSG_NOT_ITERABLE, obj === null ? "null" : typeof obj);
  }

  // Step 5. Let nextMethod be ? GetV(iterator, "next").
  var nextMethod = iterator.next;

  // Step 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
  var iteratorRecord = {
    iterator,
    nextMethod,
    done: false,
  };

  // Step 7. Return iteratorRecord.
  return iteratorRecord;
}

/* Iterator Helpers proposal 1.1.1 */
function GetIteratorDirect(obj) {
  // Step 1.
  if (!IsObject(obj)) {
    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, obj));
  }

  // Step 2.
  const nextMethod = obj.next;
  // Step 3.
  if (!IsCallable(nextMethod)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, nextMethod));
  }

  // Steps 4-5.
  return {
    iterator: obj,
    nextMethod,
    done: false,
  };
}

function GetIteratorDirectWrapper(obj) {
  // Step 1.
  if (!IsObject(obj)) {
    ThrowTypeError(JSMSG_OBJECT_REQUIRED, obj);
  }

  // Step 2.
  const nextMethod = obj.next;
  // Step 3.
  if (!IsCallable(nextMethod)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, nextMethod);
  }

  // Steps 4-5.
  return {
    // Use a named function expression instead of a method definition, so
    // we don't create an inferred name for this function at runtime.
    [GetBuiltinSymbol("iterator")]: function IteratorMethod() {
      return this;
    },
    next(value) {
      return callContentFunction(nextMethod, obj, value);
    },
    return(value) {
      const returnMethod = obj.return;
      if (!IsNullOrUndefined(returnMethod)) {
        return callContentFunction(returnMethod, obj, value);
      }
      return { done: true, value };
    },
  };
}

/* Iterator Helpers proposal 1.1.2 */
function IteratorStep(iteratorRecord, value) {
  // Steps 2-3.
  let result;
  if (ArgumentsLength() === 2) {
    result = callContentFunction(
      iteratorRecord.nextMethod,
      iteratorRecord.iterator,
      value
    );
  } else {
    result = callContentFunction(
      iteratorRecord.nextMethod,
      iteratorRecord.iterator
    );
  }

  // IteratorNext Step 3.
  if (!IsObject(result)) {
    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, result));
  }

  // Steps 4-6.
  return result.done ? false : result;
}

/* Iterator Helpers proposal 2.1.3.3.1 */
function IteratorFrom(O) {
  // Step 1.
  const usingIterator = O[GetBuiltinSymbol("iterator")];

  let iteratorRecord;
  // Step 2.
  if (!IsNullOrUndefined(usingIterator)) {
    // Step a.
    // Inline call to GetIterator.
    const iterator = callContentFunction(usingIterator, O);
    iteratorRecord = GetIteratorDirect(iterator);
    // Step b-c.
    if (iteratorRecord.iterator instanceof GetBuiltinConstructor("Iterator")) {
      return iteratorRecord.iterator;
    }
  } else {
    // Step 3.
    iteratorRecord = GetIteratorDirect(O);
  }

  // Step 4.
  const wrapper = NewWrapForValidIterator();
  // Step 5.
  UnsafeSetReservedSlot(wrapper, ITERATED_SLOT, iteratorRecord);
  // Step 6.
  return wrapper;
}

/* Iterator Helpers proposal 2.1.3.3.1.1.1 */
function WrapForValidIteratorNext(value) {
  // Step 1-2.
  let O = this;
  if (!IsObject(O) || (O = GuardToWrapForValidIterator(O)) === null) {
    if (ArgumentsLength() === 0) {
      return callFunction(
        CallWrapForValidIteratorMethodIfWrapped,
        this,
        "WrapForValidIteratorNext"
      );
    }
    return callFunction(
      CallWrapForValidIteratorMethodIfWrapped,
      this,
      value,
      "WrapForValidIteratorNext"
    );
  }
  const iterated = UnsafeGetReservedSlot(O, ITERATED_SLOT);
  // Step 3.
  let result;
  if (ArgumentsLength() === 0) {
    result = callContentFunction(iterated.nextMethod, iterated.iterator);
  } else {
    // Step 4.
    result = callContentFunction(iterated.nextMethod, iterated.iterator, value);
  }
  // Inlined from IteratorNext.
  if (!IsObject(result)) {
    ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, result));
  }
  return result;
}

/* Iterator Helpers proposal 2.1.3.3.1.1.2 */
function WrapForValidIteratorReturn(value) {
  // Step 1-2.
  let O = this;
  if (!IsObject(O) || (O = GuardToWrapForValidIterator(O)) === null) {
    return callFunction(
      CallWrapForValidIteratorMethodIfWrapped,
      this,
      value,
      "WrapForValidIteratorReturn"
    );
  }
  const iterated = UnsafeGetReservedSlot(O, ITERATED_SLOT);

  // Step 3.
  // Inline call to IteratorClose.
  const iterator = iterated.iterator;
  const returnMethod = iterator.return;
  if (!IsNullOrUndefined(returnMethod)) {
    let innerResult = callContentFunction(returnMethod, iterator);
    if (!IsObject(innerResult)) {
      ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, innerResult));
    }
  }
  // Step 4.
  return {
    done: true,
    value,
  };
}

/* Iterator Helpers proposal 2.1.3.3.1.1.3 */
function WrapForValidIteratorThrow(value) {
  // Step 1-2.
  let O = this;
  if (!IsObject(O) || (O = GuardToWrapForValidIterator(O)) === null) {
    return callFunction(
      CallWrapForValidIteratorMethodIfWrapped,
      this,
      value,
      "WrapForValidIteratorThrow"
    );
  }
  const iterated = UnsafeGetReservedSlot(O, ITERATED_SLOT);
  // Step 3.
  const iterator = iterated.iterator;
  // Step 4.
  const throwMethod = iterator.throw;
  // Step 5.
  if (IsNullOrUndefined(throwMethod)) {
    throw value;
  }
  // Step 6.
  return callContentFunction(throwMethod, iterator, value);
}

/* Iterator Helper object prototype methods. */
function IteratorHelperNext(value) {
  let O = this;
  if (!IsObject(O) || (O = GuardToIteratorHelper(O)) === null) {
    return callFunction(
      CallIteratorHelperMethodIfWrapped,
      this,
      value,
      "IteratorHelperNext"
    );
  }
  const generator = UnsafeGetReservedSlot(O, ITERATOR_HELPER_GENERATOR_SLOT);
  return callContentFunction(GeneratorNext, generator, value);
}

function IteratorHelperReturn(value) {
  let O = this;
  if (!IsObject(O) || (O = GuardToIteratorHelper(O)) === null) {
    return callFunction(
      CallIteratorHelperMethodIfWrapped,
      this,
      value,
      "IteratorHelperReturn"
    );
  }
  const generator = UnsafeGetReservedSlot(O, ITERATOR_HELPER_GENERATOR_SLOT);
  return callContentFunction(GeneratorReturn, generator, value);
}

function IteratorHelperThrow(value) {
  let O = this;
  if (!IsObject(O) || (O = GuardToIteratorHelper(O)) === null) {
    return callFunction(
      CallIteratorHelperMethodIfWrapped,
      this,
      value,
      "IteratorHelperThrow"
    );
  }
  const generator = UnsafeGetReservedSlot(O, ITERATOR_HELPER_GENERATOR_SLOT);
  return callContentFunction(GeneratorThrow, generator, value);
}

// Lazy %Iterator.prototype% methods
// Iterator Helpers proposal 2.1.5.2-2.1.5.7
//
// In order to match the semantics of the built-in generator objects used in
// the proposal, we use a reserved slot on the IteratorHelper objects to store
// a regular generator that is called from the %IteratorHelper.prototype%
// methods.
//
// Each of the lazy methods is divided into a prelude and a body, with the
// eager prelude steps being contained in the corresponding IteratorX method
// and the lazy body steps inside the IteratorXGenerator generator functions.
//
// Each prelude method initializes and returns a new IteratorHelper object.
// As part of this initialization process, the appropriate generator function
// is called, followed by GeneratorNext being called on returned generator
// instance in order to move it to it's first yield point. This is done so that
// if the return or throw methods are called on the IteratorHelper before next
// has been called, we can catch them in the try and use the finally block to
// close the source iterator.
//
// The needClose flag is used to track when the source iterator should be closed
// following an exception being thrown within the generator, corresponding to
// whether or not the abrupt completions in the spec are being passed back to
// the caller (when needClose is false) or handled with IfAbruptCloseIterator
// (when needClose is true).

/* Iterator Helpers proposal 2.1.5.2 Prelude */
function IteratorMap(mapper) {
  // Step 1.
  const iterated = GetIteratorDirect(this);

  // Step 2.
  if (!IsCallable(mapper)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, mapper));
  }

  const iteratorHelper = NewIteratorHelper();
  const generator = IteratorMapGenerator(iterated, mapper);
  callContentFunction(GeneratorNext, generator);
  UnsafeSetReservedSlot(
    iteratorHelper,
    ITERATOR_HELPER_GENERATOR_SLOT,
    generator
  );
  return iteratorHelper;
}

/* Iterator Helpers proposal 2.1.5.2 Body */
function* IteratorMapGenerator(iterated, mapper) {
  // Step 1.
  let lastValue;
  // Step 2.
  let needClose = true;
  try {
    yield;
    needClose = false;

    for (
      let next = IteratorStep(iterated, lastValue);
      next;
      next = IteratorStep(iterated, lastValue)
    ) {
      // Step c.
      const value = next.value;

      // Steps d-g.
      needClose = true;
      lastValue = yield callContentFunction(mapper, undefined, value);
      needClose = false;
    }
  } finally {
    if (needClose) {
      IteratorClose(iterated);
    }
  }
}

/* Iterator Helpers proposal 2.1.5.3 Prelude */
function IteratorFilter(filterer) {
  // Step 1.
  const iterated = GetIteratorDirect(this);

  // Step 2.
  if (!IsCallable(filterer)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, filterer));
  }

  const iteratorHelper = NewIteratorHelper();
  const generator = IteratorFilterGenerator(iterated, filterer);
  callContentFunction(GeneratorNext, generator);
  UnsafeSetReservedSlot(
    iteratorHelper,
    ITERATOR_HELPER_GENERATOR_SLOT,
    generator
  );
  return iteratorHelper;
}

/* Iterator Helpers proposal 2.1.5.3 Body */
function* IteratorFilterGenerator(iterated, filterer) {
  // Step 1.
  let lastValue;
  // Step 2.
  let needClose = true;
  try {
    yield;
    needClose = false;

    for (
      let next = IteratorStep(iterated, lastValue);
      next;
      next = IteratorStep(iterated, lastValue)
    ) {
      // Step c.
      const value = next.value;

      // Steps d-g.
      needClose = true;
      if (callContentFunction(filterer, undefined, value)) {
        lastValue = yield value;
      }
      needClose = false;
    }
  } finally {
    if (needClose) {
      IteratorClose(iterated);
    }
  }
}

/* Iterator Helpers proposal 2.1.5.4 Prelude */
function IteratorTake(limit) {
  // Step 1.
  const iterated = GetIteratorDirect(this);

  // Step 2.
  const remaining = ToInteger(limit);
  // Step 3.
  if (remaining < 0) {
    ThrowRangeError(JSMSG_NEGATIVE_LIMIT);
  }

  const iteratorHelper = NewIteratorHelper();
  const generator = IteratorTakeGenerator(iterated, remaining);
  callContentFunction(GeneratorNext, generator);
  UnsafeSetReservedSlot(
    iteratorHelper,
    ITERATOR_HELPER_GENERATOR_SLOT,
    generator
  );
  return iteratorHelper;
}

/* Iterator Helpers proposal 2.1.5.4 Body */
function* IteratorTakeGenerator(iterated, remaining) {
  // Step 1.
  let lastValue;
  // Step 2.
  let needClose = true;
  try {
    yield;
    needClose = false;

    for (; remaining > 0; remaining--) {
      const next = IteratorStep(iterated, lastValue);
      if (!next) {
        return;
      }

      const value = next.value;
      needClose = true;
      lastValue = yield value;
      needClose = false;
    }
  } finally {
    if (needClose) {
      IteratorClose(iterated);
    }
  }

  IteratorClose(iterated);
}

/* Iterator Helpers proposal 2.1.5.5 Prelude */
function IteratorDrop(limit) {
  // Step 1.
  const iterated = GetIteratorDirect(this);

  // Step 2.
  const remaining = ToInteger(limit);
  // Step 3.
  if (remaining < 0) {
    ThrowRangeError(JSMSG_NEGATIVE_LIMIT);
  }

  const iteratorHelper = NewIteratorHelper();
  const generator = IteratorDropGenerator(iterated, remaining);
  callContentFunction(GeneratorNext, generator);
  UnsafeSetReservedSlot(
    iteratorHelper,
    ITERATOR_HELPER_GENERATOR_SLOT,
    generator
  );
  return iteratorHelper;
}

/* Iterator Helpers proposal 2.1.5.5 Body */
function* IteratorDropGenerator(iterated, remaining) {
  let needClose = true;
  try {
    yield;
    needClose = false;

    // Step 1.
    for (; remaining > 0; remaining--) {
      if (!IteratorStep(iterated)) {
        return;
      }
    }

    // Step 2.
    let lastValue;
    // Step 3.
    for (
      let next = IteratorStep(iterated, lastValue);
      next;
      next = IteratorStep(iterated, lastValue)
    ) {
      // Steps c-d.
      const value = next.value;

      needClose = true;
      lastValue = yield value;
      needClose = false;
    }
  } finally {
    if (needClose) {
      IteratorClose(iterated);
    }
  }
}

/* Iterator Helpers proposal 2.1.5.6 Prelude */
function IteratorAsIndexedPairs() {
  // Step 1.
  const iterated = GetIteratorDirect(this);

  const iteratorHelper = NewIteratorHelper();
  const generator = IteratorAsIndexedPairsGenerator(iterated);
  callContentFunction(GeneratorNext, generator);
  UnsafeSetReservedSlot(
    iteratorHelper,
    ITERATOR_HELPER_GENERATOR_SLOT,
    generator
  );
  return iteratorHelper;
}

/* Iterator Helpers proposal 2.1.5.6 Body */
function* IteratorAsIndexedPairsGenerator(iterated) {
  // Step 2.
  let lastValue;
  // Step 3.
  let needClose = true;
  try {
    yield;
    needClose = false;

    for (
      let next = IteratorStep(iterated, lastValue), index = 0;
      next;
      next = IteratorStep(iterated, lastValue), index++
    ) {
      // Steps c-d.
      const value = next.value;

      needClose = true;
      lastValue = yield [index, value];
      needClose = false;
    }
  } finally {
    if (needClose) {
      IteratorClose(iterated);
    }
  }
}

/* Iterator Helpers proposal 2.1.5.7 Prelude */
function IteratorFlatMap(mapper) {
  // Step 1.
  const iterated = GetIteratorDirect(this);

  // Step 2.
  if (!IsCallable(mapper)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, mapper));
  }

  const iteratorHelper = NewIteratorHelper();
  const generator = IteratorFlatMapGenerator(iterated, mapper);
  callContentFunction(GeneratorNext, generator);
  UnsafeSetReservedSlot(
    iteratorHelper,
    ITERATOR_HELPER_GENERATOR_SLOT,
    generator
  );
  return iteratorHelper;
}

/* Iterator Helpers proposal 2.1.5.7 Body */
function* IteratorFlatMapGenerator(iterated, mapper) {
  // Step 1.
  let needClose = true;
  try {
    yield;
    needClose = false;

    for (
      let next = IteratorStep(iterated);
      next;
      next = IteratorStep(iterated)
    ) {
      // Step c.
      const value = next.value;

      needClose = true;
      // Step d.
      const mapped = callContentFunction(mapper, undefined, value);
      // Steps f-i.
      for (const innerValue of allowContentIter(mapped)) {
        yield innerValue;
      }
      needClose = false;
    }
  } finally {
    if (needClose) {
      IteratorClose(iterated);
    }
  }
}

/* Iterator Helpers proposal 2.1.5.8 */
function IteratorReduce(reducer /*, initialValue*/) {
  // Step 1.
  const iterated = GetIteratorDirectWrapper(this);

  // Step 2.
  if (!IsCallable(reducer)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, reducer));
  }

  // Step 3.
  let accumulator;
  if (ArgumentsLength() === 1) {
    // Step a.
    const next = callContentFunction(iterated.next, iterated);
    if (!IsObject(next)) {
      ThrowTypeError(JSMSG_OBJECT_REQUIRED, DecompileArg(0, next));
    }
    // Step b.
    if (next.done) {
      ThrowTypeError(JSMSG_EMPTY_ITERATOR_REDUCE);
    }
    // Step c.
    accumulator = next.value;
  } else {
    // Step 4.
    accumulator = GetArgument(1);
  }

  // Step 5.
  for (const value of allowContentIter(iterated)) {
    accumulator = callContentFunction(reducer, undefined, accumulator, value);
  }
  return accumulator;
}

/* Iterator Helpers proposal 2.1.5.9 */
function IteratorToArray() {
  // Step 1.
  const iterated = {
    [GetBuiltinSymbol("iterator")]: () => this,
  };
  // Steps 2-3.
  return [...allowContentIter(iterated)];
}

/* Iterator Helpers proposal 2.1.5.10 */
function IteratorForEach(fn) {
  // Step 1.
  const iterated = GetIteratorDirectWrapper(this);

  // Step 2.
  if (!IsCallable(fn)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, fn));
  }

  // Step 3.
  for (const value of allowContentIter(iterated)) {
    callContentFunction(fn, undefined, value);
  }
}

/* Iterator Helpers proposal 2.1.5.11 */
function IteratorSome(fn) {
  // Step 1.
  const iterated = GetIteratorDirectWrapper(this);

  // Step 2.
  if (!IsCallable(fn)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, fn));
  }

  // Step 3.
  for (const value of allowContentIter(iterated)) {
    // Steps d-f.
    if (callContentFunction(fn, undefined, value)) {
      return true;
    }
  }
  // Step 3b.
  return false;
}

/* Iterator Helpers proposal 2.1.5.12 */
function IteratorEvery(fn) {
  // Step 1.
  const iterated = GetIteratorDirectWrapper(this);

  // Step 2.
  if (!IsCallable(fn)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, fn));
  }

  // Step 3.
  for (const value of allowContentIter(iterated)) {
    // Steps d-f.
    if (!callContentFunction(fn, undefined, value)) {
      return false;
    }
  }
  // Step 3b.
  return true;
}

/* Iterator Helpers proposal 2.1.5.13 */
function IteratorFind(fn) {
  // Step 1.
  const iterated = GetIteratorDirectWrapper(this);

  // Step 2.
  if (!IsCallable(fn)) {
    ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, fn));
  }

  // Step 3.
  for (const value of allowContentIter(iterated)) {
    // Steps d-f.
    if (callContentFunction(fn, undefined, value)) {
      return value;
    }
  }
}
