import type { IDeref, IObjectOf, Maybe } from "@thi.ng/api";
import type { IAtom } from "@thi.ng/atom";
import { type EffectDef, type EffectPriority, type Event, type EventDef, type IDispatch, type Interceptor, type InterceptorContext, type InterceptorFn, type SideEffect } from "./api.js";
/**
 * Batched event processor for using composable interceptors for event handling
 * and side effects to execute the result of handled events.
 *
 * @remarks
 * Events processed by this class are simple 2-element tuples/arrays of this
 * form: `["event-id", payload?]`, where the `payload` is optional and can be of
 * any type.
 *
 * Events are processed by registered handlers which transform each event into a
 * number of side effect descriptions to be executed later. This separation
 * ensures event handlers themselves are pure functions and leads to more
 * efficient reuse of side effecting operations. The pure data nature until the
 * last stage of processing (the application side effects) too means that event
 * flow can be much easier inspected and debugged.
 *
 * In this model a single event handler itself is an array of objects with `pre`
 * and/or `post` keys and functions attached to each key. These functions are
 * called interceptors, since each intercepts the processing of an event and can
 * contribute their own side effects. Each event's interceptor chain is
 * processed bi-directionally (`pre` in forward, `post` in reverse order) and
 * the effects returned from each interceptor are merged/collected. The outcome
 * of this setup is a more aspect-oriented, composable approach to event
 * handling and allows to inject common, re-usable behaviors for multiple event
 * types (logging, validation, undo/redo triggers etc.).
 *
 * Side effects are only processed after all event handlers have run.
 * Furthermore, their order of execution can be configured with optional
 * priorities.
 *
 * See for further details:
 *
 * - {@link StatelessEventBus.processQueue}
 * - {@link StatelessEventBus.processEvent}
 * - {@link StatelessEventBus.processEffects}
 * - {@link StatelessEventBus.mergeEffects}
 *
 * The overall approach of this type of event processing is heavily based on the
 * pattern initially pioneered by @Day8/re-frame, with the following
 * differences:
 *
 * - stateless (see {@link EventBus} for the more common stateful alternative)
 * - standalone implementation (no assumptions about surrounding
 *   context/framework)
 * - manual control over event queue processing
 * - supports event cancellation (via FX_CANCEL side effect)
 * - side effect collection (multiple side effects for same effect type per
 *   frame)
 * - side effect priorities (to control execution order)
 * - dynamic addition/removal of handlers & effects
 */
export declare class StatelessEventBus implements IDispatch {
    state: any;
    protected eventQueue: Event[];
    protected currQueue: Maybe<Event[]>;
    protected currCtx: Maybe<InterceptorContext>;
    protected handlers: IObjectOf<Interceptor[]>;
    protected effects: IObjectOf<SideEffect>;
    protected priorities: EffectPriority[];
    /**
     * Creates a new event bus instance with given handler and effect
     * definitions (all optional).
     *
     * @remarks
     * In addition to the user provided handlers & effects, a number of
     * built-ins are added automatically. See
     * {@link StatelessEventBus.addBuiltIns}. User handlers can override
     * built-ins.
     *
     * @param handlers -
     * @param effects -
     */
    constructor(handlers?: IObjectOf<EventDef>, effects?: IObjectOf<EffectDef>);
    /**
     * Adds built-in event & side effect handlers.
     *
     * @remarks
     * Also see additional built-ins defined by the stateful {@link EventBus}
     * extension of this class, as well as comments for these class methods:
     *
     * - {@link StatelessEventBus.mergeEffects}
     * - {@link StatelessEventBus.processEvent}
     *
     * ### Handlers
     *
     * currently none...
     *
     * ### Side effects
     *
     * #### `FX_CANCEL`
     *
     * If assigned `true`, cancels processing of current event, though still
     * applies any side effects already accumulated.
     *
     * #### `FX_DISPATCH`
     *
     * Dispatches assigned events to be processed in next frame.
     *
     * #### `FX_DISPATCH_ASYNC`
     *
     * Async wrapper for promise based side effects.
     *
     * #### `FX_DISPATCH_NOW`
     *
     * Dispatches assigned events as part of currently processed event queue (no
     * delay).
     *
     * #### `FX_DELAY`
     *
     * Async side effect. Only to be used in conjunction with
     * `FX_DISPATCH_ASYNC`. Triggers given event after `x` milliseconds.
     *
     * ```js
     * import { FX_DELAY, FX_DISPATCH_ASYNC } from "@thi.ng/interceptors";
     *
     * // this triggers `[EV_SUCCESS, "ok"]` event after 1000 ms
     * { [FX_DISPATCH_ASYNC]: [FX_DELAY, [1000, "ok"], EV_SUCCESS, EV_ERROR] }
     * ```
     *
     * #### `FX_FETCH`
     *
     * Async side effect. Only to be used in conjunction with
     * `FX_DISPATCH_ASYNC`. Performs `fetch()` HTTP request and triggers success
     * with received response, or if there was an error with response's
     * `statusText`. The error event is only triggered if the fetched response's
     * `ok` field is non-truthy.
     *
     * - https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
     * - https://developer.mozilla.org/en-US/docs/Web/API/Response/statusText
     *
     * ```js
     * import { FX_FETCH, FX_DISPATCH_ASYNC } from "@thi.ng/interceptors";
     *
     * // fetches "foo.json" and then dispatches EV_SUCCESS or EV_ERROR event
     * { [FX_DISPATCH_ASYNC]: [FX_FETCH, "foo.json", EV_SUCCESS, EV_ERROR] }
     * ```
     */
    addBuiltIns(): any;
    addHandler(id: string, spec: EventDef): void;
    addHandlers(specs: IObjectOf<EventDef>): void;
    addEffect(id: string, fx: SideEffect, priority?: number): void;
    addEffects(specs: IObjectOf<EffectDef>): void;
    /**
     * Prepends given interceptors (or interceptor functions) to
     * selected handlers. If no handler IDs are given, applies
     * instrumentation to all currently registered handlers.
     *
     * @param inject -
     * @param ids -
     */
    instrumentWith(inject: (Interceptor | InterceptorFn)[], ids?: string[]): void;
    removeHandler(id: string): void;
    removeHandlers(ids: string[]): void;
    removeEffect(id: string): void;
    removeEffects(ids: string[]): void;
    /**
     * If called during event processing, returns current side effect
     * accumulator / interceptor context. Otherwise returns nothing.
     */
    context(): Maybe<InterceptorContext>;
    /**
     * Adds given events to event queue to be processed by
     * {@link StatelessEventBus.processQueue} later on.
     *
     * @remarks
     * It's the user's responsibility to call that latter function
     * repeatedly in a timely manner, preferably via
     * `requestAnimationFrame()` or similar.
     *
     * @param e -
     */
    dispatch(...e: Event[]): void;
    /**
     * Adds given events to whatever is the current event queue. If
     * triggered via the `FX_DISPATCH_NOW` side effect from an event
     * handler / interceptor, the event will still be executed in the
     * currently active batch / frame. If called from elsewhere, the
     * result is the same as calling {@link dispatch}.
     *
     * @param e -
     */
    dispatchNow(...e: Event[]): void;
    /**
     * Dispatches given event after `delay` milliseconds (by default
     * 17).
     *
     * @remarks
     * Since events are only processed by calling
     * {@link StatelessEventBus.processQueue}, it's the user's
     * responsibility to call that latter function repeatedly in a
     * timely manner, preferably via `requestAnimationFrame()` or
     * similar.
     *
     * @param e -
     * @param delay -
     */
    dispatchLater(e: Event, delay?: number): void;
    /**
     * Triggers processing of current event queue and returns `true` if
     * any events have been processed.
     *
     * @remarks
     * If an event handler triggers the `FX_DISPATCH_NOW` side effect,
     * the new event will be added to the currently processed batch and
     * therefore executed in the same frame. Also see {@link dispatchNow}.
     *
     * An optional `ctx` (context) object can be provided, which is used
     * to collect any side effect definitions during processing. This
     * can be useful for debugging, inspection or post-processing
     * purposes.
     *
     * @param ctx -
     */
    processQueue(ctx?: InterceptorContext): boolean;
    /**
     * Processes a single event using its configured handler/interceptor
     * chain. Logs warning message and skips processing if no handler is
     * available for the event type.
     *
     * @remarks
     * The array of interceptors is processed in bi-directional order.
     * First any `pre` interceptors are processed in forward order. Then
     * `post` interceptors are processed in reverse.
     *
     * Each interceptor can return a result object of side effects,
     * which are being merged and collected for
     * {@link StatelessEventBus.processEffects}.
     *
     * Any interceptor can trigger zero or more known side effects, each
     * (side effect) will be collected in an array to support multiple
     * invocations of the same effect type per frame. If no side effects
     * are requested, an interceptor can return `undefined`.
     *
     * Processing of the current event stops immediately, if an
     * interceptor sets the `FX_CANCEL` side effect key to `true`.
     * However, the results of any previous interceptors (incl. the one
     * which cancelled) are kept and processed further as usual.
     *
     * @param ctx -
     * @param e -
     */
    protected processEvent(ctx: InterceptorContext, e: Event): void;
    protected processForward(ctx: InterceptorContext, iceps: Interceptor[], e: Event): boolean;
    protected processReverse(ctx: InterceptorContext, iceps: Interceptor[], e: Event): void;
    /**
     * Takes a collection of side effects generated during event
     * processing and applies them in order of configured priorities.
     *
     * @param ctx -
     */
    protected processEffects(ctx: InterceptorContext): void;
    protected processEffect(ctx: InterceptorContext, effects: IObjectOf<SideEffect>, id: string, val: any): void;
    /**
     * Merges the new side effects returned from an interceptor into the
     * internal effect accumulator.
     *
     * @remarks
     * Any events assigned to the `FX_DISPATCH_NOW` effect key are
     * immediately added to the currently active event batch.
     *
     * If an interceptor wishes to cause multiple invocations of a
     * single side effect type (e.g. dispatch multiple other events), it
     * MUST return an array of these values. The only exceptions to this
     * are the following effects, which for obvious reasons can only
     * accept a single value.
     *
     * **Note:** the `FX_STATE` effect is not actually defined by this
     * class here, but is supported to avoid code duplication in
     * {@link EventBus}.
     *
     * - `FX_CANCEL`
     * - `FX_STATE`
     *
     * Because of this support (multiple values), the value of a single
     * side effect MUST NOT be a nested array itself, or rather its
     * first item can't be an array.
     *
     * For example:
     *
     * ```js
     * import { FX_DISPATCH } from "@thi.ng/interceptors";
     *
     * // interceptor result map to dispatch a single event
     * { [FX_DISPATCH]: ["foo", "bar"]}
     *
     * // result map format to dispatch multiple events
     * { [FX_DISPATCH]: [ ["foo", "bar"], ["baz", "beep"] ]}
     * ```
     *
     * Any `null` / `undefined` values directly assigned to a side
     * effect are ignored and will not trigger the effect.
     *
     * @param ctx -
     * @param ret -
     */
    protected mergeEffects(ctx: InterceptorContext, ret: any): void;
    protected interceptorsFromSpec(spec: EventDef): any;
}
/**
 * Stateful version of {@link StatelessEventBus}.
 *
 * @remarks
 * Wraps an [`IAtom`](https://docs.thi.ng/umbrella/atom/interfaces/IAtom.html)
 * state container (i.e. `Atom`/`Cursor`/`History`) and provides additional
 * pre-defined event handlers and side effects to manipulate wrapped state.
 * Prefer this as the default implementation for most use cases.
 */
export declare class EventBus extends StatelessEventBus implements IDeref<any>, IDispatch {
    readonly state: IAtom<any>;
    /**
     * Creates a new event bus instance with given parent state, handler and
     * effect definitions (all optional).
     *
     * @remarks
     * If no state is given, automatically creates an
     * [`Atom`](https://docs.thi.ng/umbrella/atom/classes/Atom.html) with empty
     * state object.
     *
     * In addition to the user provided handlers & effects, a number of
     * built-ins are added automatically. See {@link EventBus.addBuiltIns}. User
     * handlers can override built-ins.
     *
     * @param state -
     * @param handlers -
     * @param effects -
     */
    constructor(state?: IAtom<any> | null, handlers?: IObjectOf<EventDef>, effects?: IObjectOf<EffectDef>);
    /**
     * Returns value of internal state. Shorthand for:
     * `bus.state.deref()`
     */
    deref(): any;
    /**
     * Adds same built-in event & side effect handlers as in
     * `StatelessEventBus.addBuiltIns()` and the following additions:
     *
     * ### Handlers
     *
     * #### `EV_SET_VALUE`
     *
     * Resets state path to provided value. See
     * [`setIn`](https://docs.thi.ng/umbrella/paths/functions/setIn.html).
     *
     * Example event definition:
     * ```js
     * import { EV_SET_VALUE } from "@thi.ng/interceptors";
     *
     * [EV_SET_VALUE, ["path.to.value", val]]
     * ```
     *
     * #### `EV_UPDATE_VALUE`
     *
     * Updates a state path's value with provided function and optional extra
     * arguments. See
     * [`updateIn`](https://docs.thi.ng/umbrella/paths/functions/updateIn.html).
     *
     * Example event definition:
     * ```js
     * import { EV_UPDATE_VALUE } from "@thi.ng/interceptors";
     *
     * [EV_UPDATE_VALUE, ["path.to.value", (x, y) => x + y, 1]]
     * ```
     *
     * #### `EV_TOGGLE_VALUE`
     *
     * Negates a boolean state value at given path.
     *
     * Example event definition:
     * ```js
     * import { EV_TOGGLE_VALUE } from "@thi.ng/interceptors";
     *
     * [EV_TOGGLE_VALUE, "path.to.value"]
     * ```
     *
     * #### `EV_UNDO`
     *
     * Calls `ctx[id].undo()` and uses return value as new state. Assumes
     * `ctx[id]` is a
     * [`History`](https://docs.thi.ng/umbrella/atom/classes/History.html)
     * instance, provided via e.g. `processQueue({ history })`. The event can be
     * triggered with or without ID. By default `"history"` is used as default
     * key to lookup the `History` instance. Furthermore, an additional event
     * can be triggered based on if a previous state has been restored or not
     * (basically, if the undo was successful). This is useful for
     * resetting/re-initializing stateful resources after a successful undo
     * action or to notify the user that no more undo's are possible. The new
     * event will be processed in the same frame and has access to the
     * (possibly) restored state. The event structure for these options is shown
     * below:
     *
     * ```js
     * import { EV_UNDO } from "@thi.ng/interceptors";
     *
     * // using default ID
     * bus.dispatch([EV_UNDO]);
     *
     * // using custom history ID
     * bus.dispatch([EV_UNDO, ["custom"]]);
     *
     * // using custom ID and dispatch another event after undo
     * bus.dispatch([EV_UNDO, ["custom", ["ev-undo-success"], ["ev-undo-fail"]]]);
     * ```
     *
     * #### `EV_REDO`
     *
     * Similar to `EV_UNDO`, but for redo actions.
     *
     * ### Side effects
     *
     * #### `FX_STATE`
     *
     * Resets state atom to provided value (only a single update per processing
     * frame).
     */
    addBuiltIns(): any;
    /**
     * Triggers processing of current event queue and returns `true` if the any
     * of the processed events caused a state change.
     *
     * If an event handler triggers the `FX_DISPATCH_NOW` side effect, the new
     * event will be added to the currently processed batch and therefore
     * executed in the same frame. Also see {@link dispatchNow}.
     *
     * If the optional `ctx` arg is provided it will be merged into the
     * {@link InterceptorContext} object passed to each interceptor. Since the
     * merged object is also used to collect triggered side effects, care must
     * be taken that there're no key name clashes.
     *
     * In order to use the built-in `EV_UNDO`, `EV_REDO` events, users MUST
     * provide a
     * [`History`](https://docs.thi.ng/umbrella/atom/classes/History.html) (or
     * compatible undo history instance) via the `ctx` arg, e.g.
     *
     * ```
     * bus.processQueue({ history });
     * ```
     */
    processQueue(ctx?: InterceptorContext): boolean;
}
//# sourceMappingURL=event-bus.d.ts.map