import { ReactElement, Ref, RefAttributes } from 'react';

interface Disposable {
    /** Release any memory that would otherwise leak */
    dispose: () => void;
}
declare type Falsy = false | null | undefined;

/** For accessing the proxied state */
declare const $$: unique symbol;
/** For storing observers and the observable proxy */
declare const $O: unique symbol;
/** For injecting custom proxy traps */
declare const $T: unique symbol;

/** Watch the properties of the given observable object */
declare const shallowChanges: (target: ObserverTarget, onChange: (change: Change) => void) => {
    onChange: (change: Change) => void;
    dispose(): void;
};
/** A function that can subscribe to observable objects. */
declare abstract class Observer implements Disposable {
    observed: ReadonlySet<ObservedSlot>;
    onChange: ((change: Change) => void) | null;
    /** The current nonce of our observed values combined */
    get nonce(): number;
    dispose(): void;
}
/** @internal */
declare class Observable<T extends object = any> extends Map<ObservedKey, ObservedSlot> {
    readonly source?: T | undefined;
    readonly proxy: T | undefined;
    constructor(source?: T | undefined);
    /** Return true if at least one observer exists for the given key. */
    has(key: ObservedKey): boolean;
    /** Return a `Set` of observers for the given key, even if not observed. */
    get(key: ObservedKey): ObservedSlot;
    /** Create an observer for the given key. */
    observe(key: ObservedKey, onChange: (change: Change) => void): {
        onChange: (change: Change) => void;
        dispose(): void;
    };
}
/** Mutable state with an associated observable */
declare type ObserverTarget = object & {
    [$O]?: Observable;
};
/** Any value acting as an object key */
declare type ObservedKey = any;
/** An observer set with metadata about what's being observed */
declare class ObservedSlot extends Set<ChangeObserver> {
    readonly owner: Observable;
    readonly key: ObservedKey;
    nonce: number;
    constructor(owner: Observable, key: ObservedKey);
    onChange(change: Change): void;
}
/** An observed mutation of an observable object. */
interface Change<T = any> {
    op: 'add' | 'replace' | 'remove' | 'splice' | 'clear' | 'define';
    target: object;
    key?: any;
    value?: T;
    oldValue?: T;
}
/** The most basic observer of changes. */
interface ChangeObserver extends Disposable {
    onChange: ((change: Change) => void) | null;
}

/** Run an effect when its tracked values change. */
declare function auto(effect: () => void, config?: AutoConfig): Auto;
interface AutoConfig {
    /** When true, react to changes immediately. By default, changes are delayed until the next microtask loop */
    sync?: boolean;
    /** By default, rerun the last effect (after any delay) */
    onDirty?: Auto['onDirty'];
    /** By default, errors are rethrown */
    onError?: Auto['onError'];
    /** Run an effect after an `observer` is activated */
    onCommit?: Auto['onCommit'];
    /** Run an effect after being disposed */
    onDispose?: Auto['onDispose'];
}
declare class Auto {
    sync: boolean;
    dirty: boolean;
    nonce: number;
    observer: AutoObserver | null;
    onDirty: (this: Auto) => void;
    onError: (this: Auto, error: Error) => void;
    onCommit: (observer: AutoObserver) => void;
    onDispose: () => void;
    constructor(config?: AutoConfig);
    run<T extends Function>(compute: T): any;
    /** Rerun the last effect and commit its observer */
    rerun(): any;
    /**
     * Replace the current `observer`.
     *
     * Pass a nonce to bail out if observed values have since changed.
     */
    commit(observer: AutoObserver, nonce?: number): boolean;
    dispose(): void;
    /**
     * @internal
     * Create an observer and start observing.
     *
     * The given `effect` is used by `rerun` calls made after
     * the new observer is committed.
     */
    start(effect: Function): AutoObserver;
    /**
     * @internal
     * Stop observing and reset the `dirty` flag.
     */
    stop(): this;
    /**
     * @internal
     * Replace the current `observer` when appropriate.
     */
    clear(): void;
    protected _onChange(change: Change): void;
    protected _onDirty(): void;
}
declare class AutoObserver extends Observer {
    readonly effect: Function;
    observed: Set<ObservedSlot>;
    constructor(effect: Function);
}

/**
 * An observable getter that memoizes its result.
 *
 * The memoization is observed, so when a dependency changes,
 * the memoized value is released and observers are notified.
 */
interface Derived<T = any> extends Disposable {
    /** The underlying observable */
    [$O]?: Observable;
    /** The underlying observer */
    auto: Auto;
    /** Get the current value */
    (): T;
}
declare function isDerived(value: unknown): value is Derived;
/** Convert all `Derived<T>` property types into `T` */
declare type WithDerived<T extends object> = {
    [P in keyof T]: T[P] extends Derived<infer U> ? U : T[P];
};

/**
 * Pass an **object** to receive an observable proxy.
 * Pass a **function** to receive an observable getter.
 * Anything else is returned as-is.
 */
declare function o<T>(value: T): T extends () => infer U ? Derived<U> : T extends Function ? Derived : T;

/**
 * Create a promise to resolve when the given condition returns true.
 * Any observable access in the condition is tracked.
 */
declare const when: (condition: () => boolean) => Promise<void>;

declare type WatchedState = ObserverTarget & {
    forEach?: (cb: (value: any, key: any, ctx: any) => void) => void;
};
declare type ChangeHandler = (change: Change) => void;
/**
 * Watch a single property.
 * If its value is observable, watch it recursively.
 */
declare function watch<P>(root: Map<P, any>, key: P, onChange: (change: Change) => void): Watcher;
declare function watch<T extends object, P extends string & keyof T>(root: T, key: P, onChange: (change: Change) => void): Watcher;
/**
 * Watch an observable tree for changes.
 * Only observable objects are searched for watchable values.
 */
declare function watch(root: object, onChange: ChangeHandler): Watcher;
/** An observer of deep changes */
declare class Watcher extends Observer {
    readonly root: object;
    readonly key?: any;
    observed: Set<ObservedSlot>;
    counts: Map<object, number>;
    constructor(root: object, onChange: ChangeHandler, key?: any);
    watch: (value: any, key?: any, ctx?: any) => void;
    unwatch: (value: any, key?: any, ctx?: any) => void;
    dispose(): void;
    protected _watch(target: WatchedState): void;
    protected _unwatch(target: WatchedState): void;
}

/**
 * Get the original object from an observable proxy,
 * or wrap a function to disable observation inside it.
 *
 * Read `no` as "non-observable", essentially the reverse of the `o` function.
 */
declare function no<T>(value: T): T;

/** Run an effect without any observable tracking */
declare function noto<T>(effect: () => T): T;

interface DebugState {
    name: string;
    actions?: any[];
    renders?: number;
}
/** Get the `Auto` object for the current `withAuto` component being rendered. */
declare function getCurrentAuto(): Auto | null;
/**
 * Get the `DebugState` object of the `target` object.
 *
 * Returns `undefined` if target was never passed to `setDebug`.
 */
declare function getDebug(target: object): DebugState;
/**
 * Set the `DebugState` object of the `target` object.
 */
declare function setDebug<T>(target: T, debug: DebugState): T;
/** Safely add an action to a `DebugState` object */
declare function addDebugAction(target: any, action: any): void;

declare type OnRender = (auto: Auto, depth: number, component: React.FunctionComponent<any>) => void;
declare type Globals = {
    /** Notify the current observer. */
    observe: ((target: ObserverTarget, key: any) => void) | null;
    /** The `Auto` object for the current `withAuto` component being rendered. */
    auto: Auto | null;
    /** For debugging re-renders. Only called in development. */
    onRender: OnRender | null;
    /** For spying on every change event. */
    onChange: ((change: Change) => void) | null;
};
declare const globals: Globals;

/**
 * Flush the queue of delayed reactions.
 *
 * Returns `true` if the queue wasn't flushed entirely.
 *
 * Useful when testing `wana`-integrated components/hooks.
 */
declare function flushSync(): boolean;

interface Props {
    observer?: AutoObserver;
    mounted?: boolean;
}
/**
 * Call the returned function to set the "mounting state" for the
 * given `Auto` object. Observation is postponed until mounted.
 */
declare function mountAuto(auto: Auto): (props: Props) => void;

declare const ObjectTraps: ProxyHandler<object>;

declare function useO<T extends object>(state: Exclude<T, Function>, deps?: readonly any[]): WithDerived<T>;
/**
 * Create an observable getter that is managed by React.
 * This lets you memoize an expensive combination of observable values.
 */
declare function useO<T>(create: () => () => T, deps?: readonly any[]): Derived<T>;
/** Create observable component state. */
declare function useO<T>(create: () => Exclude<T, Function>, deps?: readonly any[]): T;
/** Memoize an object and return its observable proxy. Non-objects are returned as-is. */
declare function useO<T>(state: T, deps?: readonly any[]): T;

declare type Deps$1 = readonly any[];
declare type EffectReturn = void | (() => void | undefined);
/** Wrap a `useEffect` call with magic observable tracking */
declare function useAuto(effect: () => EffectReturn, deps?: Deps$1): Auto;
/**
 * Combine a `useEffect` call with an `Auto` instance that invokes an
 * unobserved side `effect` when the `compute` function returns a new value.
 */
declare function useAuto<T>(compute: () => T, effect: (value: T) => EffectReturn, deps?: Deps$1): Auto;

interface Component<P = any> {
    (props: P): ReactElement | null;
    displayName?: string;
}
interface RefForwardingComponent<T = any, P = any> {
    (props: P, ref: Ref<T>): ReactElement | null;
    displayName?: string;
}
declare type RefForwardingAuto<T extends RefForwardingComponent> = T & ((props: T extends RefForwardingComponent<infer U, infer P> ? P & RefAttributes<U> : never) => ReactElement | null);
/** Wrap a component with magic observable tracking */
declare function withAuto<T extends Component>(render: T): T;
/** Wrap a component with `forwardRef` and magic observable tracking */
declare function withAuto<T extends RefForwardingComponent>(render: T): RefForwardingAuto<T>;

/**
 * Create an observable getter that is managed by React.
 * This lets you memoize an expensive combination of observable values.
 *
 * If the `compute` argument changes over the course of a component's lifetime,
 * its value should be added to the `deps` array.
 *
 * When `deps` are changed, the derived state is reset. This is useful when
 * the `compute` function is using a variable from another function scope.
 */
declare function useDerived<T>(compute: () => T, deps?: readonly any[]): Derived<T>;
declare function useDerived<T>(compute: (() => T) | Falsy, deps?: readonly any[]): Derived<T> | null;
declare function useDerived<T>(compute: () => T, discard: (memo: T, oldMemo: T | undefined) => boolean, deps?: readonly any[]): Derived<T>;
declare function useDerived<T>(compute: (() => T) | Falsy, discard: (memo: T, oldMemo: T | undefined) => boolean, deps?: readonly any[]): Derived<T> | null;

/** Listen for shallow changes to an observable object. */
declare function useChanges(target: ObserverTarget, onChange: (change: Change) => void, deps?: readonly any[]): void;

declare type Deps = readonly any[];
declare type UnmountFn = () => undefined | void;
declare type Source<T = any> = {
    [key: string]: T;
} | ReadonlyArray<T> | ReadonlySet<T> | ReadonlyMap<any, T>;
declare type Effect<T extends Source> = T extends Source<infer U> ? T extends ReadonlyMap<infer P, U> ? (value: U, key: P) => UnmountFn | void : T extends ReadonlyArray<U> | ReadonlySet<U> ? (value: U) => UnmountFn | void : (value: U, key: string) => UnmountFn | void : never;
/**
 * Create a layout effect for every key of your `values` object.
 *
 * Values are passed to your `effect` function as they are added and replaced.
 * The `effect` can return a cleanup function to call for removed values.
 *
 * Map objects use their keys
 */
declare function useEffects<T extends Source>(source: T, effect: Effect<T>, deps?: Deps): void;

/**
 * An alternative to `withAuto` that re-renders the caller
 * when the given observable value is changed.
 *
 * If you pass an object without a key, the entire object
 * is observed.
 *
 * ⚠️ This hook has performance drawbacks! It cannot automatically
 * batch React updates, so you need to wrap changes with `batchedUpdates`
 * to avoid multiple re-renders. It also cannot wait for ancestor
 * components to re-render first, which also leads to excessive re-renders.
 */
declare function useBinding<T extends object, P extends keyof T>(target: T extends ReadonlyMap<any, any> ? never : T, key: P): T[P];
declare function useBinding<K, V>(target: ReadonlyMap<K, V>, key: K): V | undefined;
declare function useBinding<T>(target: Derived<T>): T;
declare function useBinding<T extends object>(target: T): T;

/** @internal */
declare const useAutoContext: () => {
    depth: number;
};

export { $$, $O, $T, Auto, AutoConfig, Change, ChangeObserver, Derived, ObjectTraps, Observable, ObservedSlot, Observer, ObserverTarget, Watcher, addDebugAction, auto, flushSync, getCurrentAuto, getDebug, globals, isDerived, mountAuto, no, noto, o, setDebug, shallowChanges, useAuto, useAutoContext, useBinding, useChanges, useDerived, useEffects, useO, watch, when, withAuto };
