import Accessor from "@arcgis/core/core/Accessor";
import Collection, { type ReadonlyCollection } from "@arcgis/core/core/Collection.js";
import type { Evented, EventedCallback, EventNames, EventTypes } from "@arcgis/core/core/Evented.js";
import { type ObservableMap } from "../utilities/ObservableMap.js";
import type { ObservableSet } from "../utilities/ObservableSet.js";
/**
 * Represents an observable object. This can be an esri class that extends
 * Accessor and implements esri's property watch functionality or an object that
 * has been made observable via the {@link support/observableUtils!observable}
 * mixin.
 */
export type Observable = ObservableMixin | Accessor;
/**
 * Represents an object that is collection like and can be observed. This can be
 * an esri collection, an ObservableMap, or an ObservableSet.
 */
type ObservableCollectionLike<T> = Collection<T> | ReadonlyCollection<T> | ObservableMap<any, T> | ObservableSet<T>;
/**
 * Represents a path to a property on an observable or esri class with a base
 * class of accessor. Only traverses to 3 levels deep.
 *
 * Examples:
 *
 * "foo" | "bar.baz" | "foo.bar.baz" | "foo.bar.baz.qux".
 *
 * Note: This is a complex recursive type that should only be used for function
 * definitions related to the observable watch system. It is not intended to be
 * used for general purposes and should not be used by casts or other type
 * manipulations. Do not set the Depth type parameter.
 */
export type ObservablePropertyPath<T extends Observable, Depth extends number[] = []> = Depth["length"] extends 3 ? never : NestedObjectPaths<T, Depth>;
type NestedObjectPaths<T extends Observable, Depth extends number[]> = {
    [K in Extract<keyof T, string>]-?: [NonNullable<T[K]>] extends [never] ? never : NonNullable<T[K]> extends Observable ? K | `${K}.${ObservablePropertyPath<NonNullable<T[K]>, [...Depth, 0]>}` : K;
}[Extract<keyof T, string>];
/**
 * Represents a path to a property on an observable or esri class with a base
 * class of accessor in a collection. Only traverse to 2 levels deep to the
 * collection then 1 level into the object type in the collection.
 *
 * This will build paths into the objects of the collections if the collection
 * is properly typed and contains observables or accessors. Collections are only
 * supported at the first and second level of the path as well as being the root
 * of the path. It is only possible to watch up to two properties deep into the
 * object in a collection. This helps stave off some performance issues with the
 * type system.
 *
 * Examples:
 *
 * "[].foo" | "[].foo.bar".
 *
 * "foo.[].bar" | "foo.[].bar.baz" | "foo.bar.[].baz" | "foo.bar.[].baz.qux".
 */
type CollectionPropertyPath<T extends Observable, Depth extends number[] = []> = Depth["length"] extends 2 ? never : T extends ObservableCollectionLike<infer ItemType> ? ItemType extends Observable ? `[].${DeepObjectPaths<ItemType>}` : never : NestedCollectionPropertyPath<T, Depth>;
type NestedCollectionPropertyPath<T, Depth extends number[]> = {
    [K in Extract<keyof T, string>]-?: [NonNullable<T[K]>] extends [never] ? never : NonNullable<T[K]> extends ObservableCollectionLike<infer ItemType> ? ItemType extends Observable ? `${K}.[].${DeepObjectPaths<ItemType>}` : never : NonNullable<T[K]> extends Observable ? `${K}.${CollectionPropertyPath<NonNullable<T[K]>, [...Depth, 0]>}` : never;
}[Extract<keyof T, string>];
type DeepObjectPaths<T extends Observable> = {
    [K in Extract<keyof T, string>]-?: [NonNullable<T[K]>] extends [never] ? never : K;
}[Extract<keyof T, string>];
/**
 * The type of the value at the end of a path created from
 * ObservablePropertyPath or CollectionPropertyPath. Only intended for use with
 * ObservablePropertyPath or CollectionPropertyPath.
 *
 * Because the path was built with ObservablePropertyPath or
 * CollectionPropertyPath, we know that the path is valid. We still have to
 * build out the conditionals for the type system to understand what we are
 * doing, but we don't need to handle the false cases.
 *
 * This type uses recursion without any depth limits because the path is
 * guaranteed to be valid and of a limited depth.
 *
 * Note: This is a complex recursive type that should only be used for function
 * definitions related to the observable watch system. It is not intended to be
 * used for general purposes and should not be used by casts or other type
 * manipulations.
 */
export type ValueTypeFromPath<T extends Observable, P extends string> = P extends keyof T ? T[P] : P extends `[].${infer ItemPath}` ? [
    T
] extends [ObservableCollectionLike<infer ItemType>] ? ItemType extends Observable ? ItemPath extends keyof ItemType ? ItemType[ItemPath] : ValueTypeFromPath<ItemType, ItemPath> : unknown : unknown : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? [T[K]] extends [Observable | undefined | null] ? Rest extends `[].${string}` ? ValueTypeFromPath<NonNullable<T[K]>, Rest> : // Keep recursing through the path to get the type of the
ValueTypeFromPath<NonNullable<T[K]>, Rest> | (undefined extends T[K] ? undefined : never) : unknown : unknown : unknown;
/**
 * The type of the target at the second to last position of a path created from
 * ObservablePropertyPath or CollectionPropertyPath. Only intended for use with
 * ObservablePropertyPath or CollectionPropertyPath.
 *
 * Because the path was built with ObservablePropertyPath or
 * CollectionPropertyPath, we know that the path is valid. We still have to
 * build out the conditionals for the type system to understand what we are
 * doing, but we don't need to handle the false cases.
 *
 * This type uses recursion without any depth limits because the path is
 * guaranteed to be valid and of a limited depth.
 *
 * Note: This is a complex recursive type that should only be used for function
 * definitions related to the observable watch system. It is not intended to be
 * used for general purposes and should not be used by casts or other type
 * manipulations.
 */
export type TargetTypeFromPath<T extends Observable, P extends string> = P extends keyof T ? T : P extends `[].${infer ItemPath}` ? [
    T
] extends [ObservableCollectionLike<infer ItemType>] ? ItemType extends Observable ? ItemPath extends keyof ItemType ? ItemType : TargetTypeFromPath<ItemType, ItemPath> : unknown : unknown : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? [T[K]] extends [Observable | undefined | null] ? Rest extends `[].${string}` ? TargetTypeFromPath<NonNullable<T[K]>, Rest> : // Keep recursing through the path to get the type of the
TargetTypeFromPath<NonNullable<T[K]>, Rest> | (undefined extends T[K] ? undefined : never) : unknown : unknown : T;
/**
 * The type of the path to a property that is an evented object in an observable
 * or esri class. Limits the depth of the path to 3 levels deep.
 */
type EventedPropertyPath<T extends Observable, Depth extends number[] = []> = Depth["length"] extends 3 ? never : NestedEventedPropertyPath<T, Depth>;
type NestedEventedPropertyPath<T extends Observable, Depth extends number[]> = {
    [K in Extract<keyof T, string>]-?: [NonNullable<T[K]>] extends [never] ? never : NonNullable<T[K]> extends Observable ? (NonNullable<T[K]> extends Evented ? K : never) | `${K}.${EventedPropertyPath<NonNullable<T[K]>, [...Depth, 0]>}` : NonNullable<T[K]> extends Evented ? K : never;
}[Extract<keyof T, string>];
/**
 * The type of the value at the end of a path created from EventedPropertyPath.
 * Only intended for use with EventedPropertyPath.
 *
 * Because the path was built with ObservablePropertyPath, we know that the path
 * is valid. We still have to build out the conditionals for the type system to
 * understand what we are doing, but we don't need to handle the false cases.
 *
 * This type uses recursion without any depth limits because the path is
 * guaranteed to be valid and of a limited depth.
 */
type EventedTypeFromPath<T extends Observable, P extends string> = P extends keyof T ? T[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? NonNullable<T[K]> extends Observable ? // Keep recursing through the path to get the type of the
EventedTypeFromPath<NonNullable<T[K]>, Rest> | (undefined extends T[K] ? undefined : never) : unknown : unknown : unknown;
/**
 * Ensures that the given object is an evented object.
 */
export type EventTarget<TTarget> = TTarget extends Evented ? TTarget : never;
/**
 * Options for {@link support/observableUtils!watch}.
 */
export interface WatchOptions {
    /**
     * If true, the watch callback will be invoked immediately with the current
     * value of the property being watched. Both the newValue and oldValue
     * parameters will be set to the current value.
     *
     * If sync is also true, the callback invoked with the initial value will be
     * synchronous. Otherwise it will be asynchronous.
     *
     * @default false
     */
    initial?: boolean;
    /**
     * If true, the watch callback will be invoked synchronously. If false, the
     * watch callback will be invoked asynchronously.
     *
     * @default false
     */
    sync?: boolean;
    /**
     * If true, the watch callback will only be invoked once and then the remove
     * handle will be called. If false, the watch callback will be invoked each
     * time the watched property changes.
     *
     * If sync is also true, the callback will be invoked synchronously.
     * Otherwise it will be asynchronous.
     *
     * @default false
     */
    once?: boolean;
}
/**
 * Options for {@link support/observableUtils!watchEach}.
 */
export interface WatchEachOptions extends WatchOptions {
    /**
     * If true, the watch callback will be invoked immediately for each object
     * in the collection with the current value of the property being watched,
     * as well as any item added to the collection. Both the newValue and
     * oldValue parameters will be set to the current value of the object in the
     * collection.
     *
     * If sync is also true, the callback invoked with the initial value will be
     * synchronous. Otherwise it will be asynchronous.
     *
     * @default false
     */
    initial?: boolean;
    /**
     * If true, the watch callback will be invoked once for each object in the
     * collection as well as once for each item added to the collection. If
     * false, the watch callback will be invoked each time the watched property
     * changes for each item in the collection.
     *
     * If sync is also true, the callback will be invoked synchronously.
     * Otherwise it will be asynchronous.
     *
     * @default false
     */
    once?: boolean;
}
/**
 * Options for {@link support/observableUtils!when}.
 */
export type WhenOptions = Omit<WatchOptions, "initial"> & {
    /**
     * If true, the when callback will be invoked if the value is falsy. If
     * false, the when callback will be invoked if the value is truthy.
     *
     * @default false
     */
    invert?: boolean;
};
/**
 * Returned by {@link support/observableUtils!watch}.
 *
 * Call `remove` to stop watching.
 */
export interface IHandle {
    /**
     * Stops watching the property.
     *
     * Any pending asynchronous callbacks (watch options used `sync: false`)
     * that have not yet been invoked will also be stopped and not invoked. This
     * includes the callback invoked with the initial value (watch options used
     * `initial: true`).
     */
    remove(): void;
}
/**
 * Supporting mixin declaration for {@link support/observableUtils!observable}.
 */
export declare class ObservableMixin {
    private readonly __isObservable;
    private readonly _subscribedPaths;
    private readonly _onWatchPaths;
    private _onWatch;
    private _notifyChange;
    private _watch;
    private _recurseWatch;
    private _recurseCollectionWatch;
    private _collectionWatch;
    private _watchSingle;
    private _createWatchDescriptor;
    private _createSingleWatchRemoveHandle;
}
/**
 * Mixin to add observable functionality to a class.
 *
 * Observability allows you to watch for changes to properties on an object.
 *
 * Observability is handled by this mixin but if you have an accessor property
 * that is backed by a backing field and that backing field is updated
 * separately from the accessor set, you must call
 * {@link support/observableUtils!notifyChange} for the accessor property after
 * the backing field has changed to ensure watchers are notified of the change.
 *
 * The implementation of the mixin expects that accessor properties with a
 * setter use the value given to the setter as the new value that will be
 * returned by the getter.
 *
 * See {@link support/observableUtils!watch}.
 *
 * See {@link support/observableUtils!notifyChange}.
 *
 * See {@link support/observableUtils!onWatch}.
 *
 * See {@link support/observableUtils!watchEvent}.
 *
 * See {@link support/observableUtils!once}.
 *
 * Usage example of the mixin by wrapping a base class.
 *
 * ```ts
 * class MyClass extends observable(BaseClass) {
 *     foo: string = "foo";
 * }
 *
 * const myClass = new MyClass();
 * watch(myClass, "foo", (newValue, oldValue) => {});
 * ```
 *
 * Usage example of the mixin used as a base class.
 *
 * ```ts
 * export class MyClass extends observable() {
 *     private _otherObservable: OtherObservable = new OtherObservable();
 *
 *     get foo(): string {
 *         return this._otherObservable.foo;
 *     }
 *
 *     constructor() {
 *         super();
 *
 *         onWatch(this, "foo", () =>
 *             watch(this._otherObservable, "foo", () => {
 *                 notifyChange(this, "foo");
 *             })
 *         );
 *     }
 * }
 * ```
 *
 * Usage example of the mixin by wrapping a class implementation.
 *
 * Note: while this pattern is supported, it is not recommended since it will
 * not allow observable functionality to be used by the class in itself.
 *
 * ```ts
 * const MyClass = observable(
 *     class MyClass {
 *         foo: string = "foo";
 *     }
 * );
 *
 * const myClass = new MyClass();
 * watch(myClass, "foo", (newValue, oldValue) => {});
 * ```
 *
 * @param obj The class to make observable.
 */
export declare function observable<T extends abstract new (...args: any[]) => any = new () => ObservableMixin>(obj?: T): T & (abstract new (...args: any[]) => ObservableMixin);
/**
 * Watches a property for changes. This can be a property on the given object or
 * a property on a nested object if the nested object is also observable.
 *
 * This function will return a handle that can be used to stop watching the
 * property.
 *
 * The callback will be invoked with the new value, old value, and the target
 * object when the property changes. The target object is the object that
 * contains the property that changed.
 *
 * The callback will not be invoked if the value has not changed. This includes
 * if a parent property changes but the resulting value is the same as the
 * previous value.
 *
 * If any property in the path can be undefined, the callback will be invoked
 * with an undefined newValue and an undefined target when a parent property
 * changes to undefined.
 *
 * ```ts
 * class MyObservable extends observable() {
 *     private _backingFieldForGetter = "backingFieldForGetter";
 *     dataProperty: string = "dataProperty";
 *
 *     get getter() {
 *       return this._backingFieldForGetter;
 *     }
 * }
 *
 * const myObservable = new MyObservable();
 * watch(myObservable, "getter", (newValue, oldValue) => {});
 * watch(myObservable, "dataProperty", (newValue, oldValue) => {});
 *
 * class MyClass extends observable() {
 *    private _backingFieldForAccessor = new MyObservable();
 *
 *    get getterSetter {
 *      return this._backingFieldForAccessor;
 *    }
 *    set getterSetter(value: MyObservable) {
 *      this._backingFieldForAccessor = value;
 *    }
 * }
 *
 * const myClass = new MyClass();
 * watch(myClass, "getterSetter", (newValue, oldValue) => {});
 * watch(myClass, "getterSetter.getter", (newValue, oldValue) => {});
 * watch(myClass, "getterSetter.dataProperty", (newValue, oldValue) => {});
 * ```
 *
 * @param obj The object to watch.
 * @param path The property path on the given object to watch.
 * @param callback The callback to invoke when the property changes.
 * @param options The options to use for watching the property.
 * @throws Error if the object is not observable.
 * @throws Error if the path is to a non-configurable property.
 * @throws Error if the path is not a valid property path.
 */
export declare function watch<TObsv extends Observable, TPath extends ObservablePropertyPath<TObsv>, TValue extends ValueTypeFromPath<TObsv, TPath>, TTarget extends TargetTypeFromPath<TObsv, TPath>>(obj: TObsv, path: TPath, callback: (newValue: TValue, oldValue: TValue, target: TTarget) => void, options?: WatchOptions): IHandle;
export declare function watch<TObsv extends Observable, TPath extends `${string}.${string}.${string}.${string}`, TValue extends ValueTypeFromPath<TObsv, TPath>, TTarget extends TargetTypeFromPath<TObsv, TPath>>(obj: TObsv, path: TPath, callback: (newValue: TValue, oldValue: TValue, target: TTarget) => void, options?: WatchOptions): IHandle;
/**
 * Watches a property for changes on each item in a collection like object. This
 * can be a property on an item in a collection, an item in a collection in the
 * given object or an item in a collection in a nested observable object.
 *
 * This function will return a handle that can be used to stop watching the
 * property.
 *
 * If the collection contains multiple different types of objects, only objects
 * that have the property being watched will be watched for changes, the other
 * objects will be ignored.
 *
 * The callback will be invoked with the new value, old value, and the target
 * object when the property changes. The target object will be the object in the
 * collection that changed. The callback will be invoked for each item in the
 * collection when the property on the object changes. The callback will not be
 * invoked when items are added or removed from the collection unless the
 * options include `initial: true` in which case the callback will be invoked
 * for each item in the collection when the watch is created as well as for any
 * items that are added to the collection after.
 *
 * The callback will not be invoked if the value has not changed. This includes
 * if a parent property changes but the resulting value is the same as the
 * previous value.
 *
 * If any property in the path after the path to the collection can be
 * undefined, the callback will be invoked with an undefined newValue and an
 * undefined target when a parent property changes to undefined.
 *
 * ```ts
 * class MyObservable extends observable() {
 *     property: string = "testProperty";
 * }
 *
 * class MyClass extends observable() {
 *     collection: Collection<MyObservable> = new Collection();
 * }
 *
 * const myClass = new MyClass();
 * watchEach(myClass, "collection.[].property", (newValue, oldValue) => {});
 *
 * const myCollection = new Collection<MyObservable>();
 * watchEach(myCollection, "[].property", (newValue, oldValue) => {});
 * ```
 *
 * @param obj The object to watch.
 * @param path The property path on the given object to watch.
 * @param callback The callback to invoke when the property changes.
 * @param options The options to use for watching the property.
 * @throws Error if the object is not observable.
 * @throws Error if the path is to a non-configurable property.
 * @throws Error if the path is not a valid property path.
 */
export declare function watchEach<TObsv extends Observable, TPath extends CollectionPropertyPath<TObsv>, TValue extends ValueTypeFromPath<TObsv, TPath>, TTarget extends TargetTypeFromPath<TObsv, TPath>>(obj: TObsv, path: TPath, callback: (newValue: TValue, oldValue: TValue, target: TTarget) => void, options?: WatchEachOptions): IHandle;
export declare function watchEach<TObsv extends Observable, TPath extends `${string}.[].${string}.${string}` | `[].${string}.${string}`, TValue extends ValueTypeFromPath<TObsv, TPath>, TTarget extends TargetTypeFromPath<TObsv, TPath>>(obj: TObsv, path: TPath, callback: (newValue: TValue, oldValue: TValue, target: TTarget) => void, options?: WatchEachOptions): IHandle;
/**
 * Watches the given path on the given object for the first change.
 *
 * See {@link support/observableUtils!watch} for more details.
 *
 * @param obj The object to watch.
 * @param path The property path to watch.
 * @param options Optional. The options to use for watching the property.
 * @param options.signal Optional signal to abort. The promise will be rejected
 *   if the signal is aborted.
 * @throws Error if the object is not observable.
 * @throws Error if the path is to a non-configurable property.
 * @throws Error if the path is not a valid property path.
 */
export declare function once<TObsv extends Observable, TPath extends ObservablePropertyPath<TObsv>>(obj: TObsv, path: TPath, options?: {
    signal?: AbortSignal;
}): Promise<void>;
/**
 * Notifies any watchers that a property has changed. Will not notify if the
 * value has not changed.
 *
 * Typically only used when a backing field for a getter only property has
 * changed.
 *
 * @param obj The object with the property that changed.
 * @param path The property that changed.
 * @throws Error if the object is not observable.
 */
export declare function notifyChange<TObsv extends ObservableMixin, TPath extends keyof TObsv>(obj: TObsv, path: TPath): void;
/**
 * For the given object, if a watch is added to the given path the callback will
 * be invoked. When all the watches are removed, the handle returned by the
 * callback will be invoked.
 *
 * A common use case for this is to notify changes to a property only if someone
 * is watching it, this avoids unnecessary work from the watch if no one is
 * watching the property.
 *
 * ```ts
 * class MyClass extends observable() {
 *     _foo = "foo";
 *     _bar = "bar";
 *
 *     get foo() {
 *         return `${this._foo} ${this._bar}`;
 *     }
 *
 *     get bar() {
 *         return this._bar;
 *     }
 *     set bar(value: string) {
 *         this._bar = value;
 *     }
 * }
 *
 * const myClass = new MyClass();
 *
 * onWatch(myClass, "foo", () =>
 *     watch(myClass, "bar", () => notifyChange(myClass, "foo"), {
 *         sync: true,
 *     })
 * );
 * ```
 *
 * @param obj The object to watch.
 * @param path The property to watch.
 * @param callback The callback to invoke when the property is watched.
 * @throws Error if the object is not observable.
 */
export declare function onWatch<TObsv extends ObservableMixin>(obj: TObsv, path: keyof TObsv, callback: () => IHandle): IHandle;
/**
 * Watches the given path on the given object for changes and automatically adds
 * and removes the given event listener.
 *
 * @param obj The object to watch.
 * @param path The property path to an Evented object on the given object to
 *   watch.
 * @param event The event and the listener to add to the Evented object.
 * @param watchCallback The callback to invoke when the watched property
 *   changes. This is optional and will be invoked with the Evented object being
 *   watched.
 * @throws Error if the object is not observable.
 * @throws Error if the path is to a non-configurable property on an observable.
 */
export declare function watchEvent<TObsv extends Observable, TPath extends EventedPropertyPath<TObsv>, TTarget extends EventedTypeFromPath<TObsv, TPath>, TEventName extends EventNames<EventTarget<TTarget>>>(obj: TObsv, path: TPath, event: [TEventName, EventedCallback<EventTypes<EventTarget<TTarget>>[TEventName]>], watchCallback?: (newTarget: TTarget, oldTarget: TTarget) => void): IHandle;
/**
 * Watches the given path on the given object and invokes the callback when the
 * value of the property becomes truthy.
 *
 * If the value of the property is already truthy, the callback will be invoked
 * immediately.
 *
 * @param obj The object to watch.
 * @param path The property path to watch.
 * @param callback The callback to invoke when the property becomes truthy.
 * @param options Optional. The options to use for watching the property.
 */
export declare function when<TObsv extends Observable, TPath extends ObservablePropertyPath<TObsv>>(obj: TObsv, path: TPath, callback: () => void, options?: WhenOptions): IHandle;
export declare function when<TObsv extends Observable, TPath extends `${string}.${string}.${string}.${string}`>(obj: TObsv, path: TPath, callback: () => void, options?: WhenOptions): IHandle;
/**
 * Watches the given path on the given object for the first change that causes
 * the value of the watched property to be truthy.
 *
 * See {@link support/observableUtils!watch} for more details.
 *
 * @param obj The object to watch.
 * @param path The property path to watch.
 * @param options Optional. The options to use for watching the property.
 * @throws Error if the object is not observable.
 * @throws Error if the path is to a non-configurable property on an observable.
 */
export declare function whenOnce<TObsv extends Observable, TPath extends ObservablePropertyPath<TObsv>>(obj: TObsv, path: TPath, options?: Pick<WhenOptions, "invert"> & {
    /**
     * The promise will be rejected if the signal is aborted.
     */
    signal?: AbortSignal;
}): Promise<void>;
/**
 * Creates an IHandle that will remove all the handles in the given iterable
 * when the returned handle is removed.
 *
 * This holds the reference to the iterable of handles so that the handles can
 * be updated and the returned handle will remove the handles in the iterable
 * even if added later when it is removed.
 *
 * @param handles An iterable of handles to remove when the returned handle is
 *   removed.
 */
export declare function createIHandle(handles: Iterable<IHandle>): IHandle;
export {};
