import { Observable } from 'rxjs';
import { QueryClient, type QueryClientCtorOptions } from './client';
import { QueryCache, type QueryCacheMutation, type QueryCacheRecord } from './cache';
import type { CacheOptions, QueryFn, QueryOptions, QueryQueueFn, QueryTaskCached, QueryTaskCompleted } from './types';
import type { QueryCacheCtorArgs } from './cache/QueryCache';
import { QueryEvent, type IQueryEvent, type QueryEvents } from './events';
/**
 * Defines the constructor options for a QueryClient object.
 * It includes a query function and optional additional options for the client.
 *
 * @template TDataType - The type of the data returned by the query.
 * @template TQueryArguments - The type of the arguments passed to the query function.
 *
 * @property fn - The query function to be used for fetching data.
 * @property options - Optional additional options for the client.
 *
 * @see {@link QueryClient} - The QueryClient class.
 * @see {@link QueryClientCtorOptions} - The constructor options for the QueryClient class.
 */
interface QueryClientOptions<TDataType, TQueryArguments> {
    fn: QueryFn<TDataType, TQueryArguments>;
    options?: QueryClientCtorOptions;
}
/**
 * Defines the constructor options for a Query object.
 *
 * @template TDataType - The type of the data returned by the query.
 * @template TQueryArguments - The type of the arguments passed to the query function.
 *
 * @property client - The client instance or configuration to be used for fetching data.
 * @property key - A function that generates a unique key for caching query results based on the arguments.
 * @property cache - The cache instance or configuration to be used for caching query results.
 * @property validate - A function that validates a cache entry before it is used.
 * @property expire - The number of milliseconds before a cache entry expires.
 * @property queue - The queueing strategy to use for the query.
 * @property logger - The logger instance to use for logging events and operations within the Query class.
 *
 * @see {@link QueryClient} - The QueryClient class.
 * @see {@link CacheOptions} - The options for the cache instance.
 * @see {@link QueryCache} - The QueryCache class.
 * @see {@link QueueOperatorType} - The type of queueing strategy.
 * @see {@link QueryQueueFn} - The type of the queueing strategy function.
 */
export type QueryCtorOptions<TDataType, TQueryArguments> = {
    /**
     * The client instance or configuration to be used for fetching data.
     * It can either be an instance of QueryClient or an object with a 'fn' property
     * that is a query function and an optional 'options' object for additional QueryClient options.
     */
    client: QueryClient<TDataType, TQueryArguments> | QueryClientOptions<TDataType, TQueryArguments>;
    /**
     * A function that generates a unique key for caching query results based on the arguments.
     * This key is used to store and retrieve cache entries.
     */
    key: CacheOptions<TDataType, TQueryArguments>['key'];
    /**
     * An optional function to validate cache entries. It receives a cache entry and the arguments
     * and returns a boolean indicating whether the cache entry is still valid.
     */
    validate?: CacheOptions<TDataType, TQueryArguments>['validate'];
    /**
     * An optional instance of QueryCache or constructor arguments for creating a new QueryCache instance.
     * If not provided, a new QueryCache will be created with default options.
     */
    cache?: QueryCache<TDataType, TQueryArguments> | QueryCacheCtorArgs<TDataType, TQueryArguments>;
    /**
     * The expiration time of the cache in milliseconds.
     * If undefined or 0, caching is disabled.
     * This attribute is used only when the 'validate' function is not provided.
     */
    expire?: number;
    /**
     * Queue strategies determine how multiple concurrent query requests are handled.
     *
     * - 'switch': This strategy cancels the current active request when a new request comes in.
     *   Only the result from the latest request will be returned. This is useful when only the latest data is relevant.
     *
     * - 'merge': With this strategy, multiple requests can run in parallel without canceling each other.
     *   All responses will be returned as they arrive. This is useful when all requests need to be resolved,
     *   regardless of the order in which they were initiated.
     *
     * - 'concat': This strategy queues requests and executes them one after another in a sequential manner.
     *   A new request will only start after the previous one has completed. This is useful when the order of
     *   execution is important and each request must be completed before the next begins.
     */
    queueOperator?: QueueOperatorType | QueryQueueFn<TDataType, TQueryArguments>;
};
/**
 * A type alias for the predefined operator types that can be used to control query request queuing.
 * These operator types are used to define different strategies for handling concurrent query requests.
 */
type QueueOperatorType = 'switch' | 'merge' | 'concat';
/**
 * The primary use case for `Query` involves:
 * - Asynchronous Data Fetching: Seamlessly fetching data from APIs or databases asynchronously without blocking the UI, improving the user experience.
 * - Caching: Storing fetched data in a cache to improve performance by reducing the number of redundant requests to the server.
 * - Automatic Updates: Automatically updating the UI when the underlying data changes, without requiring explicit refresh actions from the user.
 * - Concurrent Requests Management: Efficiently handling multiple, concurrent data fetches through strategies like merging, switching, or concatenating requests.
 * - Retry and Error Handling: Automatically retrying failed requests and handling errors gracefully to ensure application stability.
 *
 * ## Benefits
 *
 * Using a `Query` mechanism offers numerous benefits, including:
 * 1. Improved Performance and Efficiency: By caching responses and reducing unnecessary server requests, applications load faster and use fewer resources, both on the client and server side.
 * 2. Simplified Data Fetching Logic: It abstracts away the boilerplate code associated with fetching data, handling errors, and managing response states, leading to cleaner and more maintainable code.
 * 3. Automatic Synchronization: `Query` libraries often come with features to automatically refetch data on certain triggers (e.g., window focus), ensuring the UI is always up-to-date with the latest server state without manual intervention.
 * 4. Built-in Asynchronous Management: Handling asynchronous data fetches becomes straightforward, with built-in support for loading states, error handling, and data updates.
 * 5. Scalability: Easily scalable for complex applications, supporting various fetching strategies to manage multiple data sources, endpoints, and concurrent requests effectively.
 * 6. Developer Experience: By standardizing the approach to data fetching and state management, it enhances developer experience, reducing the cognitive load and making it easier to onboard new developers.
 * 7. Robust Error and Retry Handling: Features to automatically retry requests and sophisticated mechanisms for error handling improve application reliability.
 * 8. Customizable and Extendable: While offering sensible defaults for most use cases, `Query` implementations are usually highly customizable, allowing developers to tailor their behavior for specific needs, such as custom caching strategies, query deduplication, and more.
 *
 * ## Examples
 * Example of creating a basic query with a query function:
 * @example
 * ```typescript
 * import { Query, QueryFn } from '@equinor/fusion-query';
 *
 * type ExampleData = {
 *  id: string;
 *  name: string;
 *  value: number;
 * }
 *
 * // create a query function
 * const queryFn: QueryFn<ExampleData, {id: string}>  = async (args, signal) => {
 *   const response = await fetch(`https://api.example.com/data?id=${args.id}`, {signal});
 *   return response.json();
 * };
 *
 * const queryOptions = QueryCtorOptions<ExampleData, {id: string}> = {
 *  client: { fn: queryFn },
 *  key: (args) => args.id,
 *  // optional cache options
 *  // optional queue operator
 *  // optional logger
 * };
 *
 * // create a new Query instance with the options
 * const query = new Query(queryOptions);
 *
 * ```
 *
 * @see {@link QueryCtorOptions} for more details on the constructor options.
 * @see {@link Query.query} for more details on executing a query.
 * @see {@link Query.queryAsync} for more details on executing a query asynchronously.
 * @see {@link Query.mutate} for more details on mutating cache entries.
 * @see {@link Query.invalidate} for more details on invalidating cache entries.
 * @see {@link QueueOperatorType} for more details on the available queue operators.
 */
export declare class Query<TDataType, TQueryArguments = any> {
    #private;
    /**
     * Static utility that extracts the raw value from a query result Observable.
     *
     * Transforms a stream of `QueryTaskValue<TType>` into a plain `Observable<TType>`,
     * stripping away query metadata such as status, transaction, and timestamps.
     *
     * @see {@link queryValue} for the standalone operator function.
     */
    static extractQueryValue: <TType, TArgs>(source$: ReturnType<Query<TType, TArgs>["query"]>) => Observable<TType>;
    /**
     * A public getter for the client instance.
     * TODO: Implement a proxy to control access to the client.
     * This proxy would allow for additional functionality or restrictions when accessing the client instance.
     */
    get client(): QueryClient<TDataType, TQueryArguments>;
    /**
     * Protected helper method to register events if an event observer is configured.
     * @param type - The event type
     * @param data - The event data
     */
    protected _registerEvent<TKey extends keyof QueryEvents>(type: TKey, key: string, data?: QueryEvents[TKey] extends QueryEvent<infer T> ? T : undefined): void;
    /**
     * A public getter for the cache instance.
     * TODO: Implement a proxy to control access to the cache.
     * This proxy would allow for additional functionality or restrictions when accessing the cache instance.
     */
    get cache(): QueryCache<TDataType, TQueryArguments>;
    /**
     * An Observable stream of Query events. It allows subscribers to react to
     * query lifecycle events such as query creation, cache hits, task execution, etc.
     * Also includes QueryClient events for complete observability.
     * @returns {Observable<CombinedQueryEvent<TDataType, TQueryArguments>>} An Observable stream of events.
     */
    get event$(): Observable<IQueryEvent>;
    /**
     * The constructor for the Query class.
     * It initializes the query client, cache, and sets up the query request queue.
     * The constructor takes an options object which can include a custom client, cache, key generation function,
     * cache validation function, cache expiration time, queuing strategy, and a logger.
     *
     * @param options - The constructor options for the Query instance.
     */
    constructor(options: QueryCtorOptions<TDataType, TQueryArguments>);
    /**
     * Executes a query and returns an Observable that emits the result.
     * It will throw an error if the query was skipped or canceled.
     * The returned Observable can be subscribed to in order to receive updates on the query's execution and results.
     *
     * @param args - The arguments to be passed to the query function.
     * @param options - Optional additional options for the query.
     * @returns An Observable that emits the result of the query.
     */
    query(args: TQueryArguments, options?: QueryOptions<TDataType, TQueryArguments>): Observable<QueryTaskCached<TDataType> | QueryTaskCompleted<TDataType>>;
    /**
     * Executes an asynchronous query and returns a Promise that resolves with the result.
     * If `skipResolve` is set to true, the Promise resolves as soon as the query is sent (using `firstValueFrom`).
     * If `skipResolve` is false or not provided, the Promise resolves with the final result of the query (using `lastValueFrom`).
     * This method is useful for cases where an asynchronous, one-time result is needed rather than a stream of updates.
     * Note that skipping resolution may result in returning invalid cache.
     *
     * @example
     * ```typescript
     * try{
     *  const result = await query.queryAsync({ id: '123' });
     * console.log(result);
     * } catch (error) {
     *  console.error(error);
     * }
     * ```
     *
     * @param payload - The arguments to be passed to the query function.
     * @param opt - Optional additional options for the query. The `skipResolve` option determines the resolution behavior of the Promise.
     * @returns A Promise that resolves with the result of the query or rejects with an Error.
     */
    queryAsync(payload: TQueryArguments, opt?: QueryOptions<TDataType, TQueryArguments> & {
        skipResolve?: boolean;
    }): Promise<QueryTaskCached<TDataType> | QueryTaskCompleted<TDataType>>;
    /**
     * Executes a query that remains subscribed to cache mutations after the initial fetch completes.
     *
     * Unlike {@link Query.query}, which completes after emitting the result, `persistentQuery`
     * continues to emit whenever the underlying cache entry is updated or mutated.
     * This is useful for scenarios where the UI must reflect optimistic updates,
     * background refetches, or external cache mutations in real time.
     *
     * The returned Observable deduplicates emissions based on the transaction identifier
     * and mutation timestamp, so subscribers only receive meaningful state changes.
     *
     * @param args - The arguments to pass to the query function.
     * @param options - Optional query options including signal, retry, and cache validation overrides.
     * @returns An Observable that emits cached and completed results, and continues emitting on cache mutations.
     */
    persistentQuery(args: TQueryArguments, options?: QueryOptions<TDataType, TQueryArguments>): Observable<QueryTaskCached<TDataType> | QueryTaskCompleted<TDataType>>;
    /**
     * Generates a cache key based on the provided query arguments.
     * This method is used internally to uniquely identify cache entries.
     *
     * @param args - The query arguments to be used for generating the cache key.
     * @returns A string representing the cache key.
     */
    generateCacheKey(args: TQueryArguments): string;
    /**
     * Performs a mutation on the cache entry associated with the given arguments.
     * This method allows for updating the state of a cache entry without needing to perform a new query.
     * The changes are applied by invoking the `mutate` method on the cache with the generated key and the changes function.
     *
     * @param args - The arguments that identify the specific cache entry to be mutated.
     * @param changes - A function that defines the changes to be applied to the cache entry.
     */
    mutate(args: TQueryArguments, changes: Parameters<QueryCache<TDataType, TQueryArguments>['mutate']>[1], options?: {
        allowCreation?: boolean;
    }): VoidFunction;
    /**
     * Invalidates a specific cache record or all records if no arguments are provided.
     * When a specific record is invalidated, it is identified by the provided arguments.
     * If no arguments are provided, all records in the cache are invalidated, effectively clearing the cache.
     *
     * @param args - Optional arguments that identify the specific cache record to be invalidated.
     *             If not provided, all cache records will be invalidated.
     */
    invalidate(args?: TQueryArguments): void;
    /**
     * Completes all subscriptions and cleans up resources.
     * This method should be called when the Query instance is no longer needed, to ensure that all resources are properly released.
     * Failing to call `complete` could result in memory leaks due to lingering subscriptions.
     */
    complete(): void;
    /**
     * Registers a callback function that will be invoked when a cache invalidation occurs.
     * The callback function will receive an event object containing the details of the invalidation,
     * including the affected cache entry, if available.
     * The returned function can be called to unsubscribe the callback from further invalidation events.
     *
     * @param cb - The callback function to be registered.
     * @returns A function that, when called, will unsubscribe the callback from further invalidation events.
     */
    onInvalidate(cb: (e: {
        detail: {
            item?: QueryCacheRecord;
        };
    }) => void): VoidFunction;
    /**
     * Registers a callback function that will be invoked when a mutation occurs on the cache.
     * The callback function will receive an event object containing the details of the mutation,
     * including the changes made and the current state of the cache entry, if available.
     *
     * @param cb - The callback function to be registered.
     * @returns A function that, when called, will unsubscribe the callback from further mutation events.
     */
    onMutate(cb: (e: {
        detail: {
            changes: QueryCacheMutation;
            current?: QueryCacheRecord<TDataType, TQueryArguments>;
        };
    }) => void): VoidFunction;
    /**
     * Internal method that executes a query and returns an Observable with the result.
     *
     * @param args - The arguments to be passed to the query function.
     * @param options - Optional additional options for the query.
     * @returns An Observable that emits the result of the query.
     */
    protected _query(args: TQueryArguments, options?: QueryOptions<TDataType, TQueryArguments>): Observable<QueryTaskCached<TDataType> | QueryTaskCompleted<TDataType>>;
    /**
     * The `_createTask` method is responsible for initiating a query task, which involves either returning a cached result or
     * starting a new query request. This method encapsulates the logic for creating and managing the lifecycle of a query task.
     * It ensures that if a query with the same arguments is initiated multiple times, they will all share the same task observable,
     * thus avoiding duplicate network requests and unnecessary computation.
     *
     * @param key - A string that uniquely identifies the cache entry associated with the query arguments. This key is used to
     *              check if there is already a cached result that can be returned immediately, avoiding the need for a new network request.
     * @param args - The arguments that will be passed to the query function. These arguments are used to generate the cache key
     *               and are also passed to the query function when making a new request.
     * @param options - An optional object containing additional options for the query. This can include custom cache validation
     *                  logic and retry strategies. For example, options can specify whether to suppress the emission of invalid
     *                  cache entries or to define a custom function for validating the cache.
     *
     * @returns An Observable that emits the result of the query task. If a valid cache entry is found, the Observable will emit
     *          the cached result. If no valid cache entry exists, the Observable will emit the result of a new query request once
     *          it completes. Subscribers to this Observable will receive updates on the query's execution and results.
     *
     * The method performs the following steps:
     * 1. Creates a new Observable that represents the task to be executed.
     * 2. Attempts to retrieve a cache entry using the provided `key`.
     * 3. If a cache entry is found, it checks whether the entry is still valid using the provided validation function or the default one.
     *    - If the cache entry is valid, it emits the cached result and completes the Observable.
     *    - If the cache entry is not valid or does not exist, it proceeds to the next step.
     * 4. Checks if there is already an ongoing task for the same query (identified by the `key`).
     *    - If there is an ongoing task, it subscribes the new Observable to the existing task.
     *    - If there is no ongoing task, it creates a new `QueryTask` instance and adds it to the ongoing tasks record.
     * 5. Subscribes to the new or existing task, forwarding any emissions to the subscriber of the returned Observable.
     * 6. Adds the new task to the query queue by emitting the `key` on the `#queue$` Subject, which will eventually trigger the processing
     *    of the task based on the queuing strategy.
     * 7. The task is finalized (removed from the ongoing tasks record) when it completes or errors out.
     *
     * Note: The subscribers to the returned Observable are responsible for subscribing and unsubscribing to manage the lifecycle of the
     *       subscription. The method ensures that the task is properly cleaned up when it is no longer needed.
     */
    protected _createTask(key: string, args: TQueryArguments, options?: QueryOptions<TDataType, TQueryArguments>): Observable<QueryTaskCached<TDataType> | QueryTaskCompleted<TDataType>>;
}
export default Query;
