"use strict";
(() => {
  var __defProp = Object.defineProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

  // src/eventified.ts
  var Eventified = class {
    constructor(options) {
      __publicField(this, "_eventListeners");
      __publicField(this, "_maxListeners");
      __publicField(this, "_logger");
      __publicField(this, "_throwOnEmitError", false);
      __publicField(this, "_throwOnEmptyListeners", false);
      __publicField(this, "_errorEvent", "error");
      this._eventListeners = /* @__PURE__ */ new Map();
      this._maxListeners = 100;
      this._logger = options?.logger;
      if (options?.throwOnEmitError !== void 0) {
        this._throwOnEmitError = options.throwOnEmitError;
      }
      if (options?.throwOnEmptyListeners !== void 0) {
        this._throwOnEmptyListeners = options.throwOnEmptyListeners;
      }
    }
    /**
     * Gets the logger
     * @returns {Logger}
     */
    get logger() {
      return this._logger;
    }
    /**
     * Sets the logger
     * @param {Logger} logger
     */
    set logger(logger) {
      this._logger = logger;
    }
    /**
     * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
     * @returns {boolean}
     */
    get throwOnEmitError() {
      return this._throwOnEmitError;
    }
    /**
     * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
     * @param {boolean} value
     */
    set throwOnEmitError(value) {
      this._throwOnEmitError = value;
    }
    /**
     * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.
     * @returns {boolean}
     */
    get throwOnEmptyListeners() {
      return this._throwOnEmptyListeners;
    }
    /**
     * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.
     * @param {boolean} value
     */
    set throwOnEmptyListeners(value) {
      this._throwOnEmptyListeners = value;
    }
    /**
     * Adds a handler function for a specific event that will run only once
     * @param {string | symbol} eventName
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    once(eventName, listener) {
      const onceListener = (...arguments_) => {
        this.off(eventName, onceListener);
        listener(...arguments_);
      };
      this.on(eventName, onceListener);
      return this;
    }
    /**
     * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners
     * @param {string} eventName The event name. Not required
     * @returns {number} The number of listeners
     */
    listenerCount(eventName) {
      if (eventName === void 0) {
        return this.getAllListeners().length;
      }
      const listeners = this._eventListeners.get(eventName);
      return listeners ? listeners.length : 0;
    }
    /**
     * Gets an array of event names
     * @returns {Array<string | symbol>} An array of event names
     */
    eventNames() {
      return [...this._eventListeners.keys()];
    }
    /**
     * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners
     * @param {string} [event] (Optional) The event name
     * @returns {EventListener[]} An array of listeners
     */
    rawListeners(event) {
      if (event === void 0) {
        return this.getAllListeners();
      }
      return this._eventListeners.get(event) ?? [];
    }
    /**
     * Prepends a listener to the beginning of the listeners array for the specified event
     * @param {string | symbol} eventName
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    prependListener(eventName, listener) {
      const listeners = this._eventListeners.get(eventName) ?? [];
      listeners.unshift(listener);
      this._eventListeners.set(eventName, listeners);
      return this;
    }
    /**
     * Prepends a one-time listener to the beginning of the listeners array for the specified event
     * @param {string | symbol} eventName
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    prependOnceListener(eventName, listener) {
      const onceListener = (...arguments_) => {
        this.off(eventName, onceListener);
        listener(...arguments_);
      };
      this.prependListener(eventName, onceListener);
      return this;
    }
    /**
     * Gets the maximum number of listeners that can be added for a single event
     * @returns {number} The maximum number of listeners
     */
    maxListeners() {
      return this._maxListeners;
    }
    /**
     * Adds a listener for a specific event. It is an alias for the on() method
     * @param {string | symbol} event
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    addListener(event, listener) {
      this.on(event, listener);
      return this;
    }
    /**
     * Adds a listener for a specific event
     * @param {string | symbol} event
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    on(event, listener) {
      if (!this._eventListeners.has(event)) {
        this._eventListeners.set(event, []);
      }
      const listeners = this._eventListeners.get(event);
      if (listeners) {
        if (listeners.length >= this._maxListeners) {
          console.warn(
            `MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`
          );
        }
        listeners.push(listener);
      }
      return this;
    }
    /**
     * Removes a listener for a specific event. It is an alias for the off() method
     * @param {string | symbol} event
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    removeListener(event, listener) {
      this.off(event, listener);
      return this;
    }
    /**
     * Removes a listener for a specific event
     * @param {string | symbol} event
     * @param {EventListener} listener
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    off(event, listener) {
      const listeners = this._eventListeners.get(event) ?? [];
      const index = listeners.indexOf(listener);
      if (index !== -1) {
        listeners.splice(index, 1);
      }
      if (listeners.length === 0) {
        this._eventListeners.delete(event);
      }
      return this;
    }
    /**
     * Calls all listeners for a specific event
     * @param {string | symbol} event
     * @param arguments_ The arguments to pass to the listeners
     * @returns {boolean} Returns true if the event had listeners, false otherwise
     */
    emit(event, ...arguments_) {
      let result = false;
      const listeners = this._eventListeners.get(event);
      if (listeners && listeners.length > 0) {
        for (const listener of listeners) {
          listener(...arguments_);
          result = true;
        }
      }
      if (event === this._errorEvent) {
        const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);
        if (this._throwOnEmitError && !result) {
          throw error;
        } else {
          if (this.listeners(this._errorEvent).length === 0 && this._throwOnEmptyListeners === true) {
            throw error;
          }
        }
      }
      this.sendLog(event, arguments_);
      return result;
    }
    /**
     * Gets all listeners for a specific event. If no event is provided, it returns all listeners
     * @param {string} [event] (Optional) The event name
     * @returns {EventListener[]} An array of listeners
     */
    listeners(event) {
      return this._eventListeners.get(event) ?? [];
    }
    /**
     * Removes all listeners for a specific event. If no event is provided, it removes all listeners
     * @param {string} [event] (Optional) The event name
     * @returns {IEventEmitter} returns the instance of the class for chaining
     */
    removeAllListeners(event) {
      if (event !== void 0) {
        this._eventListeners.delete(event);
      } else {
        this._eventListeners.clear();
      }
      return this;
    }
    /**
     * Sets the maximum number of listeners that can be added for a single event
     * @param {number} n The maximum number of listeners
     * @returns {void}
     */
    setMaxListeners(n) {
      this._maxListeners = n;
      for (const listeners of this._eventListeners.values()) {
        if (listeners.length > n) {
          listeners.splice(n);
        }
      }
    }
    /**
     * Gets all listeners
     * @returns {EventListener[]} An array of listeners
     */
    getAllListeners() {
      let result = [];
      for (const listeners of this._eventListeners.values()) {
        result = [...result, ...listeners];
      }
      return result;
    }
    /**
     * Sends a log message using the configured logger based on the event name
     * @param {string | symbol} eventName - The event name that determines the log level
     * @param {unknown} data - The data to log
     */
    sendLog(eventName, data) {
      if (!this._logger) {
        return;
      }
      let message;
      if (typeof data === "string") {
        message = data;
      } else if (Array.isArray(data) && data.length > 0 && data[0] instanceof Error) {
        message = data[0].message;
      } else if (data instanceof Error) {
        message = data.message;
      } else if (Array.isArray(data) && data.length > 0 && typeof data[0]?.message === "string") {
        message = data[0].message;
      } else {
        message = JSON.stringify(data);
      }
      switch (eventName) {
        case "error": {
          this._logger.error?.(message, { event: eventName, data });
          break;
        }
        case "warn": {
          this._logger.warn?.(message, { event: eventName, data });
          break;
        }
        case "trace": {
          this._logger.trace?.(message, { event: eventName, data });
          break;
        }
        case "debug": {
          this._logger.debug?.(message, { event: eventName, data });
          break;
        }
        case "fatal": {
          this._logger.fatal?.(message, { event: eventName, data });
          break;
        }
        default: {
          this._logger.info?.(message, { event: eventName, data });
          break;
        }
      }
    }
  };

  // src/index.ts
  var Hookified = class extends Eventified {
    constructor(options) {
      super({
        logger: options?.logger,
        throwOnEmitError: options?.throwOnEmitError,
        throwOnEmptyListeners: options?.throwOnEmptyListeners
      });
      __publicField(this, "_hooks");
      __publicField(this, "_throwOnHookError", false);
      __publicField(this, "_enforceBeforeAfter", false);
      __publicField(this, "_deprecatedHooks");
      __publicField(this, "_allowDeprecated", true);
      this._hooks = /* @__PURE__ */ new Map();
      this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();
      if (options?.throwOnHookError !== void 0) {
        this._throwOnHookError = options.throwOnHookError;
      } else if (options?.throwHookErrors !== void 0) {
        this._throwOnHookError = options.throwHookErrors;
      }
      if (options?.enforceBeforeAfter !== void 0) {
        this._enforceBeforeAfter = options.enforceBeforeAfter;
      }
      if (options?.allowDeprecated !== void 0) {
        this._allowDeprecated = options.allowDeprecated;
      }
    }
    /**
     * Gets all hooks
     * @returns {Map<string, Hook[]>}
     */
    get hooks() {
      return this._hooks;
    }
    /**
     * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
     * @returns {boolean}
     * @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
     */
    get throwHookErrors() {
      return this._throwOnHookError;
    }
    /**
     * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
     * @param {boolean} value
     * @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
     */
    set throwHookErrors(value) {
      this._throwOnHookError = value;
    }
    /**
     * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
     * @returns {boolean}
     */
    get throwOnHookError() {
      return this._throwOnHookError;
    }
    /**
     * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
     * @param {boolean} value
     */
    set throwOnHookError(value) {
      this._throwOnHookError = value;
    }
    /**
     * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.
     * @returns {boolean}
     * @default false
     */
    get enforceBeforeAfter() {
      return this._enforceBeforeAfter;
    }
    /**
     * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.
     * @param {boolean} value
     */
    set enforceBeforeAfter(value) {
      this._enforceBeforeAfter = value;
    }
    /**
     * Gets the map of deprecated hook names to deprecation messages.
     * @returns {Map<string, string>}
     */
    get deprecatedHooks() {
      return this._deprecatedHooks;
    }
    /**
     * Sets the map of deprecated hook names to deprecation messages.
     * @param {Map<string, string>} value
     */
    set deprecatedHooks(value) {
      this._deprecatedHooks = value;
    }
    /**
     * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.
     * @returns {boolean}
     */
    get allowDeprecated() {
      return this._allowDeprecated;
    }
    /**
     * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.
     * @param {boolean} value
     */
    set allowDeprecated(value) {
      this._allowDeprecated = value;
    }
    /**
     * Validates hook event name if enforceBeforeAfter is enabled
     * @param {string} event - The event name to validate
     * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
     */
    validateHookName(event) {
      if (this._enforceBeforeAfter) {
        const eventValue = event.trim().toLocaleLowerCase();
        if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
          throw new Error(
            `Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
          );
        }
      }
    }
    /**
     * Checks if a hook is deprecated and emits a warning if it is
     * @param {string} event - The event name to check
     * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
     */
    checkDeprecatedHook(event) {
      if (this._deprecatedHooks.has(event)) {
        const message = this._deprecatedHooks.get(event);
        const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
        this.emit("warn", { hook: event, message: warningMessage });
        return this._allowDeprecated;
      }
      return true;
    }
    /**
     * Adds a handler function for a specific event
     * @param {string} event
     * @param {Hook} handler - this can be async or sync
     * @returns {void}
     */
    onHook(event, handler) {
      this.onHookEntry({ event, handler });
    }
    /**
     * Adds a handler function for a specific event
     * @param {HookEntry} hookEntry
     * @returns {void}
     */
    onHookEntry(hookEntry) {
      this.validateHookName(hookEntry.event);
      if (!this.checkDeprecatedHook(hookEntry.event)) {
        return;
      }
      const eventHandlers = this._hooks.get(hookEntry.event);
      if (eventHandlers) {
        eventHandlers.push(hookEntry.handler);
      } else {
        this._hooks.set(hookEntry.event, [hookEntry.handler]);
      }
    }
    /**
     * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
     * @param {string} event
     * @param {Hook} handler - this can be async or sync
     * @returns {void}
     */
    addHook(event, handler) {
      this.onHookEntry({ event, handler });
    }
    /**
     * Adds a handler function for a specific event
     * @param {Array<HookEntry>} hooks
     * @returns {void}
     */
    onHooks(hooks) {
      for (const hook of hooks) {
        this.onHook(hook.event, hook.handler);
      }
    }
    /**
     * Adds a handler function for a specific event that runs before all other handlers
     * @param {string} event
     * @param {Hook} handler - this can be async or sync
     * @returns {void}
     */
    prependHook(event, handler) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return;
      }
      const eventHandlers = this._hooks.get(event);
      if (eventHandlers) {
        eventHandlers.unshift(handler);
      } else {
        this._hooks.set(event, [handler]);
      }
    }
    /**
     * Adds a handler that only executes once for a specific event before all other handlers
     * @param event
     * @param handler
     */
    prependOnceHook(event, handler) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return;
      }
      const hook = async (...arguments_) => {
        this.removeHook(event, hook);
        return handler(...arguments_);
      };
      this.prependHook(event, hook);
    }
    /**
     * Adds a handler that only executes once for a specific event
     * @param event
     * @param handler
     */
    onceHook(event, handler) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return;
      }
      const hook = async (...arguments_) => {
        this.removeHook(event, hook);
        return handler(...arguments_);
      };
      this.onHook(event, hook);
    }
    /**
     * Removes a handler function for a specific event
     * @param {string} event
     * @param {Hook} handler
     * @returns {void}
     */
    removeHook(event, handler) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return;
      }
      const eventHandlers = this._hooks.get(event);
      if (eventHandlers) {
        const index = eventHandlers.indexOf(handler);
        if (index !== -1) {
          eventHandlers.splice(index, 1);
        }
      }
    }
    /**
     * Removes all handlers for a specific event
     * @param {Array<HookEntry>} hooks
     * @returns {void}
     */
    removeHooks(hooks) {
      for (const hook of hooks) {
        this.removeHook(hook.event, hook.handler);
      }
    }
    /**
     * Calls all handlers for a specific event
     * @param {string} event
     * @param {T[]} arguments_
     * @returns {Promise<void>}
     */
    async hook(event, ...arguments_) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return;
      }
      const eventHandlers = this._hooks.get(event);
      if (eventHandlers) {
        for (const handler of eventHandlers) {
          try {
            await handler(...arguments_);
          } catch (error) {
            const message = `${event}: ${error.message}`;
            this.emit("error", new Error(message));
            if (this._throwOnHookError) {
              throw new Error(message);
            }
          }
        }
      }
    }
    /**
     * Calls all synchronous handlers for a specific event.
     * Async handlers (declared with `async` keyword) are silently skipped.
     *
     * Note: The `hook` method is preferred as it executes both sync and async functions.
     * Use `hookSync` only when you specifically need synchronous execution.
     * @param {string} event
     * @param {T[]} arguments_
     * @returns {void}
     */
    hookSync(event, ...arguments_) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return;
      }
      const eventHandlers = this._hooks.get(event);
      if (eventHandlers) {
        for (const handler of eventHandlers) {
          if (handler.constructor.name === "AsyncFunction") {
            continue;
          }
          try {
            handler(...arguments_);
          } catch (error) {
            const message = `${event}: ${error.message}`;
            this.emit("error", new Error(message));
            if (this._throwOnHookError) {
              throw new Error(message);
            }
          }
        }
      }
    }
    /**
     * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.
     * @param {string} event - The event name
     * @param {T[]} arguments_ - The arguments to pass to the hook
     */
    async beforeHook(event, ...arguments_) {
      await this.hook(`before:${event}`, ...arguments_);
    }
    /**
     * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.
     * @param {string} event - The event name
     * @param {T[]} arguments_ - The arguments to pass to the hook
     */
    async afterHook(event, ...arguments_) {
      await this.hook(`after:${event}`, ...arguments_);
    }
    /**
     * Calls all handlers for a specific event. This is an alias for `hook` and is provided for
     * compatibility with other libraries that use the `callHook` method.
     * @param {string} event
     * @param {T[]} arguments_
     * @returns {Promise<void>}
     */
    async callHook(event, ...arguments_) {
      await this.hook(event, ...arguments_);
    }
    /**
     * Gets all hooks for a specific event
     * @param {string} event
     * @returns {Hook[]}
     */
    getHooks(event) {
      this.validateHookName(event);
      if (!this.checkDeprecatedHook(event)) {
        return void 0;
      }
      return this._hooks.get(event);
    }
    /**
     * Removes all hooks
     * @returns {void}
     */
    clearHooks() {
      this._hooks.clear();
    }
  };
})();
/* v8 ignore next -- @preserve */
//# sourceMappingURL=index.global.js.map