import { Observable, OperatorFunction } from 'rxjs';
import { IStorage, IStorageBase } from '../../core';
import { Action, ActionsResult, DispatcherCore, DispatchFunction, ExtractResultType, WatcherFunction } from '../dispatcher';
import { ChunkRequestConsistent, ChunkRequestParallel } from './utils';
/**
 * Тип действия с типизированным payload
 */
export interface TypedAction<P> extends Action<P> {
    type: string;
    payload: P;
}
/**
 * Тип для внешних состояний — Observable или хранилище (IStorageBase), которое автоматически конвертируется в Observable
 */
export type ExternalStates = Record<string, Observable<any> | IStorageBase<any>>;
/**
 * Контекст эффекта — объект с зависимостями, передаваемый третьим аргументом
 */
export interface EffectContext<TDispatcher = any, TServices extends Record<string, any> = Record<string, never>, TConfig extends Record<string, any> = Record<string, never>, TExternalDispatchers extends Record<string, DispatcherCore<any, any>> = Record<string, never>, TExternalStates extends ExternalStates = Record<string, never>> {
    /** Основной dispatcher текущего synapse */
    dispatcher: TDispatcher;
    /** Внешние dispatcher'ы из других synapse */
    externalDispatchers: TExternalDispatchers;
    /** Внешние состояния — Observable'ы от других хранилищ (Synapse.state$, или любой Observable) */
    externalStates: TExternalStates;
    /** Сервисы (API-клиенты и т.д.) */
    services: TServices;
    /** Глобальная конфигурация для эффектов */
    config: TConfig;
}
/**
 * Тип для эффекта с доступом к состоянию и контексту — основной тип
 */
export type Effect<TState extends Record<string, any> = any, TDispatcher = any, TServices extends Record<string, any> = Record<string, never>, TConfig extends Record<string, any> = Record<string, never>, TExternalDispatchers extends Record<string, DispatcherCore<any, any>> = Record<string, never>, TExternalStates extends ExternalStates = Record<string, never>> = (action$: Observable<Action>, state$: Observable<TState>, context: EffectContext<TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>) => Observable<unknown>;
/**
 * Опции конкретного эффекта. Прикрепляются к функции-эффекту через {@link EFFECT_OPTIONS}
 * (это делает `Effects.effect(fn, options)` из базового класса). EffectsModule читает их
 * при подписке.
 */
export interface EffectOptions {
    /**
     * Переподписаться на поток при непойманной ошибке вместо терминального завершения.
     *
     * - `true` — бесконечный немедленный resubscribe;
     * - `{ count, delay }` — лимит ретраев и задержка (мс) между ними (см. rxjs `retry`).
     *
     * По умолчанию (опция не задана) — текущее поведение: ошибка завершает эффект,
     * остальные продолжают работать.
     */
    resubscribeOnError?: boolean | {
        count?: number;
        delay?: number;
    };
}
/**
 * Symbol-маркер, под которым опции эффекта ({@link EffectOptions}) хранятся на функции-эффекте.
 * @internal
 */
export declare const EFFECT_OPTIONS: unique symbol;
/**
 * Symbol-маркер с именем эффекта (имя поля class-слоя `Effects`). Проставляется
 * `Effects.getEffects()` для диагностики — EffectsModule использует его, чтобы в
 * предупреждении об упавшем эффекте назвать конкретный эффект.
 * @internal
 */
export declare const EFFECT_NAME: unique symbol;
/**
 * Тип для получения типов действий диспетчера
 */
export type DispatcherActions<T> = T extends DispatcherCore<any, infer A> ? ActionsResult<A> : Record<string, DispatchFunction<any, any>>;
/**
 * Конфигурация для валидации в validateMap
 */
export interface ValidateConfig {
    conditions: boolean[];
    /**
     * Что сделать, если валидация не прошла. Необязательно: если не задано — эффект просто
     * ничего не делает (поток завершается без эмита). Это убирает повторяющийся бойлерплейт
     * `skipAction: () => d.loadX.reset()` там, где сбрасывать нечего.
     */
    skipAction?: (() => any) | any | ((() => any) | any)[];
}
/**
 * Утилиты для запросов в validateMap
 */
export interface ValidateMapRequestUtils {
    chunkRequest: ChunkRequestParallel;
    chunkRequestConsistent: ChunkRequestConsistent;
}
/**
 * Оператор для фильтрации действий по типу с сохранением типа payload
 */
export declare function ofType<T extends DispatchFunction<any, any> | WatcherFunction<any>>(actionFn: T): OperatorFunction<Action, TypedAction<T extends WatcherFunction<infer R> ? R : ExtractResultType<T>>>;
/**
 * Оператор для фильтрации действий по нескольким типам с объединением типов payload
 * @param actionFns Массив функций действий
 */
export declare function ofTypes<T extends DispatchFunction<any, any>[]>(actionFns: [...T]): OperatorFunction<Action, TypedAction<ExtractResultType<T[number]>>>;
/**
 * Оператор для ожидания выполнения всех указанных действий.
 *
 * **Важно:** Использует `combineLatest` — Observable не эмитит, пока КАЖДЫЙ из
 * указанных action не будет диспатчнут хотя бы один раз. Если хотя бы один action
 * никогда не будет вызван, поток зависнет навсегда без уведомления.
 * Убедитесь, что все указанные actions гарантированно будут диспатчнуты,
 * либо используйте `ofTypes` с ручной агрегацией при необходимости таймаута.
 *
 * @param actionFns Массив функций действий
 */
export declare function ofTypesWaitAll<T extends DispatchFunction<any, any>[]>(actionFns: [...T]): (source$: Observable<Action>) => Observable<{ [K in keyof T]: TypedAction<ExtractResultType<T[K]>>; }>;
/**
 * Создает Observable с выбранными данными из состояния
 * @param state$ Поток состояния
 * @param selectors Селекторы для выбора частей состояния
 * @returns Observable с массивом выбранных значений
 */
export declare function selectorMap<TState, TResults extends any[]>(state$: Observable<TState>, ...selectors: {
    [K in keyof TResults]: (state: TState) => TResults[K];
}): Observable<TResults>;
/**
 * Создает именованный объект вместо массива
 * @param state$ Поток состояния
 * @param selectors Объект с селекторами
 * @returns Observable с объектом выбранных значений
 */
export declare function selectorObject<TState, TResult extends Record<string, any>>(state$: Observable<TState>, selectors: {
    [K in keyof TResult]: (state: TState) => TResult[K];
}): Observable<TResult>;
/**
 * Оператор наложения (flattening) для {@link requestMap} / {@link mutationMap}: задаёт стратегию
 * конкуренции между перекрывающимися срабатываниями (switchMap / exhaustMap / mergeMap / concatMap).
 * Сигнатура совпадает с rxjs-операторами, поэтому их можно передавать напрямую.
 */
export type FlattenOperator = <A, R>(project: (value: A) => Observable<R>) => OperatorFunction<A, R>;
/**
 * Общая конфигурация обработки запроса. Используется и для чтения ({@link validateMap}),
 * и для записи ({@link mutationMap}) — единый словарь.
 */
export interface RequestMapConfig<T, Body, TResult> {
    /** Гейт перед запросом: `conditions` все true → запрос; иначе `skipAction` (или no-op). */
    validator?: (value: T) => ValidateConfig;
    /**
     * Асинхронная сборка тела запроса ПЕРЕД apiCall (FormData, blob'ы, теги). Результат приходит
     * вторым аргументом в apiCall. Нет prepare → body = undefined. Для чтения обычно не нужен.
     */
    prepare?: (value: T) => Body | Promise<Body>;
    /** Вызывается после успешной валидации, перед apiCall. Типичное использование — dispatch loading-статуса. */
    loadingAction?: (value: T) => void;
    /** Вызывается при ошибке в apiCall (catchError). Получает ошибку + те же данные что loadingAction/apiCall. */
    errorAction?: (error: any, value: T) => void;
    /** Сам запрос: из value (+ собранного prepare тела) строим поток. Успех обрабатывается внутри через apiResult. */
    apiCall: (value: T, body: Body, utils: ValidateMapRequestUtils) => Observable<TResult>;
}
/**
 * Оператор для ЧТЕНИЯ (запросов-ресурсов): валидация → loading → apiCall, стратегия switchMap
 * («последний выигрывает», отменяет устаревший in-flight запрос). Для записи используйте
 * {@link mutationMap} — switchMap отменял бы in-flight мутацию (потеря ответа уже закоммиченной
 * записи, отмена первого сабмита вместо игнора дубля).
 *
 * @example
 * ```ts
 * action$.pipe(
 *   ofType(d.loadPosts),
 *   validateMap({
 *     validator: ([, { status }]) => ({ conditions: [status !== ApiStatus.Loading] }),
 *     loadingAction: () => d.loadPosts.loading(),
 *     errorAction: (err) => d.loadPosts.failure(getErrorMessage(err)),
 *     apiCall: ([action]) =>
 *       fromRequest(api.getPosts.request(action.payload)).pipe(
 *         apiResult((page) => { d.applyPosts(page); d.loadPosts.success() }),
 *       ),
 *   }),
 * )
 * ```
 */
export declare function validateMap<T, TResult = any>(config: {
    validator?: (value: T) => ValidateConfig;
    loadingAction?: (value: T) => void;
    errorAction?: (error: any, value: T) => void;
    apiCall: (value: T, utils: ValidateMapRequestUtils) => Observable<TResult>;
}): OperatorFunction<T, any>;
/**
 * Оператор для ЗАПИСИ (мутаций). Тот же словарь, что у {@link validateMap}, плюс два понятия:
 *  - `flatten` — стратегия конкуренции (rxjs-оператор). У записи нет одного правильного варианта,
 *    поэтому его выбирает вызывающий под смысл операции:
 *      • `exhaustMap` — одиночная операция (форма create/update): дабл-сабмит игнорируется,
 *        in-flight НЕ отменяется;
 *      • `mergeMap`   — операции над разными сущностями (delete/toggle/repost): параллельность;
 *      • `concatMap`  — строго по очереди.
 *  - `prepare` — асинхронная сборка тела (FormData, blob'ы) перед запросом; результат приходит
 *    вторым аргументом в apiCall.
 *
 * Успех/статусы ведутся ВНУТРИ apiCall через apiResult — единообразно с {@link validateMap}.
 *
 * @example
 * ```ts
 * action$.pipe(
 *   ofType(d.createPost),
 *   mutationMap({
 *     flatten: exhaustMap,
 *     loadingAction: () => d.createPost.loading(),
 *     errorAction: (err) => d.createPost.failure(getErrorMessage(err)),
 *     prepare: (payload) => buildCreateBody(api, payload),
 *     apiCall: (_payload, body) =>
 *       fromRequest(api.createPost.request({ body })).pipe(
 *         apiResult((post) => { d.createPost.success(); d.prependPost(post) }),
 *       ),
 *   }),
 * )
 * ```
 */
export declare function mutationMap<T, Body = void, TResult = any>({ flatten, validator, prepare, loadingAction, errorAction, apiCall, }: {
    flatten: FlattenOperator;
} & RequestMapConfig<T, Body, TResult>): OperatorFunction<T, any>;
/**
 * Метаданные ответа API, доступные в колбэках apiResult.
 */
export interface ApiResultMeta {
    status: number;
    statusText: string;
    headers: Headers;
    fromCache?: boolean;
}
/**
 * Ошибка API-запроса. Бросается apiResult при !result.ok.
 * Ловится errorAction в validateMap.
 */
export declare class ApiError extends Error {
    readonly originalError: any;
    readonly meta: ApiResultMeta;
    constructor(originalError: any, meta: ApiResultMeta);
}
/**
 * Оператор для обработки успешного результата API-запроса (QueryResult).
 *
 * При `result.ok` — вызывает callback с `data` и `meta`.
 * При `!result.ok` — бросает `ApiError`, который ловится `errorAction` в `validateMap`.
 *
 * @example
 * ```ts
 * // Простой случай
 * validateMap({
 *   errorAction: (err) => dispatcher.dispatch.loadError(String(err)),
 *   apiCall: () => from(api.request('getList', params)).pipe(
 *     apiResult((data) => dispatcher.dispatch.loadSuccess(data)),
 *   ),
 * })
 *
 * // С доступом к headers (пагинация)
 * apiResult((data, meta) => {
 *   const total = Number(meta.headers.get('X-Total-Count'))
 *   dispatcher.dispatch.loadSuccess({ items: data, total })
 * })
 * ```
 */
export declare function apiResult<TData, TResult = void>(onSuccess: (data: TData, meta: ApiResultMeta) => TResult | Promise<TResult>): OperatorFunction<{
    ok: boolean;
    data?: TData;
    error?: any;
    status?: number;
    statusText?: string;
    headers?: Headers;
    fromCache?: boolean;
}, TResult>;
/**
 * Класс для управления эффектами с поддержкой доступа к состоянию и контексту
 * Основной класс, который следует использовать
 */
export declare class EffectsModule<TState extends Record<string, any> = any, TDispatcher = any, TServices extends Record<string, any> = Record<string, never>, TConfig extends Record<string, any> = Record<string, never>, TExternalDispatchers extends Record<string, DispatcherCore<any, any>> = Record<string, never>, TExternalStates extends ExternalStates = Record<string, never>> {
    private storage;
    private dispatcher;
    private externalDispatchers;
    private services;
    private config;
    private effects;
    private subscriptions;
    private running;
    private action$;
    private externalStates;
    /**
     * Поток состояния
     */
    readonly state$: Observable<TState>;
    /**
     * Создает модуль эффектов
     * @param storage Хранилище состояния
     * @param dispatcher Основной dispatcher текущего synapse
     * @param externalDispatchers Внешние dispatcher'ы из других synapse
     * @param services Сервисы (API-клиенты и т.д.)
     * @param config Глобальная конфигурация для всех эффектов
     * @param externalStates Внешние состояния (Observable'ы от других хранилищ)
     */
    constructor(storage: IStorage<TState>, dispatcher: TDispatcher & {
        actions: Observable<Action>;
    }, externalDispatchers?: TExternalDispatchers, services?: TServices, config?: TConfig, externalStates?: TExternalStates);
    /**
     * Нормализует externalStates: конвертирует IStorageBase в Observable, пропускает Observable как есть
     */
    private normalizeExternalStates;
    /**
     * Подписывается на действия от основного dispatcher'а и внешних dispatcher'ов
     */
    private subscribeToDispatchers;
    add(effect: Effect<TState, TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>): this;
    /**
     * Добавляет несколько эффектов
     * @param effects Эффекты для добавления
     * @returns Текущий модуль
     */
    addEffects(effects: Effect<TState, TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>[]): this;
    /**
     * Запускает все эффекты
     * @returns Текущий модуль
     */
    start(): Promise<this>;
    /**
     * Останавливает все эффекты
     * @returns Текущий модуль
     */
    stop(): this;
    /**
     * Подписывается на конкретный эффект
     * @param effect Эффект для подписки
     */
    private subscribeToEffect;
}
/**
 * Вспомогательная функция для создания типизированного эффекта
 */
export declare function createEffect<TState extends Record<string, any>, TDispatcher = any, TServices extends Record<string, any> = Record<string, never>, TConfig extends Record<string, any> = Record<string, never>, TExternalDispatchers extends Record<string, DispatcherCore<any, any>> = Record<string, never>, TExternalStates extends ExternalStates = Record<string, never>>(effect: Effect<TState, TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>): Effect<TState, TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>;
/**
 * Объединяет несколько эффектов в один
 * @param effects Эффекты для объединения
 * @returns Объединенный эффект
 */
export declare function combineEffects<TState extends Record<string, any>, TDispatcher = any, TServices extends Record<string, any> = Record<string, never>, TConfig extends Record<string, any> = Record<string, never>, TExternalDispatchers extends Record<string, DispatcherCore<any, any>> = Record<string, never>, TExternalStates extends ExternalStates = Record<string, never>>(...effects: Effect<TState, TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>[]): Effect<TState, TDispatcher, TServices, TConfig, TExternalDispatchers, TExternalStates>;
