// Copyright 2018-2026 the Deno authors. MIT license.
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

(function () {
const { core, primordials } = __bootstrap;
const {
  ArrayPrototypeIndexOf,
  ArrayPrototypeJoin,
  ArrayPrototypeMap,
  ArrayPrototypeFilter,
  ArrayPrototypePop,
  ArrayPrototypePush,
  ArrayPrototypeShift,
  ArrayPrototypeSlice,
  ArrayPrototypeSplice,
  ArrayPrototypeUnshift,
  Boolean,
  Error,
  ErrorCaptureStackTrace,
  ErrorPrototype,
  FunctionPrototypeApply,
  FunctionPrototypeBind,
  FunctionPrototypeCall,
  MathMin,
  NumberMAX_SAFE_INTEGER,
  ObjectCreate,
  ObjectDefineProperties,
  ObjectDefineProperty,
  ObjectEntries,
  ObjectGetPrototypeOf,
  ObjectPrototypeIsPrototypeOf,
  ObjectSetPrototypeOf,
  Promise,
  PromiseReject,
  PromiseResolve,
  ReflectOwnKeys,
  SafeArrayIterator,
  SafeMap,
  SafeSet,
  String,
  StringPrototypeSplit,
  Symbol,
  SymbolFor,
  SymbolAsyncIterator,
} = primordials;

const kRejection = SymbolFor("nodejs.rejection");
const kWatermarkData = SymbolFor("nodejs.watermarkData");
const kEvents = Symbol("kEvents");

const { inspect } = core.loadExtScript(
  "ext:deno_node/internal/util/inspect.mjs",
);
const { kEmptyObject } = core.loadExtScript(
  "ext:deno_node/internal/util.mjs",
);
const {
  AbortError,
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_THIS,
  ERR_UNHANDLED_ERROR,
} = core.loadExtScript("ext:deno_node/internal/errors.ts");

const lazyAsyncHooks = core.createLazyLoader("node:async_hooks");
const {
  validateAbortSignal,
  validateBoolean,
  validateFunction,
  validateInteger,
  validateNumber,
  validateObject,
  validateString,
} = core.loadExtScript("ext:deno_node/internal/validators.mjs");
const { spliceOne } = core.loadExtScript("ext:deno_node/_utils.ts");
const { nextTick } = core.loadExtScript("ext:deno_node/_process/process.ts");
const {
  eventTargetData,
  kResistStopImmediatePropagation,
} = core.loadExtScript("ext:deno_web/02_event.js");

const { addAbortListener } = core.loadExtScript(
  "ext:deno_node/internal/events/abort_listener.mjs",
);
const kFirstEventParam = Symbol("kFirstEventParam");
const kCapture = Symbol("kCapture");
const kErrorMonitor = Symbol("events.errorMonitor");
const kMaxEventTargetListeners = Symbol("events.maxEventTargetListeners");
const kMaxEventTargetListenersWarned = Symbol(
  "events.maxEventTargetListenersWarned",
);

/**
 * Creates a new `EventEmitter` instance.
 * @param {{ captureRejections?: boolean; }} [opts]
 * @returns {EventEmitter}
 */
function EventEmitter(opts) {
  FunctionPrototypeCall(EventEmitter.init, this, opts);
}
EventEmitter.on = on;
EventEmitter.once = once;
EventEmitter.getEventListeners = getEventListeners;
EventEmitter.setMaxListeners = setMaxListeners;
EventEmitter.getMaxListeners = getMaxListeners;
EventEmitter.listenerCount = listenerCount;
EventEmitter.addAbortListener = addAbortListener;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.usingDomains = false;

EventEmitter.captureRejectionSymbol = kRejection;
EventEmitter.errorMonitor = kErrorMonitor;
const captureRejectionSymbol = kRejection;
const errorMonitor = kErrorMonitor;

ObjectDefineProperty(EventEmitter, "captureRejections", {
  __proto__: null,
  get() {
    return EventEmitter.prototype[kCapture];
  },
  set(value) {
    validateBoolean(value, "EventEmitter.captureRejections");

    EventEmitter.prototype[kCapture] = value;
  },
  enumerable: true,
});

// The default for captureRejections is false
ObjectDefineProperty(EventEmitter.prototype, kCapture, {
  __proto__: null,
  value: false,
  writable: true,
  enumerable: false,
});

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
let defaultMaxListeners = 10;

function checkListener(listener) {
  validateFunction(listener, "listener");
}

ObjectDefineProperty(EventEmitter, "defaultMaxListeners", {
  __proto__: null,
  enumerable: true,
  get: function () {
    return defaultMaxListeners;
  },
  set: function (arg) {
    validateNumber(arg, "defaultMaxListeners", 0);
    defaultMaxListeners = arg;
  },
});

ObjectDefineProperties(EventEmitter, {
  __proto__: null,
  kMaxEventTargetListeners: {
    __proto__: null,
    value: kMaxEventTargetListeners,
    enumerable: false,
    configurable: false,
    writable: false,
  },
  kMaxEventTargetListenersWarned: {
    __proto__: null,
    value: kMaxEventTargetListenersWarned,
    enumerable: false,
    configurable: false,
    writable: false,
  },
});

/**
 * Sets the max listeners.
 * @param {number} n
 * @param {EventTarget[] | EventEmitter[]} [eventTargets]
 * @returns {void}
 */
function setMaxListeners(
  n = defaultMaxListeners,
  ...eventTargets
) {
  validateNumber(n, "setMaxListeners", 0);
  if (eventTargets.length === 0) {
    defaultMaxListeners = n;
  } else {
    for (let i = 0; i < eventTargets.length; i++) {
      const target = eventTargets[i];
      if (ObjectPrototypeIsPrototypeOf(EventTarget.prototype, target)) {
        target[kMaxEventTargetListeners] = n;
        target[kMaxEventTargetListenersWarned] = false;
      } else if (typeof target.setMaxListeners === "function") {
        target.setMaxListeners(n);
      } else {
        throw new ERR_INVALID_ARG_TYPE(
          "eventTargets",
          ["EventEmitter", "EventTarget"],
          target,
        );
      }
    }
  }
}

EventEmitter.init = function (opts) {
  if (
    this._events === undefined ||
    this._events === ObjectGetPrototypeOf(this)._events
  ) {
    this._events = ObjectCreate(null);
    this._eventsCount = 0;
  }

  this._maxListeners = this._maxListeners || undefined;

  if (opts?.captureRejections) {
    validateBoolean(opts.captureRejections, "options.captureRejections");
    this[kCapture] = Boolean(opts.captureRejections);
  } else {
    // Assigning the kCapture property directly saves an expensive
    // prototype lookup in a very sensitive hot path.
    this[kCapture] = EventEmitter.prototype[kCapture];
  }
};

function addCatch(that, promise, type, args) {
  if (!that[kCapture]) {
    return;
  }

  // Handle Promises/A+ spec, then could be a getter
  // that throws on second use.
  try {
    const then = promise.then;

    if (typeof then === "function") {
      FunctionPrototypeCall(then, promise, undefined, function (err) {
        // The callback is called with nextTick to avoid a follow-up
        // rejection from this promise.
        nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
      });
    }
  } catch (err) {
    that.emit("error", err);
  }
}

function emitUnhandledRejectionOrErr(ee, err, type, args) {
  if (typeof ee[kRejection] === "function") {
    ee[kRejection](err, type, ...new SafeArrayIterator(args));
  } else {
    // We have to disable the capture rejections mechanism, otherwise
    // we might end up in an infinite loop.
    const prev = ee[kCapture];

    // If the error handler throws, it is not catcheable and it
    // will end up in 'uncaughtException'. We restore the previous
    // value of kCapture in case the uncaughtException is present
    // and the exception is handled.
    try {
      ee[kCapture] = false;
      ee.emit("error", err);
    } finally {
      ee[kCapture] = prev;
    }
  }
}

/**
 * Increases the max listeners of the event emitter.
 * @param {number} n
 * @returns {EventEmitter}
 */
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
  validateNumber(n, "setMaxListeners", 0);
  this._maxListeners = n;
  return this;
};

/**
 * Returns the max listeners set.
 * @param {EventEmitter | EventTarget} emitterOrTarget
 * @returns {number}
 */
function getMaxListeners(emitterOrTarget) {
  if (typeof emitterOrTarget?.getMaxListeners === "function") {
    return _getMaxListeners(emitterOrTarget);
  } else if (
    typeof emitterOrTarget?.[kMaxEventTargetListeners] === "number"
  ) {
    return emitterOrTarget[kMaxEventTargetListeners] ?? defaultMaxListeners;
  } else if (
    ObjectPrototypeIsPrototypeOf(AbortSignal.prototype, emitterOrTarget)
  ) {
    return 0; // default for AbortController if not set by EventTarget prototype.
  } else if (
    ObjectPrototypeIsPrototypeOf(EventTarget.prototype, emitterOrTarget)
  ) {
    return defaultMaxListeners;
  }

  throw new ERR_INVALID_ARG_TYPE(
    "emitter",
    ["EventEmitter", "EventTarget"],
    emitterOrTarget,
  );
}

function _getMaxListeners(that) {
  if (that._maxListeners === undefined) {
    return EventEmitter.defaultMaxListeners;
  }
  return that._maxListeners;
}

/**
 * Returns the current max listener value for the event emitter.
 * @returns {number}
 */
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
  return _getMaxListeners(this);
};

// Returns the length and line number of the first sequence of `a` that fully
// appears in `b` with a length of at least 4.
function identicalSequenceRange(a, b) {
  for (let i = 0; i < a.length - 3; i++) {
    // Find the first entry of b that matches the current entry of a.
    const pos = ArrayPrototypeIndexOf(b, a[i]);
    if (pos !== -1) {
      const rest = b.length - pos;
      if (rest > 3) {
        let len = 1;
        const maxLen = MathMin(a.length - i, rest);
        // Count the number of consecutive entries.
        while (maxLen > len && a[i + len] === b[pos + len]) {
          len++;
        }
        if (len > 3) {
          return [len, i];
        }
      }
    }
  }

  return [0, 0];
}

// deno-lint-ignore no-unused-vars
function enhanceStackTrace(err, own) {
  let ctorInfo = "";
  try {
    const { name } = this.constructor;
    if (name !== "EventEmitter") {
      ctorInfo = ` on ${name} instance`;
    }
  } catch {
    // pass
  }
  const sep = `\nEmitted 'error' event${ctorInfo} at:\n`;

  const errStack = ArrayPrototypeSlice(
    StringPrototypeSplit(err.stack, "\n"),
    1,
  );
  const ownStack = ArrayPrototypeSlice(
    StringPrototypeSplit(own.stack, "\n"),
    1,
  );

  const { 0: len, 1: off } = identicalSequenceRange(ownStack, errStack);
  if (len > 0) {
    ArrayPrototypeSplice(
      ownStack,
      off + 1,
      len - 2,
      "    [... lines matching original stack trace ...]",
    );
  }

  return err.stack + sep + ArrayPrototypeJoin(ownStack, "\n");
}

/**
 * Synchronously calls each of the listeners registered
 * for the event.
 * @param {string | symbol} type
 * @param {...any} [args]
 * @returns {boolean}
 */
EventEmitter.prototype.emit = function emit(type, ...args) {
  let doError = type === "error";

  const events = this._events;
  if (events !== undefined) {
    if (doError && events[kErrorMonitor] !== undefined) {
      this.emit(kErrorMonitor, ...new SafeArrayIterator(args));
    }
    doError = doError && events.error === undefined;
  } else if (!doError) {
    return false;
  }

  // If there is no 'error' event listener then throw.
  if (doError) {
    let er;
    if (args.length > 0) {
      er = args[0];
    }
    if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, er)) {
      try {
        const capture = {};
        ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
        // ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
        //   value: enhanceStackTrace.bind(this, er, capture),
        //   configurable: true
        // });
      } catch {
        // pass
      }

      // Note: The comments on the `throw` lines are intentional, they show
      // up in Node's output if this results in an unhandled exception.
      throw er; // Unhandled 'error' event
    }

    let stringifiedEr;
    try {
      stringifiedEr = inspect(er);
    } catch {
      stringifiedEr = er;
    }

    // At least give some kind of context to the user
    const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
    err.context = er;
    throw err; // Unhandled 'error' event
  }

  const handler = events[type];

  if (handler === undefined) {
    return false;
  }

  if (typeof handler === "function") {
    const result = FunctionPrototypeApply(handler, this, args);

    // We check if result is undefined first because that
    // is the most common case so we do not pay any perf
    // penalty
    if (result !== undefined && result !== null) {
      addCatch(this, result, type, args);
    }
  } else {
    const len = handler.length;
    const listeners = arrayClone(handler);
    for (let i = 0; i < len; ++i) {
      const result = FunctionPrototypeApply(listeners[i], this, args);

      // We check if result is undefined first because that
      // is the most common case so we do not pay any perf
      // penalty.
      // This code is duplicated because extracting it away
      // would make it non-inlineable.
      if (result !== undefined && result !== null) {
        addCatch(this, result, type, args);
      }
    }
  }

  return true;
};

function _addListener(target, type, listener, prepend) {
  let m;
  let events;
  let existing;

  checkListener(listener);

  events = target._events;
  if (events === undefined) {
    events = target._events = ObjectCreate(null);
    target._eventsCount = 0;
  } else {
    // To avoid recursion in the case that type === "newListener"! Before
    // adding it to the listeners, first emit "newListener".
    if (events.newListener !== undefined) {
      target.emit("newListener", type, listener.listener ?? listener);

      // Re-assign `events` because a newListener handler could have caused the
      // this._events to be assigned to a new object
      events = target._events;
    }
    existing = events[type];
  }

  if (existing === undefined) {
    // Optimize the case of one listener. Don't need the extra array object.
    events[type] = listener;
    ++target._eventsCount;
  } else {
    if (typeof existing === "function") {
      // Adding the second element, need to change to array.
      existing = events[type] = prepend
        ? [listener, existing]
        : [existing, listener];
      // If we've already got an array, just append.
    } else if (prepend) {
      ArrayPrototypeUnshift(existing, listener);
    } else {
      ArrayPrototypePush(existing, listener);
    }

    // Check for listener leak
    m = _getMaxListeners(target);
    if (m > 0 && existing.length > m && !existing.warned) {
      existing.warned = true;
      // No error code for this since it is a Warning
      // eslint-disable-next-line no-restricted-syntax
      const w = new Error(
        "Possible EventEmitter memory leak detected. " +
          `${existing.length} ${String(type)} listeners ` +
          `added to ${inspect(target, { depth: -1 })}. ` +
          `MaxListeners is ${m}.`,
      );
      w.name = "MaxListenersExceededWarning";
      w.emitter = target;
      w.type = type;
      w.count = existing.length;
      // deno-lint-ignore no-process-global
      process.emitWarning(w);
    }
  }

  return target;
}

/**
 * Adds a listener to the event emitter.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener, false);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

/**
 * Adds the `listener` function to the beginning of
 * the listeners array.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.prependListener = function prependListener(
  type,
  listener,
) {
  return _addListener(this, type, listener, true);
};

function onceWrapper() {
  if (!this.fired) {
    this.target.removeListener(this.type, this.wrapFn);
    this.fired = true;
    if (arguments.length === 0) {
      return FunctionPrototypeCall(this.listener, this.target);
    }
    return FunctionPrototypeApply(this.listener, this.target, arguments);
  }
}

function _onceWrap(target, type, listener) {
  const state = { fired: false, wrapFn: undefined, target, type, listener };
  const wrapped = FunctionPrototypeBind(onceWrapper, state);
  wrapped.listener = listener;
  state.wrapFn = wrapped;
  return wrapped;
}

/**
 * Adds a one-time `listener` function to the event emitter.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.once = function once(type, listener) {
  checkListener(listener);

  this.on(type, _onceWrap(this, type, listener));
  return this;
};

/**
 * Adds a one-time `listener` function to the beginning of
 * the listeners array.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.prependOnceListener = function prependOnceListener(
  type,
  listener,
) {
  checkListener(listener);

  this.prependListener(type, _onceWrap(this, type, listener));
  return this;
};

/**
 * Removes the specified `listener` from the listeners array.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.removeListener = function removeListener(
  type,
  listener,
) {
  checkListener(listener);

  const events = this._events;
  if (events === undefined) {
    return this;
  }

  const list = events[type];
  if (list === undefined) {
    return this;
  }

  if (list === listener || list.listener === listener) {
    if (--this._eventsCount === 0) {
      this._events = ObjectCreate(null);
    } else {
      delete events[type];
      if (events.removeListener) {
        this.emit("removeListener", type, list.listener || listener);
      }
    }
  } else if (typeof list !== "function") {
    let position = -1;

    for (let i = list.length - 1; i >= 0; i--) {
      if (list[i] === listener || list[i].listener === listener) {
        position = i;
        break;
      }
    }

    if (position < 0) {
      return this;
    }

    if (position === 0) {
      ArrayPrototypeShift(list);
    } else {
      spliceOne(list, position);
    }

    if (list.length === 1) {
      events[type] = list[0];
    }

    if (events.removeListener !== undefined) {
      this.emit("removeListener", type, listener);
    }
  }

  return this;
};

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;

/**
 * Removes all listeners from the event emitter. (Only
 * removes listeners for a specific event name if specified
 * as `type`).
 * @param {string | symbol} [type]
 * @returns {EventEmitter}
 */
EventEmitter.prototype.removeAllListeners = function removeAllListeners(
  type,
) {
  const events = this._events;
  if (events === undefined) {
    return this;
  }

  // Not listening for removeListener, no need to emit
  if (events.removeListener === undefined) {
    if (arguments.length === 0) {
      this._events = ObjectCreate(null);
      this._eventsCount = 0;
    } else if (events[type] !== undefined) {
      if (--this._eventsCount === 0) {
        this._events = ObjectCreate(null);
      } else {
        delete events[type];
      }
    }
    return this;
  }

  // Emit removeListener for all listeners on all events
  if (arguments.length === 0) {
    for (const key of new SafeArrayIterator(ReflectOwnKeys(events))) {
      if (key === "removeListener") continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners("removeListener");
    this._events = ObjectCreate(null);
    this._eventsCount = 0;
    return this;
  }

  const listeners = events[type];

  if (typeof listeners === "function") {
    this.removeListener(type, listeners);
  } else if (listeners !== undefined) {
    // LIFO order
    for (let i = listeners.length - 1; i >= 0; i--) {
      this.removeListener(type, listeners[i]);
    }
  }

  return this;
};

function _listeners(target, type, unwrap) {
  const events = target._events;

  if (events === undefined) {
    return [];
  }

  const evlistener = events[type];
  if (evlistener === undefined) {
    return [];
  }

  if (typeof evlistener === "function") {
    return unwrap ? [evlistener.listener || evlistener] : [evlistener];
  }

  return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener);
}

/**
 * Returns a copy of the array of listeners for the event name
 * specified as `type`.
 * @param {string | symbol} type
 * @returns {Function[]}
 */
EventEmitter.prototype.listeners = function listeners(type) {
  return _listeners(this, type, true);
};

/**
 * Returns a copy of the array of listeners and wrappers for
 * the event name specified as `type`.
 * @param {string | symbol} type
 * @returns {Function[]}
 */
EventEmitter.prototype.rawListeners = function rawListeners(type) {
  return _listeners(this, type, false);
};

/**
 * Returns the number of listeners listening to event name
 * specified as `type`.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {number}
 */
const _listenerCount = function listenerCount(type, listener) {
  const events = this._events;

  if (events !== undefined) {
    const evlistener = events[type];

    if (typeof evlistener === "function") {
      if (listener != null) {
        return listener === evlistener || listener === evlistener.listener
          ? 1
          : 0;
      }
      return 1;
    } else if (evlistener !== undefined) {
      if (listener != null) {
        let matching = 0;

        for (let i = 0, l = evlistener.length; i < l; i++) {
          if (
            evlistener[i] === listener || evlistener[i].listener === listener
          ) {
            matching++;
          }
        }

        return matching;
      }
      return evlistener.length;
    }
  }

  return 0;
};

EventEmitter.prototype.listenerCount = _listenerCount;

/**
 * Returns the number of listeners listening to the event name
 * specified as `type`.
 * @deprecated since v3.2.0
 * @param {EventEmitter} emitter
 * @param {string | symbol} type
 * @returns {number}
 */
function listenerCount(emitter, type) {
  if (typeof emitter.listenerCount === "function") {
    return emitter.listenerCount(type);
  }
  if (ObjectPrototypeIsPrototypeOf(EventTarget.prototype, emitter)) {
    return getEventListeners(emitter, type).length;
  }
  return FunctionPrototypeCall(_listenerCount, emitter, type);
}

/**
 * Returns an array listing the events for which
 * the emitter has registered listeners.
 * @returns {any[]}
 */
EventEmitter.prototype.eventNames = function eventNames() {
  return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};

function arrayClone(arr) {
  // At least since V8 8.3, this implementation is faster than the previous
  // which always used a simple for-loop
  switch (arr.length) {
    case 2:
      return [arr[0], arr[1]];
    case 3:
      return [arr[0], arr[1], arr[2]];
    case 4:
      return [arr[0], arr[1], arr[2], arr[3]];
    case 5:
      return [arr[0], arr[1], arr[2], arr[3], arr[4]];
    case 6:
      return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];
  }
  return ArrayPrototypeSlice(arr);
}

function unwrapListeners(arr) {
  const ret = arrayClone(arr);
  for (let i = 0; i < ret.length; ++i) {
    const orig = ret[i].listener;
    if (typeof orig === "function") {
      ret[i] = orig;
    }
  }
  return ret;
}

/**
 * Returns a copy of the array of listeners for the event name
 * specified as `type`.
 * @param {EventEmitter | EventTarget} emitterOrTarget
 * @param {string | symbol} type
 * @returns {Function[]}
 */
function getEventListeners(emitterOrTarget, type) {
  // First check if EventEmitter
  if (typeof emitterOrTarget.listeners === "function") {
    return emitterOrTarget.listeners(type);
  }
  if (ObjectPrototypeIsPrototypeOf(EventTarget.prototype, emitterOrTarget)) {
    return emitterOrTarget[eventTargetData]?.listeners?.[type]?.map((
      listener,
    ) => listener.callback) || [];
  }
  throw new ERR_INVALID_ARG_TYPE(
    "emitter",
    ["EventEmitter", "EventTarget"],
    emitterOrTarget,
  );
}

/**
 * Creates a `Promise` that is fulfilled when the emitter
 * emits the given event.
 * @param {EventEmitter} emitter
 * @param {string} name
 * @param {{ signal: AbortSignal; }} [options]
 * @returns {Promise}
 */
// deno-lint-ignore require-await
async function once(emitter, name, options = kEmptyObject) {
  validateObject(options, "options");
  const signal = options?.signal;
  validateAbortSignal(signal, "options.signal");
  if (signal?.aborted) {
    throw new AbortError();
  }

  if (signal) {
    // Patches [kEvents] field of AbortSignal to simulate Node.js EventTarget
    // This is necessary to pass `paralle/test-events-once.js` test
    // TODO(kt3k): This can be removed if events.getEventListeners() is used
    // instead of `signal[kEvents]` in upstream.
    ObjectDefineProperty(signal, kEvents, kEventsGetter);
  }

  return new Promise((resolve, reject) => {
    const errorListener = (err) => {
      emitter.removeListener(name, resolver);
      if (signal != null) {
        eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
      }
      reject(err);
    };
    const resolver = (...args) => {
      if (typeof emitter.removeListener === "function") {
        emitter.removeListener("error", errorListener);
      }
      if (signal != null) {
        eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
      }
      resolve(args);
    };
    eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
    if (name !== "error" && typeof emitter.once === "function") {
      emitter.once("error", errorListener);
    }
    function abortListener() {
      eventTargetAgnosticRemoveListener(emitter, name, resolver);
      eventTargetAgnosticRemoveListener(emitter, "error", errorListener);
      reject(new AbortError());
    }
    if (signal != null) {
      eventTargetAgnosticAddListener(
        signal,
        "abort",
        abortListener,
        { once: true, [kResistStopImmediatePropagation]: true },
      );
    }
  });
}

const AsyncIteratorPrototype = ObjectGetPrototypeOf(
  ObjectGetPrototypeOf(async function* () {}).prototype,
);

function createIterResult(value, done) {
  return { value, done };
}

function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) {
  if (typeof emitter.removeListener === "function") {
    emitter.removeListener(name, listener);
  } else if (typeof emitter.removeEventListener === "function") {
    emitter.removeEventListener(name, listener, flags);
  } else {
    throw new ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter);
  }
}

function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
  if (typeof emitter.on === "function") {
    if (flags?.once) {
      emitter.once(name, listener);
    } else {
      emitter.on(name, listener);
    }
  } else if (typeof emitter.addEventListener === "function") {
    // EventTarget does not have `error` event semantics like Node
    // EventEmitters, we do not listen to `error` events here.
    emitter.addEventListener(name, listener, flags);
  } else {
    throw new ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter);
  }
}

const kEventsGetter = {
  get() {
    const data = this[eventTargetData];
    if (!data) {
      return new SafeMap();
    }
    return new SafeMap(
      ArrayPrototypeMap(
        ArrayPrototypeFilter(
          ObjectEntries(data.listeners),
          (entry) => entry[1].length > 0,
        ),
        (entry) => [entry[0], new SafeSet(entry[1])],
      ),
    );
  },
};

/**
 * Returns an `AsyncIterator` that iterates `event` events.
 * @param {EventEmitter} emitter
 * @param {string | symbol} event
 * @param {{ signal: AbortSignal; }} [options]
 * @returns {AsyncIterator}
 */
function on(emitter, event, options = kEmptyObject) {
  validateObject(options, "options");
  const signal = options?.signal;
  validateAbortSignal(signal, "options.signal");
  if (signal?.aborted) {
    throw new AbortError();
  }

  if (signal) {
    // Patches [kEvents] field of AbortSignal to simulate Node.js EventTarget
    // This is necessary to pass `paralle/test-events-on-async-iterator.js` test
    // TODO(kt3k): This can be removed if events.getEventListeners() is used
    // instead of `signal[kEvents]` in upstream.
    ObjectDefineProperty(signal, kEvents, kEventsGetter);
  }

  // Support both highWaterMark and highWatermark for backward compatibility
  const highWatermark = options.highWaterMark ?? options.highWatermark ??
    NumberMAX_SAFE_INTEGER;
  validateInteger(highWatermark, "options.highWaterMark", 1);
  const lowWatermark = options.lowWaterMark ?? options.lowWatermark ?? 1;
  validateInteger(lowWatermark, "options.lowWaterMark", 1);

  const unconsumedEvents = [];
  const unconsumedPromises = [];
  let paused = false;
  let error = null;
  let finished = false;
  let size = 0;

  const iterator = ObjectSetPrototypeOf({
    next() {
      // First, we consume all unread events
      if (size) {
        const value = ArrayPrototypeShift(unconsumedEvents);
        size--;
        if (paused && size < lowWatermark) {
          emitter.resume();
          paused = false;
        }
        return PromiseResolve(createIterResult(value, false));
      }

      // Then we error, if an error happened
      // This happens one time if at all, because after 'error'
      // we stop listening
      if (error) {
        const p = PromiseReject(error);
        // Only the first element errors
        error = null;
        return p;
      }

      // If the iterator is finished, resolve to done
      if (finished) return closeHandler();

      // Wait until an event happens
      return new Promise(function (resolve, reject) {
        ArrayPrototypePush(unconsumedPromises, { resolve, reject });
      });
    },

    return() {
      return closeHandler();
    },

    throw(err) {
      if (!err || !ObjectPrototypeIsPrototypeOf(ErrorPrototype, err)) {
        throw new ERR_INVALID_ARG_TYPE(
          "EventEmitter.AsyncIterator",
          "Error",
          err,
        );
      }
      errorHandler(err);
    },

    [SymbolAsyncIterator]() {
      return this;
    },

    [kWatermarkData]: {
      get size() {
        return size;
      },
      get low() {
        return lowWatermark;
      },
      get high() {
        return highWatermark;
      },
      get isPaused() {
        return paused;
      },
    },
  }, AsyncIteratorPrototype);

  const { addEventListener, removeAll } = listenersController();
  addEventListener(
    emitter,
    event,
    options[kFirstEventParam] ? eventHandler : function (...args) {
      return eventHandler(args);
    },
  );
  if (event !== "error" && typeof emitter.on === "function") {
    addEventListener(emitter, "error", errorHandler);
  }

  const closeEvents = options?.close;
  if (closeEvents?.length) {
    for (let i = 0; i < closeEvents.length; i++) {
      addEventListener(emitter, closeEvents[i], closeHandler);
    }
  }

  if (signal) {
    addEventListener(signal, "abort", abortListener, { once: true });
  }

  return iterator;

  function abortListener() {
    errorHandler(new AbortError());
  }

  function eventHandler(value) {
    if (unconsumedPromises.length === 0) {
      size++;
      if (!paused && size > highWatermark) {
        paused = true;
        emitter.pause();
      }
      ArrayPrototypePush(unconsumedEvents, value);
    } else {
      ArrayPrototypeShift(unconsumedPromises).resolve(
        createIterResult(value, false),
      );
    }
  }

  function errorHandler(err) {
    if (unconsumedPromises.length === 0) {
      error = err;
    } else {
      ArrayPrototypeShift(unconsumedPromises).reject(err);
    }

    closeHandler();
  }

  function closeHandler() {
    removeAll();
    finished = true;
    paused = false;
    const doneResult = createIterResult(undefined, true);
    while (unconsumedPromises.length > 0) {
      ArrayPrototypeShift(unconsumedPromises).resolve(doneResult);
    }

    return PromiseResolve(doneResult);
  }
}

function listenersController() {
  const listeners = [];

  return {
    addEventListener(emitter, event, handler, flags) {
      eventTargetAgnosticAddListener(emitter, event, handler, flags);
      ArrayPrototypePush(listeners, [emitter, event, handler, flags]);
    },
    removeAll() {
      while (listeners.length > 0) {
        const entry = ArrayPrototypePop(listeners);
        eventTargetAgnosticRemoveListener(entry[0], entry[1], entry[2]);
      }
    },
  };
}

const kAsyncResource = Symbol("kAsyncResource");
const kEventEmitter = Symbol("kEventEmitter");

// EventEmitterAsyncResource and its helper class are defined lazily to avoid
// eagerly loading node:async_hooks (which provides AsyncResource).
let _EventEmitterAsyncResource;
function getEventEmitterAsyncResource() {
  if (_EventEmitterAsyncResource) return _EventEmitterAsyncResource;
  const { AsyncResource } = lazyAsyncHooks();

  class EventEmitterReferencingAsyncResource extends AsyncResource {
    constructor(ee, type, options) {
      super(type, options);
      this[kEventEmitter] = ee;
    }

    get eventEmitter() {
      if (this[kEventEmitter] === undefined) {
        throw new ERR_INVALID_THIS("EventEmitterReferencingAsyncResource");
      }
      return this[kEventEmitter];
    }
  }

  _EventEmitterAsyncResource = class EventEmitterAsyncResource
    extends EventEmitter {
    constructor(options = undefined) {
      let name;
      if (typeof options === "string") {
        name = options;
        options = undefined;
      } else {
        if (new.target === _EventEmitterAsyncResource) {
          validateString(options?.name, "options.name");
        }
        name = options?.name || new.target.name;
      }
      super(options);

      this[kAsyncResource] = new EventEmitterReferencingAsyncResource(
        this,
        name,
        options,
      );
    }

    emit(event, ...args) {
      if (this[kAsyncResource] === undefined) {
        throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
      }
      const { asyncResource } = this;
      ArrayPrototypeUnshift(args, super.emit, this, event);
      return FunctionPrototypeApply(
        asyncResource.runInAsyncScope,
        asyncResource,
        args,
      );
    }

    emitDestroy() {
      if (this[kAsyncResource] === undefined) {
        throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
      }
      this.asyncResource.emitDestroy();
    }

    get asyncId() {
      if (this[kAsyncResource] === undefined) {
        throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
      }
      return this.asyncResource.asyncId();
    }

    get triggerAsyncId() {
      if (this[kAsyncResource] === undefined) {
        throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
      }
      return this.asyncResource.triggerAsyncId();
    }

    get asyncResource() {
      if (this[kAsyncResource] === undefined) {
        throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
      }
      return this[kAsyncResource];
    }
  };

  return _EventEmitterAsyncResource;
}

ObjectDefineProperty(EventEmitter, "EventEmitterAsyncResource", {
  __proto__: null,
  configurable: true,
  enumerable: true,
  get: getEventEmitterAsyncResource,
});

return {
  addAbortListener,
  captureRejectionSymbol,
  default: EventEmitter,
  defaultMaxListeners,
  errorMonitor,
  EventEmitter,
  get EventEmitterAsyncResource() {
    return getEventEmitterAsyncResource();
  },
  getEventListeners,
  getMaxListeners,
  kEvents,
  kFirstEventParam,
  listenerCount,
  on,
  once,
  setMaxListeners,
};
})();
