/*!
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
import { Context } from './rvx.js';
import { Component, Falsy } from './rvx.js';
import { View } from './rvx.js';
import { Expression } from './rvx.js';

/**
 * Create a new abort controller that aborts when the current lifecycle is disposed.
 */
declare function useAbortController(reason?: unknown): AbortController;
/**
 * Get an abort signal that aborts when the current lifecycle is disposed.
 */
declare function useAbortSignal(reason?: unknown): AbortSignal;

interface AsyncContextParent {
	/**
	 * Called by async contexts to track a pending task.
	 */
	track(task: Promise<unknown>): void;
}
/**
 * Represents pending operations in an asynchronously rendered tree.
 *
 * This can be used to wait until an entire async tree is rendered or to check if any unhandled errors occurred.
 */
declare class AsyncContext {
	#private;
	constructor(parent?: AsyncContextParent);
	/**
	 * Reactively check if there are any pending tasks in this context.
	 *
	 * @example
	 * ```tsx
	 * <Show when={() => asyncCtx.pending}>
	 *   <div class="overlay">Please wait...</div>
	 * </Show>
	 * ```
	 */
	get pending(): boolean;
	/**
	 * Track the specified task in this and all parent contexts.
	 */
	track(task: Promise<unknown>): void;
	/**
	 * Wait until all tracked tasks in this and all child contexts have completed.
	 *
	 * This also includes new tasks that are tracked while waiting.
	 *
	 * @throws Errors thrown by any tracked task or an {@link AsyncError} if multiple tasks failed.
	 */
	complete(): Promise<void>;
	/**
	 * Create a new async context using the current context as parent.
	 */
	static fork(): AsyncContext;
}
/**
 * Thrown by {@link AsyncContext.complete} if multiple unhandled {@link errors} occurred.
 */
declare class AsyncError extends Error {
	errors: unknown[];
	constructor(errors: unknown[]);
}
/**
 * Context for the current {@link AsyncContext}.
 */
declare const ASYNC: Context<AsyncContext | undefined>;

/**
 * Render content depending on the state of an async function or promise.
 *
 * See {@link Async `<Async>`} when using JSX or when named properties are preferred.
 *
 * This task is tracked using the current {@link ASYNC async context} if any. It is guaranteed that the view is updated before the tracked task completes.
 *
 * @param source The async function or promise.
 * + If this is a function, it runs {@link isolate isolated}.
 * @param component A component to render content when resolved.
 * + The resolved value is passed as the first argument.
 * + Nothing is rendered by default.
 * @param pending A component to render while pending.
 * + Nothing is rendered by default.
 * @param rejected A component to render content when rejected.
 * + The rejected error is passed as the first argument.
 * + Nothing is rendered by default.
 */
declare function nestAsync<T>(source: (() => Promise<T>) | Promise<T>, component?: Component<T>, pending?: Component, rejected?: Component<unknown>): View;
/**
 * Render content depending on the state of an async function or promise.
 *
 * See {@link nestAsync} when not using JSX or when positional arguments are preferred.
 *
 * This task is tracked using the current {@link ASYNC async context} if any. It is guaranteed, that the view is updated before the tracked task completes.
 */
declare function Async<T>(props: {
	/**
	 * The async function or promise.
	 *
	 * If this is a function, it runs {@link isolate isolated}.
	 */
	source: (() => Promise<T>) | Promise<T>;
	/**
	 * A component to render content when resolved.
	 *
	 * + The resolved value is passed as the first argument.
	 * + Nothing is rendered by default.
	 */
	children?: Component<T>;
	/**
	 * A component to render content while pending.
	 *
	 * + Nothing is rendered by default.
	 */
	pending?: Component;
	/**
	 * A component to render content when rejected.
	 *
	 * + The rejected error is passed as the first argument.
	 * + Nothing is rendered by default.
	 */
	rejected?: Component<unknown>;
}): View;

/**
 * A queue for sequentially running async tasks that can be triggered by both the user and side effects.
 */
declare class Queue {
	#private;
	/**
	 * Create a new queue.
	 *
	 * When the current lifecycle is disposed, all side effects are aborted and removed from the queue.
	 */
	constructor();
	/**
	 * Queue a side effect to run if this queue isn't currently blocked.
	 *
	 * This will abort and remove all other side effects from the queue.
	 *
	 * @param task The side effect to queue.
	 */
	sideEffect(task: (signal: AbortSignal) => unknown | Promise<unknown>): void;
	/**
	 * Queue a task to run and block this queue until it completes.
	 *
	 * This will abort and remove all other side effects from the queue.
	 *
	 * @param task The blocking task to queue.
	 * @returns The result of the task.
	 */
	block<T>(task: () => T | Promise<T>): Promise<T>;
}

type TaskSource = (() => unknown) | Promise<unknown> | null | undefined;
interface TasksOptions {
	/**
	 * If true, focus is restored on the last active element when there are no more pending tasks in this instance.
	 *
	 * By default, this is inherited from the parent or true of there is none.
	 */
	restoreFocus?: boolean;
}
/**
 * Represents a set of pending tasks in a specific context.
 *
 * This is meant to be used for preventing concurrent user interaction in a specific context.
 */
declare class Tasks {
	#private;
	/**
	 * Create a new tasks instance with the specified parent.
	 *
	 * @param parent The parent to use. Default is no parent.
	 */
	constructor(parent?: Tasks, options?: TasksOptions);
	/**
	 * The parent instance or undefined if there is none.
	 */
	get parent(): Tasks | undefined;
	/**
	 * True if this instance has any pending tasks.
	 *
	 * @example
	 * ```tsx
	 * <div inert={() => tasks.selfPending}>...</div>
	 * ```
	 */
	get selfPending(): boolean;
	/**
	 * True if this instance or any of its parents has any pending tasks.
	 *
	 * @example
	 * ```tsx
	 * <button disabled={() => tasks.pending}>...</button>
	 * ```
	 */
	get pending(): boolean;
	/**
	 * Pretend, that there is a pending task until the current lifecycle is disposed.
	 */
	setPending(): void;
	/**
	 * Wait for an async function or a promise.
	 *
	 * @param source The async function or promise to wait for.
	 * + If this is a function, it runs {@link isolate isolated}.
	 */
	waitFor(source: TaskSource): void;
	/**
	 * Create a new tasks instance using the current instance as parent.
	 */
	static fork(options?: TasksOptions): Tasks;
}
/**
 * Context for the current {@link Tasks} instance.
 */
declare const TASKS: Context<Tasks | undefined>;
/**
 * Check if there are any pending tasks in the current tasks instance.
 *
 * This can be used in conjunction with {@link Tasks.prototype.waitFor `TASKS.current.waitFor`} to indicate if there are any pending tasks.
 *
 * This is meant to be used for preventing concurrent user interaction in a specific context.
 *
 * @example
 * ```tsx
 * <div inert={isSelfPending}>...</div>
 * ```
 */
declare function isSelfPending(): boolean;
/**
 * Check if there are any {@link Tasks.prototype.pending pending} tasks in the current tasks instance or any of its parents.
 *
 * This can be used in conjunction with {@link Tasks.prototype.waitFor `TASKS.current.waitFor`} to disable inputs and buttons while there are any pending tasks.
 *
 * This is meant to be used for preventing concurrent user interaction in a specific context.
 *
 * @example
 * ```tsx
 * <button disabled={isPending}>...</button>
 * ```
 */
declare function isPending(): boolean;

/**
 * The same as {@link queueMicrotask}, but with context & lifecycle support.
 *
 * + If the current lifecycle is disposed, the callback is never called.
 * + The lifecycle within the callback is treated as the current lifecycle.
 * + The current context is available inside the callback.
 *
 * @param callback The callback to run as a microtask.
 * @throws An error if teardown hooks are explicitly un-supported in this context.
 */
declare function useMicrotask(callback: () => void): void;
/**
 * The same as {@link setTimeout}, but with context & lifecycle support.
 *
 * + If the current lifecycle is disposed, the timeout is {@link clearTimeout cleared}.
 * + The lifecycle within the callback is treated as the current lifecycle.
 * + The current context is available inside the callback.
 *
 * @param callback The callback to run.
 * @param timeout The timeout in milliseconds. See {@link setTimeout} for details.
 * @throws An error if teardown hooks are explicitly un-supported in this context.
 */
declare function useTimeout(callback: () => void, timeout: number): void;
/**
 * The same as {@link setInterval}, but with context & lifecycle support.
 *
 * + If the current lifecycle is disposed, the interval is {@link clearInterval cleared}.
 * + The lifecycle within the callback is disposed when the interval is cleared and before each call.
 * + The current context is available inside the callback.
 *
 * @param callback The callback to run.
 * @param interval The interval in milliseconds. See {@link setInterval} for details.
 * @throws An error if teardown hooks are explicitly un-supported in this context.
 */
declare function useInterval(callback: () => void, interval: number): void;
/**
 * Repeatedly {@link requestAnimationFrame request animation frames}.
 *
 * + If the current lifecycle is disposed, the latest request is cancelled.
 * + The lifecycle within the callback is disposed before each call and when the current lifecycle is disposed.
 * + The current context is available inside the callback.
 *
 * @param callback The callback to run with a {@link performance.now high resolution timestamp}.
 * @throws An error if teardown hooks are explicitly un-supported in this context.
 */
declare function useAnimation(callback: (now: number) => void): void;

type WatchGuardCondition<T, R extends T> = (value: T) => value is R;
type WatchCondition<T> = (value: T) => boolean;
declare class WatchForTimeoutError extends Error {
}
/**
 * Utility to watch an expression until its output satisfies a condition.
 *
 * @param expr The expression to watch.
 * @param condition The condition to test. By default, all truthy values are matched.
 * @param timeout An optional timeout. Default is no timeout.
 * @returns A promise that resolves with the first matched output or rejects with a {@link WatchForTimeoutError}.
 */
declare function watchFor<T>(expr: Expression<T | Falsy>, timeout?: number): Promise<T>;
declare function watchFor<T, R extends T>(expr: Expression<T>, condition?: WatchGuardCondition<T, R>, timeout?: number): Promise<R>;
declare function watchFor<T>(expr: Expression<T>, condition?: WatchCondition<T>, timeout?: number): Promise<T>;

export { ASYNC, Async, AsyncContext, AsyncError, Queue, TASKS, Tasks, WatchForTimeoutError, isPending, isSelfPending, nestAsync, useAbortController, useAbortSignal, useAnimation, useInterval, useMicrotask, useTimeout, watchFor };
export type { AsyncContextParent, TaskSource, TasksOptions, WatchCondition, WatchGuardCondition };
