// ESM-compatible iterator helpers polyfill for Node.js 20
// Simple approach: define Iterator directly
import assert from "node:assert";

if (parseInt(process.versions.node) < 22) {

  // Create Iterator object
  const Iterator = global.Iterator = {};

  // Create Iterator.prototype
  Iterator.prototype = {
      [Symbol.iterator]() {
        return this;
      },
  };

  function iteratorImpl(next) {
    return Object.create(Iterator.prototype, {
      next: {value: next, writable: true, configurable: true},
    });
  }

  // Patch built-in iterators to inherit from Iterator.prototype
  // List of built-in iterators is derived from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator
  // (Segments Iterator is excluded, as we probably don't need it)
  for (const iterable of [Array(), new Set(), new Map(), "", "".matchAll(), Object.getPrototypeOf(function *(){}())]) {
    const iterator = iterable[Symbol.iterator]();
    assert(iterator !== undefined);
    const proto = Object.getPrototypeOf(iterator);
    const proto2 = Object.getPrototypeOf(proto);
    // proto2 is what we're overriding, make sure it's the expected Iterator prototype
    const ownProps = Object.getOwnPropertyNames(proto2).concat(Object.getOwnPropertySymbols(proto2));
    assert.deepStrictEqual(ownProps, [Symbol.iterator]);
    Object.setPrototypeOf(proto, Iterator.prototype);
  }

  // Iterator.from
  Iterator.from = function (iterable) {
    if (iterable == null) {
      throw new TypeError('Iterator.from requires an iterable');
    }
    let iterator;
    if (typeof iterable[Symbol.iterator] === 'function') {
      iterator = iterable[Symbol.iterator]();
    } else if (typeof iterable.next === 'function') {
      iterator = iterable;
    } else {
      throw new TypeError('Iterator.from requires an iterable or iterator');
    }

    // Create a wrapper that has both the original iterator methods and our prototype methods
    return iteratorImpl(iterator.next.bind(iterator));
  };

  // Iterator.prototype.map
  Iterator.prototype.map = function (fn) {
    const iterator = this;
    return iteratorImpl(() => {
      const result = iterator.next();
      if (result.done) return result;
      return {value: fn(result.value), done: false};
    });
  };

  // Iterator.prototype.filter
  Iterator.prototype.filter = function (fn) {
    const iterator = this;
    return iteratorImpl(() => {
      while (true) {
        const result = iterator.next();
        if (result.done) return result;
        if (fn(result.value)) {
          return result;
        }
      }
    });
  };

  // Iterator.prototype.take
  Iterator.prototype.take = function (n) {
    const iterator = this;
    let count = 0;
    return iteratorImpl(() => {
      if (count >= n) return {done: true};
      count++;
      return iterator.next();
    });
  };

  // Iterator.prototype.drop
  Iterator.prototype.drop = function (n) {
    const iterator = this;
    let count = 0;
    return iteratorImpl(() => {
      while (count < n) {
        const result = iterator.next();
        if (result.done) return result;
        count++;
      }
      return iterator.next();
    });
  };

  // Iterator.prototype.toArray
  Iterator.prototype.toArray = function () {
    const result = [];
    let next = this.next();
    while (!next.done) {
      result.push(next.value);
      next = this.next();
    }
    return result;
  };

 // Iterator.prototype.forEach
  Iterator.prototype.forEach = function (fn) {
    for (const value of this) {
      fn(value);
    }
  };

  // Iterator.prototype.some
  Iterator.prototype.some = function (fn) {
    for (const value of this) {
      if (fn(value)) return true;
    }
    return false;
  };

  // Iterator.prototype.every
  Iterator.prototype.every = function (fn) {
    for (const value of this) {
      if (!fn(value)) return false;
    }
    return true;
  };

  // Iterator.prototype.find
  Iterator.prototype.find = function (fn) {
    for (const value of this) {
      if (fn(value)) return value;
    }
    return undefined;
  };

  // Iterator.prototype.reduce
  Iterator.prototype.reduce = function (fn, initialValue) {
    let accumulator = initialValue;
    let first = true;

    for (const value of this) {
      if (first && initialValue === undefined) {
        accumulator = value;
        first = false;
      } else {
        accumulator = fn(accumulator, value);
        first = false;
      }
    }

    if (first && initialValue === undefined) {
      throw new TypeError('Reduce of empty iterator with no initial value');
    }

    return accumulator;
  };

  // Iterator.prototype.flatMap
  Iterator.prototype.flatMap = function (fn) {
    const iterator = this;
    let currentIterator = null;

    return iteratorImpl(() => {
      while (true) {
        if (currentIterator === null) {
          const result = iterator.next();
          if (result.done) return result;
          const mapped = fn(result.value);
          if (mapped && typeof mapped[Symbol.iterator] === 'function') {
            currentIterator = mapped[Symbol.iterator]();
          } else {
            return {value: mapped, done: false};
          }
        }

        const result = currentIterator.next();
        if (result.done) {
          currentIterator = null;
          continue;
        }
        return result;
      }
    });
  };

  // Iterator.concat
  Iterator.concat = function (...iterators) {
    let currentIndex = 0;
    let currentIterator = null;

    return iteratorImpl(() => {
      while (currentIndex < iterators.length) {
        if (currentIterator === null) {
          const iterable = iterators[currentIndex];
          if (iterable && typeof iterable[Symbol.iterator] === 'function') {
            currentIterator = iterable[Symbol.iterator]();
          } else {
            currentIndex++;
            continue;
          }
        }

        const result = currentIterator.next();
        if (result.done) {
          currentIterator = null;
          currentIndex++;
          continue;
        }
        return result;
      }
      return {done: true};
    });
  };

  // Iterator.zip
  Iterator.zip = function (...iterators) {
    const iteratorsArray = iterators.map(iter =>
        iter && typeof iter[Symbol.iterator] === 'function' ? iter[Symbol.iterator]() : null
    );

    return iteratorImpl(() => {
      const values = [];
      for (const iter of iteratorsArray) {
        if (iter === null) {
          return {done: true};
        }
        const result = iter.next();
        if (result.done) {
          return {done: true};
        }
        values.push(result.value);
      }
      return {value: values, done: false};
    });
  };

}
