import "node:module";
import is from "@sindresorhus/is";
import Emittery from "emittery";
import { moduleCaller, moduleImporter } from "@adonisjs/fold";
import { debuglog } from "node:util";
import string from "@poppinss/utils/string";
import { AssertionError } from "node:assert";
import diagnostics_channel from "node:diagnostics_channel";
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
	let target = {};
	for (var name in all) __defProp(target, name, {
		get: all[name],
		enumerable: true
	});
	if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
	return target;
};
var debug_default = debuglog("adonisjs:events");
var EventsBuffer = class {
	#events = [];
	#restoreFn;
	constructor(restoreFn) {
		this.#restoreFn = restoreFn;
	}
	add(event, data) {
		this.#events.push({
			event,
			data
		});
	}
	all() {
		return this.#events;
	}
	size() {
		return this.#events.length;
	}
	exists(event, finder) {
		return !!this.find(event, finder);
	}
	find(event, finder) {
		return this.#events.find((bufferedEvent) => {
			if (!finder) return bufferedEvent.event === event;
			return bufferedEvent.event === event && finder(bufferedEvent);
		}) || null;
	}
	assertEmitted(event, finder) {
		if (!this.exists(event, finder)) throw new AssertionError({
			message: is.class(event) ? `Expected "[class ${event.name}]" event to be emitted` : `Expected "${String(event)}" event to be emitted`,
			expected: true,
			actual: false,
			operator: "strictEqual",
			stackStartFn: this.assertEmitted
		});
	}
	assertEmittedCount(event, count) {
		const actual = this.all().filter((bufferedEvent) => bufferedEvent.event === event).length;
		if (actual !== count) throw new AssertionError({
			message: `Expected "${is.class(event) ? `[class ${event.name}]` : String(event)}" event to be emitted "${count}" ${string.pluralize("time", count)}, instead it was emitted "${actual}" ${string.pluralize("time", actual)}`,
			actual,
			expected: count
		});
	}
	assertNotEmitted(event, finder) {
		if (this.exists(event, finder)) throw new AssertionError({
			message: is.class(event) ? `Unexpected "[class ${event.name}]" event was emitted` : `Unexpected "${String(event)}" event was emitted`,
			expected: false,
			actual: true,
			operator: "strictEqual",
			stackStartFn: this.assertNotEmitted
		});
	}
	assertNoneEmitted() {
		const eventsSize = this.size();
		if (eventsSize > 0) throw new AssertionError(Object.assign({
			message: `Expected zero events to be emitted. Instead received "${eventsSize}" ${string.pluralize("event", eventsSize)}`,
			expected: 0,
			actual: eventsSize,
			operator: "strictEqual",
			stackStartFn: this.assertNoneEmitted
		}, { showDiff: true }));
	}
	flush() {
		this.#events = [];
	}
	[Symbol.dispose]() {
		this.#restoreFn();
	}
};
var tracing_channels_exports = /* @__PURE__ */ __exportAll({ eventDispatch: () => eventDispatch });
const eventDispatch = diagnostics_channel.tracingChannel("adonisjs.event.dispatch");
var Emitter = class {
	#eventsClassSymbols = /* @__PURE__ */ new Map();
	#eventsListeners = /* @__PURE__ */ new Map();
	#transport = new Emittery();
	#eventsBuffer;
	#eventsToFake = /* @__PURE__ */ new Set();
	#errorHandler;
	#app;
	get eventsListeners() {
		return this.#eventsListeners;
	}
	constructor(app) {
		this.#app = app;
	}
	#isConstructor(value) {
		return is.class(value);
	}
	#getEventClassSymbol(event) {
		if (!this.#eventsClassSymbols.has(event)) this.#eventsClassSymbols.set(event, Symbol(event.name));
		return this.#eventsClassSymbols.get(event);
	}
	#resolveEvent(event) {
		if (this.#isConstructor(event)) return this.#getEventClassSymbol(event);
		return event;
	}
	#getEventListeners(event) {
		if (!this.#eventsListeners.has(event)) this.#eventsListeners.set(event, /* @__PURE__ */ new Map());
		return this.#eventsListeners.get(event);
	}
	#normalizeEventListener(listener) {
		if (typeof listener === "string") {
			const parts = listener.split(".");
			const method = parts.length === 1 ? "handle" : parts.pop();
			const moduleRefId = parts.join(".");
			return moduleImporter(() => this.#app.import(moduleRefId), method).toCallable(this.#app.container);
		}
		if (Array.isArray(listener)) {
			const listenerModule = listener[0];
			const method = listener[1] || "handle";
			if (this.#isConstructor(listenerModule)) return moduleCaller(listenerModule, method).toCallable(this.#app.container);
			return moduleImporter(listenerModule, method).toCallable(this.#app.container);
		}
		return listener;
	}
	#resolveEventListener(event, listener) {
		const eventListeners = this.#getEventListeners(event);
		if (!eventListeners.has(listener)) eventListeners.set(listener, this.#normalizeEventListener(listener));
		return eventListeners.get(listener);
	}
	onError(callback) {
		this.#errorHandler = callback;
		return this;
	}
	listen(event, listeners) {
		listeners.forEach((listener) => this.on(event, [listener, "handle"]));
	}
	on(event, listener) {
		if (debug_default.enabled) debug_default("registering event listener, event: %O, listener: %O", event, listener);
		const normalizedEvent = this.#resolveEvent(event);
		const normalizedListener = this.#resolveEventListener(event, listener);
		this.#transport.on(normalizedEvent, normalizedListener);
		return () => this.off(event, listener);
	}
	listenIf(condition, event, listener) {
		if (!condition || typeof condition === "function" && !condition()) return () => {};
		return this.on(event, listener);
	}
	once(event, listener) {
		if (debug_default.enabled) debug_default("registering one time event listener, event: %O, listener: %O", event, listener);
		const normalizedEvent = this.#resolveEvent(event);
		const normalizedListener = this.#normalizeEventListener(listener);
		const off = this.#transport.on(normalizedEvent, async (data) => {
			off();
			debug_default("removing one time event listener, event: %O", event);
			await normalizedListener(data);
		});
	}
	onAny(listener) {
		return this.#transport.onAny(listener);
	}
	async emit(event, data) {
		if (this.#eventsToFake.has(event) || this.#eventsToFake.has("*")) {
			debug_default("faking emit. event: %O, data: %O", event, data);
			this.#eventsBuffer.add(event, data);
			return;
		}
		try {
			const normalizedEvent = this.#resolveEvent(event);
			await eventDispatch.tracePromise(this.#transport.emit, eventDispatch.hasSubscribers ? {
				event,
				data
			} : void 0, this.#transport, normalizedEvent, data);
		} catch (error) {
			if (this.#errorHandler) this.#errorHandler(event, error, data);
			else throw error;
		}
	}
	async emitSerial(event, data) {
		if (this.#eventsToFake.has(event) || this.#eventsToFake.has("*")) {
			debug_default("faking emit. event: %O, data: %O", event, data);
			this.#eventsBuffer.add(event, data);
			return;
		}
		try {
			const normalizedEvent = this.#resolveEvent(event);
			await eventDispatch.tracePromise(this.#transport.emitSerial, eventDispatch.hasSubscribers ? {
				event,
				data
			} : void 0, this.#transport, normalizedEvent, data);
		} catch (error) {
			if (this.#errorHandler) this.#errorHandler(event, error, data);
			else throw error;
		}
	}
	off(event, listener) {
		if (debug_default.enabled) debug_default("removing listener, event: %O, listener: %O", event, listener);
		const normalizedEvent = this.#resolveEvent(event);
		const listeners = this.#getEventListeners(event);
		if (Array.isArray(listener)) {
			const matchingKey = listeners.keys().find((key) => {
				if (Array.isArray(key)) return key[0] === listener[0] && key[1] === listener[1];
				return false;
			});
			const normalizedListener = matchingKey ? listeners.get(matchingKey) : void 0;
			if (normalizedListener && matchingKey) {
				listeners.delete(matchingKey);
				this.#transport.off(normalizedEvent, normalizedListener);
			}
			return;
		}
		const normalizedListener = listeners.get(listener);
		if (normalizedListener) {
			listeners.delete(listener);
			this.#transport.off(normalizedEvent, normalizedListener);
		}
	}
	offAny(listener) {
		this.#transport.offAny(listener);
		return this;
	}
	clearListener(event, listener) {
		return this.off(event, listener);
	}
	clearListeners(event) {
		debug_default("clearing all listeners for event %O", event);
		this.#transport.clearListeners(this.#resolveEvent(event));
		this.#eventsListeners.delete(event);
	}
	clearAllListeners() {
		debug_default("clearing all event listeners");
		this.#transport.clearListeners();
		this.#eventsListeners.clear();
	}
	listenerCount(event) {
		return this.#transport.listenerCount(event ? this.#resolveEvent(event) : void 0);
	}
	hasListeners(event) {
		return this.listenerCount(event) > 0;
	}
	fake(events) {
		this.restore();
		this.#eventsBuffer = new EventsBuffer(() => this.restore());
		if (!events) {
			debug_default("faking all events");
			this.#eventsToFake.add("*");
		} else {
			debug_default("faking events: %O", events);
			events.forEach((event) => this.#eventsToFake.add(event));
		}
		return this.#eventsBuffer;
	}
	restore() {
		debug_default("restoring existing fakes");
		this.#eventsToFake.clear();
		this.#eventsBuffer?.flush();
		this.#eventsBuffer = void 0;
	}
};
export { tracing_channels_exports as n, Emitter as t };
