import type { EventEmitterOptionsType } from './types';
import type { Emitter } from '../emitter';
/**
 * This class is responsible for appending listeners (functions) and sending events to them
 * when needed.
 *
 * WHY THIS IS NEEDED SINCE NODE AND THE BROWSER HAS IT'S OWN EVENTS SYSTEM?
 * We do not try to change the default event system of node, or libraries like EventEmitter2,
 * instead we try to work with them.
 *
 * We use them to emit the events locally inside of the application but we use this class to emit
 * and listen to events distributed to multiple machines. For example, if we need to send an event
 * between one machine over the other we will need to append a layer to the event emitter. This layer
 * will receive an event and dispatch it to the responsible listeners. By default a layer is just another
 * event emitter. Who defines the behavior of the layer is the emitter and not the layer, so who defines
 * the listeners of this layer is the emitter by itself.
 *
 * >>> FOR MAINTAINERS <<<
 * This class can be kinda hard to debug, specially with layers on top of it, so we need to dissect it first
 * before you start working on it.
 *
 * - 1: Simple event/emitter:
 * When you initialize this class you need to pass an Emitter instance. Emitter will be the interface that we use
 * for pub/sub. Emitter can be for example Redis, EventEmitter2, node's EventEmitter from 'events'. This interface
 * will be available right below EventEmitter class/instance.
 * This means that all of the logic is extracted away from the emitter interface and should be implemented here. One
 * of those special logics are wildcards.
 * To save an event to the emitter like 'EventEmitter2' we will have some work to do. We don't save it `raw`,
 * but instead we save a representation of the event. First things first we need to separate it between groups and
 * handlers.
 *
 * - Groups:
 * T.L.D.R.: This is the name of the event.
 * a groupId is the name of the event, so for example: for the event 'create.user', we transform 'create.user' to
 * a uuid `124002c4-3719-4c9b-a88e-f743b67f1686`, this means that on the emitter what we will be firing is the
 * `124002c4-3719-4c9b-a88e-f743b67f1686` event and not directly `create.user`. In other words we need to guarantee that
 * we do this conversion when firing the event. To help us with that we use the `this.#groupByKeys`, this means that for
 * `create.user`, or `create.**`, or `create.*` we need to fire the emit action to the following groups. You will see
 * that for most functions we just need to do is get the groupIds and fire it.
 *
 * ```ts
 * const key = `create.user`
 * const groupIdsToEmitEventTo = (this.#groupByKeys[key] || new Set()).values();
 * ```
 *
 * This guarantees that for the specific key we will call the emitters correctly. The nicest thing about doing this way
 * is that it's really easy to store this data since most of them are just strings so stuff like wildcards are like:
 *
 * ```ts
 * {
 *    'results-b35ab092-48f3-472f-be2c-48ee2ea0df91': Set(1) { '30e6d1c4-2470-4cff-8ccb-a48b6378dd67' },
 *    '**': Set(1) { 'ad14de8c-9104-4eaf-9c14-9c7384cc0473' },
 *    'create.**': Set(1) { 'ad14de8c-9104-4eaf-9c14-9c7384cc0473' },
 *    'create.*': Set(1) { 'ad14de8c-9104-4eaf-9c14-9c7384cc0473' },
 *    'create.user': Set(1) { 'ad14de8c-9104-4eaf-9c14-9c7384cc0473' }
 * }
 * ```
 *
 * You see that for `create.*`, 'create.**', 'create.user' we are pointing to the same group? That's the general idea.
 *
 * - Handlers:
 * Handlers are the functions, that is being called, it doesn't have any usage for the `emitter` instance. Our usage for
 * it is internal like for example removing a handler. Most APIs for event emitters work like:
 *
 * ```
 * const emitter = new EventEmitter2()
 *
 * const callback = (value1, value2) => {
 *    console.log(this.event, value1, value2);
 * }
 * emitter.on('foo', callback);
 * emitter.removeListener('foo', callback);
 * ```
 *
 * Do you see that we need to pass the function there to remove the listener? That's what we try to solve by storing it.
 * By transforming this handler to an id we can easily find for it with a O(n) algorithm that retrieves the handler and
 * removes it. The other usage of handlers is on results we will cover it on the next topic.
 *
 * - 2: Emitting an event and waiting for a result.
 * Your first though might be? WHAT, how's that even possible? We can't know an event has fired or even the result of
 * it, specially on distributed systems.
 *
 * That's not really magic it's really simple actually.
 * When we add a new listener you see that we wrap the function (callback) to another function
 * (see #wrapInResultCallback).
 * What this function do is that it has a lifecycle, similar to a promise in javascript: `pending`, `completed`,
 * `failed`.
 * What's the idea?
 * When we call the for example `emitter.emit('create.user', 1)` we will call this function after
 * creating a resultKey, the emitter by itself, when we initialize the class, will also hold a `resultsEventName`.
 * Why both? The second one is a listener, a listener that will only listen for results of this emitter. The second one
 * is needed because a single emitter can send multiple events at the same time, se we need to differ between them.
 *
 * Continuing on, we called `emitter.emit('create.user', 1)`, created the resultId, and sent the resultsEventName to
 * the listener. After calling we return to the user a promise, inside of this promise there will be a recursion that
 * iterates over for each tick of the event loop. (see #fetchResultForEmit). Inside of this promise be aware of
 * `pingTimeout` and `resultsTimeout`.
 * Ping is how long we will wait to be notified that ""someone"" is working on the result, this is needed for cases
 * when the event simply don't exist so we don't wait for too long. The second one is `resultsTimeout`, as you might
 * have guessed, means how long we will wait for a result.
 *
 * Now let's jump to the listener itself, you see that the first thing we do is to emit an event TO THE
 * `resultsEventName` (remember, that's the listener for the results), this event will have the following structure:
 * { status: string; data: any } When the listener receives this event, it'll append this result to `#pendingResults`
 * the Promise (that will not be resolved just yet), will iterate over and it'll see that some listener is working
 * on the response for this emit. When this finishes we enter the `waiting` stage. So now we will wait until all pending
 * results have finished or until we reach the resultsTimeout. This get's kinda complicated when adding a layer.
 *
 * - 3: Layers, what makes this almost unstoppable and where things gets kinda complicated.
 * Layers are just EventEmitter instances, a layer will be able to make distributed systems fully in sync with each
 * other.
 * But how?
 * Generally speaking a layer will be using `RedisEmitter` or `KafkaEmitter` or basically any type of Pub/Sub or
 * messaging service.
 *
 * A layer works by channels, channels enables the user to separate the logic between each of them, for example:
 * if we have have a chat, we might end up having multiple rooms, `room1` would be the first channel and `room2`
 * would be the second channel. If we want to broadcast an event to `room1` layer we can do that by just emitting
 * the event to it. If we want `room2` to be broadcasted we can send an event directly to it.
 * You will see that when layers are defined, emitting events are done inside of the layer, and not inside of the
 * EventEmitter instance.
 * In other words, what we are doing is: Every event that will be emitted from the emitter will actually be sent
 * to the layer. The layer broadcasts the events to `room1` for example, `room1`  so we emit it, when we emit a
 * broadcast will be fired, this message received it'll be handled by
 *
 * ```
 * this.emitEventToEmitter
 * ```
 *
 * in other words, i'll be handled by the emitter (the local one) itself.
 *
 * This might become easier with an example:
 * - Call `emitter.emitToChannel(['birds', 'users'], 'create.user', { id: 1, name: 'Nicolas'})`
 * - Send the key ('create.user') and the data both to `bird` and `user` keys INSIDE of the layer
 * - `Bird` handler points to a function defined in birdsEmitter, so when we are receiving this value we are
 * handling inside of `birdsEmitter`.
 * - on `birdsEmitter` instance, we get get the original key (so `create.user`) and we will be able to make
 * it work normally as the layer didn't exist.
 * - When we notify about the response we follow the same thing.
 *
 * IMPORTANT: Your emitter cannot receive responses from channels it's not subscribed to.
 *
 * ```
 * const emitter = await EventEmitter.new(EventEmitter2Emitter, {
 *  layer: {
 *    use: layer,
 *    channels: ['birds'],
 *  },
 *  wildcards: { use: true },
 * });
 *
 * const result = await emitter.emitToChannel(['users', 'birds'], 'create.*');
 * // We totally ignore 'users channel' on this case
 * ```
 *
 * IMPORTANT: We can't rely on the data inside of this class, when working with events
 * we are working with distributed systems, the data might not be inside of here, so this means
 * a listener might be in other machine. So when working with them we do not have to rely too much on
 * internal data for the class.
 */
export declare class EventEmitter<TEmitter extends Emitter = Emitter> {
    #private;
    protected $$type: string;
    emitter: TEmitter;
    protected layer?: EventEmitter;
    private resultsEventName;
    /**
     * Factory method for the building the emitter, we need this because we need to add results listener and layer
     * listeners to the function and both operations are async.
     *
     * Be aware that you need to pass the emitter, the constructor, and not the instance, you can pass the parameters
     * of the emitter inside of options: { customParams: <your_params_for_the_emitter> }
     *
     * @param emitter - The emitter constructor so we build it inside here or a default export by using
     * `import('./my-custom-emitter.ts')`
     * @param options - Custom options for the emitter, on here you can pass a layer instance, wildcards options and
     * customize the timeout for the results to be retrieved.
     *
     * @returns - This is a factory method so we create a new EventEmitter instance.
     */
    static new<TEmitter extends typeof Emitter = typeof Emitter>(emitter: Promise<{
        default: TEmitter;
    }> | TEmitter, options?: EventEmitterOptionsType & {
        emitterParams?: Parameters<TEmitter['new']>;
    }): Promise<EventEmitter<Emitter>>;
    constructor(emitterInstance: TEmitter, options?: EventEmitterOptionsType);
    get hasLayer(): boolean;
    get channels(): string[];
    /**
     * This is responsible fo retrieving the response of the emitted event, when the event
     * finishes processing it'll send a response to this function (this is handler for a specific
     * event inside of the event emitter).
     */
    private resultsListener;
    /**
     * This will subscribe a listener (function) to an specific event (key). When this key is emitted, either from
     * a channel or from the emitter itself, the listener (function) will be called.
     *
     * Returning a value from the function will emit a result back to the caller.
     *
     * IMPORTANT: The data received and the return value must be JSON serializable values. This means you cannot expect
     * to receive a callback or function in your listener. As well as this, you can't return a function, can't return
     * a class. It needs to be JSON serializable.
     *
     * @param key - The key that will be used to emit the event.
     * @param callback - The function that will be called when the event is emitted.
     *
     * @returns - A unsubscribe function that if called, will remove the listener from the emitter.
     */
    addEventListener(key: string, callback: (...args: any) => any): Promise<() => Promise<void>>;
    /**
     * This method will subscribe a listener that will not emit a result back to the caller. So it might
     * be useful for listeners where performance does matter and needs to be taken aware of.
     *
     * @param key - The key that will be used to emit the event.
     * @param callback - The function that will be called when the event is emitted.
     *
     * @returns - A unsubscribe function that if called, will remove the listener from the emitter.
     */
    addEventListenerWithoutResult(key: string, callback: (...args: any) => any): Promise<() => Promise<void>>;
    /**
     * [INTERNAL] This will subscribe a listener (function) to an specific event (key) without worrying about the result.
     * This is mostly used for internal usage, we do not need to wrap the `results` listener and
     * `layerListener` to send the results. Actually if we did this we might would end up in a loop.
     *
     * So in other words, this adds the key and the listener `raw`, so not wrapped in anything and without
     * the wildcards.
     *
     * @param key - The key that will be used to emit the event.
     * @param callback - The function that will be called when the event is emitted.
     *
     * @returns - Returns the unsubscribe function that should be called to unsubscribe the listener.
     */
    protected addRawEventListenerWithoutResult(key: string, callback: (...args: any) => any): Promise<() => Promise<void>>;
    /**
     * This will either unsubscribe all listeners or all of the listeners of a specific key. We pass an object here
     * to prevent undesired behavior, if for some reason key is undefined we will not remove all of the listeners you need
     * to explicitly define the key that you want to remove.
     *
     * @param options - The options of the listeners we want to remove.
     * @param options.key - The key that you want to remove from the emitter.
     */
    unsubscribeAll(options?: {
        key: string;
    }): Promise<void>;
    /**
     * Unsubscribes this emitter from a specific channel inside of the layer. If it doesn't exist it will do nothing.
     *
     * @param channel - The channel that you want to unsubscribe from.
     */
    unsubscribeFromChannel(channel: string): Promise<void>;
    /**
     * Emits the event to the `this.emitter.emit`
     *
     * @param resultsEventName - this is the handler you will call with the result, it'll
     * it's just one for every emitter, so each emitter instance define it's own resultsEventName
     * @param resultKey - This is the key of the result, when you all `.emit()` we will create a key
     * meaning that we will populate the contents of this key with the results.
     */
    protected emitEventToEmitter(key: string, resultsEventName: string, resultKey: string, channelLayer: string | null, ...data: any[]): Promise<void>;
    /**
     * Emits some data to a channel, a channel is something that should be defined in the layer, This will fire the event
     * in the layer calling all subscribed listeners. By doing this you can call the `emit` method on multiple machines
     * inside of the server.
     *
     * @param channel - The channel to emit the event to.
     * @param key - The key to send events to.
     * @param data - The data to send over to the listeners. (IT SHOULD BE JSON SERIALIZABLE)
     *
     * @return - A promise that will wait for a return of the emitters.
     */
    emitToChannel<TResult = unknown>(channels: string[] | string, key: string, ...data: any[]): Promise<TResult[]>;
    /**
     * When we emit the event we will return a promise, this promise will wait
     * for the results of the listeners to be sent back to the application. With this
     * we are able to retrieve the results of the connected listeners.
     *
     * @param key - The key to send events to.
     * @param data - The data to send over to the listeners. (IT SHOULD BE JSON SERIALIZABLE)
     *
     * @return - A promise that will wait for a return of the emitters.
     */
    emit<TResult = unknown>(key: string, ...data: any[]): Promise<TResult[]>;
    /**
     * Function that is used to emit the result back to the caller this way we can distribute the callers
     * and send the response back to the caller as it was on the same system / machine.
     *
     * If it is in an emitter we will send this response through the layer, otherwise it'll send normally
     * in the event emitter.
     *
     * @param resultsEventName - The results event name is a listener that exists for all emitters inside
     * of the application. This is a unique ID. Each EventEmitter instance has it's own. So what we do is that
     * we send this over the network and guarantee that only who sent the event will receive this response back.
     * @param handlerId - An id of the function (listener) that is working for the result of this event.
     * @param resultKey - The id of the result. When you emit an event it'll generate a key. This is the key
     * that we use to guarantee that the value received is from this event.
     * @param channelLayer - Only needed when using `layers`, but this is the channel that we should broadcast
     * this response to. For example if we emitted the event for every listener in the `users` channel, we should
     * guarantee that we are sending the response back to the `users` channel so that the `resultEventName` can
     * catch this value and do something with it.
     * @param data - This is the actual data that you are sending over the network, generally speaking it'll be,
     * most of the times, an array since we are spreading it over.
     */
    protected emitResult(resultsEventName: string, handlerId: string, resultKey: string, channelLayer: string | null, ...data: any[]): void;
    /**
     * Appends the listeners to the layer, this way we will be able to connect two different emitters together.
     * Those 2 different emitters might be on the same machine or a completely different machine (if we are using
     * RedisEmitter)
     *
     * @param channels - The channels that your emitter will listen to. This means that when we receive an event on
     * a specific channel and this emitter has handlers for this event, we will emit the event.
     */
    private addChannelListeners;
}
//# sourceMappingURL=index.d.ts.map