/**
 * * [Overview](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#overview)
 * * [Using reactiveUtils](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#using-reactiveutils)
 * * [Truthy checks versus explicit value checks](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#truthy-values)
 * * [Async versus sync callbacks](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#async-vs-sync)
 * * [Working with collections](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#working-with-collections)
 * * [Working with objects](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#working-with-objects)
 * * [ResourceHandles and Promises](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#resourcehandles-and-promises)
 * * [Working with TypeScript](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#typescript)
 *
 * <span id="overview"></span>
 * ## Overview
 * `reactiveUtils` provides a declarative way to watch SDK state and respond to changes.
 *
 * To use it, provide:
 * - A reactive expression that returns the state you want to track.
 * - A callback that runs whenever that state changes.
 *
 * The reactive expression is a function that automatically re-evaluates whenever any state it accesses changes.
 * For example, if the expression reads the map component's `stationary` property,
 * the callback runs whenever `stationary` changes between `true` and `false`.
 *
 * Reactive expressions can track primitive values, arrays, collections, and objects.
 * Depending on the function you use, `reactiveUtils` supports both continuous observation and one-time conditions.
 *
 * <span id="using-reactiveutils"></span>
 * ## Using reactiveUtils
 *
 * `reactiveUtils` provides five functions for observing state:
 * [on()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#on), [once()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#once), [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch), [when()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#when) and [whenOnce()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#whenOnce).
 * Each of these has different characteristics and use cases, as summarized in the table below:
 *
 * | Function                            | Runs multiple times | Resolves once | Returns          | Common use                           |
 * | ----------------------------------- | ------------------- | ------------- | ---------------- | ------------------------------------ |
 * | [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch)    | Yes                 | No            | `ResourceHandle` | Continuously observe for changes     |
 * | [when()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#when)     | Yes                 | No            | `ResourceHandle` | Run when expression becomes truthy   |
 * | [on()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#on)       | Yes                 | No            | `ResourceHandle` | Observe events                       |
 * | [once()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#once)     | No                  | Yes           | `Promise`        | Wait for first emitted value         |
 * | [whenOnce()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#whenOnce) | No                  | Yes           | `Promise`        | Wait until expression becomes truthy |
 *
 * <details>
 * <summary>Read More</summary>
 *
 * The snippet below uses [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch) to react when the
 * Map component's [stationary](https://developers.arcgis.com/javascript/latest/references/map-components/components/arcgis-map/#stationary) property changes.
 * This pattern is useful when work should run after navigation settles, such as querying
 * features for the current extent, refreshing summary statistics, or enabling UI actions.
 *
 * The first argument in the `watch()` function is the reactive expression, in the form of a `getValue` function, that evaluates the
 * `stationary` property. When a change is observed in the expression the new value is automatically passed to the callback:
 *
 * ```js
 * // Watching for changes on the map component's stationary property
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.watch(
 *   // getValue expression
 *   () => viewElement.stationary,
 *   // callback
 *   (stationary) => {
 *     console.log(stationary)
 *   });
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * Other examples of `getValue` expressions include:
 *
 * ```js
 * () => viewElement.ready                           // Wait until the view finishes updating
 * () => [viewElement.stationary, viewElement.zoom]  // Track changes to multiple properties at once
 * () => viewElement.popupElement?.open              // Detect when a popup is opened
 * () => layer.visible                               // Track changes in layer visibility
 * ```
 *
 * **Basic usage rules:**
 * - Shape expressions so callbacks receive defined values.
 * - Avoid truthy checks when falsy values like `0`, `""`, or `false` are valid.
 * - Use explicit comparisons for specific values (e.g., `=== 0` or `=== false`).
 * - Use optional chaining (`?.`) to safely access nested properties.
 * - Return derived values for collections to react to structural changes.
 *
 * </details>
 *
 * <span id="truthy-values"></span>
 * ### Truthy checks versus explicit value checks
 *
 * The [when()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#when) and [whenOnce()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#whenOnce) functions execute when the `getValue` expression
 * evaluates as truthy, instead of reacting to every value change.
 *
 * Use [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) checks when you only care whether a value is present or available.
 * Use [explicit value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Equality_comparisons_and_sameness) checks
 * when you need to verify a specific state or distinguish between different values.
 *
 * | Goal                                  | Recommended approach |
 * | ------------------------------------- | -------------------- |
 * | Wait for property existence           | Truthy               |
 * | Wait for async resource               | Truthy               |
 * | Check boolean state (true or false)   | Explicit             |
 * | Allow values such `0` or `""`         | Explicit             |
 * | Distinguish `null` or `undefined`     | Explicit             |
 * | Implement exact application logic     | Explicit             |
 *
 * <details>
 * <summary>Read More</summary>
 *
 * > [!WARNING]
 * >
 * > `when()` and `whenOnce()` functions only trigger the callback when the expression changes and then the value satisfies the expression,
 * > such as *false -> true -> false*, but not *true -> true*.
 * >
 * > Be careful with initial and default property values. For example, if you are watching for a property that is `true` by default,
 * > such as [FeatureLayer.visible](https://developers.arcgis.com/javascript/latest/references/core/layers/FeatureLayer/), using a truthy check will not trigger the callback
 * > until the property becomes `false` and then `true` again, which may not be the intended behavior.
 *
 * The snippets below use the Popup component's [open](https://developers.arcgis.com/javascript/latest/references/map-components/components/arcgis-popup/#open) property in the `getValue` expression.
 * The first snippet uses a truthy check to determine when the popup is open,
 * which is useful when you want to run the callback whenever the popup becomes visible.
 * The property is `false` by default, so the callback will run when the popup opens,
 * but not on subsequent changes that keep it open. Once the popup is closed, the callback will run again when it opens.
 *
 * ```js
 * const viewElement = document.querySelector("arcgis-map");
 *
 * const handle = reactiveUtils.when(() => viewElement.popupElement.open, () => {
 *   console.log("Truthy check for popup open");
 * });
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * Other truthy examples include:
 * ```js
 * () => viewElement.ready
 * () => layer.loaded
 * () => layer.visible
 * ```
 *
 * Use explicit value checks when your logic depends on a specific value,
 * rather than simply whether a value is truthy.
 *
 * This is useful when valid values may be falsy, such as `0`, `""`, or
 * `false`, and a truthy check would produce unintended results.
 *
 * For example, to react only when the map is at a specific scale:
 *
 * ```js
 * const handle = reactiveUtils.when(() => viewElement.scale === 5000, () => {
 *   console.log("The map is at scale 1:5000");
 * });
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * Other examples include:
 *
 * ```js
 * () => viewElement.updating === false
 * () => layer.visible === false
 * ```
 *
 * See the MDN documentation to learn more about
 * [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and
 * [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values.
 *
 * </details>
 *
 * <span id="async-vs-sync"></span>
 * ### Async versus sync callbacks
 *
 * Understanding the difference between asynchronous and synchronous callbacks is essential when working with `reactiveUtils`.
 * The timing of when your callback executes, either immediately or on the next microtask,
 * can impact both application behavior and performance.
 *
 * <details>
 * <summary>Read More</summary>
 *
 * By default, [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch), [when()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#when) and [on()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#on)
 * functions are asynchronous, meaning that the callback will run on the next [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after the
 * expression value changes. This allows for batching of multiple changes and can help with
 * performance. However, if you need the callback to run synchronously with the change, you
 * can set the `sync` option to `true` in the options parameter.
 *
 * [once()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#once) and [whenOnce()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#whenOnce) resolve via a Promise,
 * so they are always asynchronous and cannot be made synchronous. If the condition is
 * already satisfied when `once()` or `whenOnce()` is called, the promise will resolve
 * on the next microtask with the current value.
 *
 * One nuance, if you provide `initial: true` in the [ReactiveWatchOptions](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#ReactiveWatchOptions)
 * for `watch()` and `when()`, the callback will run immediately after initialization if the
 * condition is met, and then continue to watch for changes. In this case, the callback runs
 * synchronously with the initial check, but subsequent calls are asynchronous.
 * This is useful when you don’t want to duplicate initialization logic or manually “seed” state.
 *
 * ```js
 * const handle = reactiveUtils.when(
 *   () => layer.visible === true,
 *   () => showLayerUI(),
 *   { initial: true }
 * );
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * Without `initial: true`, you might miss the fact that `layer.visible` was already true at startup and only respond to future changes.
 *
 * </details>
 *
 * <span id="working-with-collections"></span>
 * ### Working with collections
 *
 * `reactiveUtils` can observe changes with a [Collection](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/), such as the Map component’s
 * [allLayerViews](https://developers.arcgis.com/javascript/latest/references/map-components/components/arcgis-map/#allLayerViews).
 * Methods like [Collection.map()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#map) and [Collection.filter()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#filter),
 * can be used in the `getValue` expression to derive values.
 * This ensures the callback reacts to additions, removals, or updates in the collection.
 *
 * ```js
 * const handle = reactiveUtils.watch(
 *   () => viewElement.allLayerViews.map((layerView) => layerView.visible),
 *   (layerViews) => {
 *     console.log(`Visible layerViews: ${layerViews}`);
 *   });
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * <span id="working-with-objects"></span>
 * ### Working with objects
 *
 * `reactiveUtils` allows you to track object properties using dot notation
 * (e.g. `viewElement.updating`) or bracket notation (e.g. `viewElement["updating"]`).
 *
 * Optional chaining (`?.`) can be used to safely access nested properties
 * without manually checking each level.
 *
 * ```js
 * const handle = reactiveUtils.watch(
 *   () => viewElement?.extent?.xmin,
 *   (xmin) => {
 *     console.log(`Extent change xmin = ${xmin}`);
 *   }
 * );
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * <span id="resourcehandles-and-promises"></span>
 * ### ResourceHandles and Promises
 *
 * The [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch), [on()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#on) and
 * [when()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#when) functions return a [ResourceHandle](https://developers.arcgis.com/javascript/latest/references/core/core/Handles/#ResourceHandle).
 * Handles retain references to observed objects and callbacks until they are removed.
 * The [once()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#once) and [whenOnce()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#whenOnce) functions return a
 * Promise instead of a `ResourceHandle` and have the option to be cancelled via an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). When cancelled, the Promise settles and is eligible for disposal.
 *
 * [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch) and [when()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#when) have an optional `once` property in
 * their `options` parameter. When `once: true` is set, the returned `ResourceHandle` will automatically remove
 * itself after the first time the callback is executed, which can be useful for one-time conditions
 * while still allowing for manual removal if needed.
 *
 * <details>
 * <summary>Read More</summary>
 *
 * To avoid memory leaks:
 * * Remove handles when the reference is no longer needed, such as during component teardown or before creating a replacement handle.
 * * Use [Handles](https://developers.arcgis.com/javascript/latest/references/core/core/Handles/) to manage groups of handles.
 * * Abort unresolved async operations, this allows them to be disposed.
 *
 * Removing a handle stops the observation and releases references to observed objects and callbacks,
 * allowing them to be garbage collected.
 * Handle removal is idempotent and can be safely called multiple times.
 *
 * ```js
 * // Remove a ResourceHandle to stop watching for changes
 * const handle = reactiveUtils.watch(
 *   () => viewElement?.extent?.xmin,
 *   (xmin) => {
 *     console.log(`Extent change xmin = ${xmin}`)
 *   });
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove()
 * ```
 *
 * Use [Handles](https://developers.arcgis.com/javascript/latest/references/core/core/Handles/) to group multiple ResourceHandles and remove them together, such as when a component is torn down.
 * This also makes it easier to manage handles by name.
 *
 * ```js
 * const handles = new Handles();
 * handles.add(
 *   reactiveUtils.watch(...),
 *   "view-watcher" // groupKey is optional but can be helpful for managing handles
 * );
 *
 * // In another function or during component teardown
 * handles.remove("view-watcher");
 * ```
 *
 * The [once()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#once) and [whenOnce()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#whenOnce) functions return a Promise.
 * In some advanced use cases where an action may take additional time, these
 * functions also offer the option to cancel the async callback via an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).
 *
 * > [!WARNING]
 * >
 * > If the component being watched is removed before the promise resolves,
 * > it can lead to memory leaks or unhandled promise rejections.
 *
 * ```js
 * let abortController = new AbortController();
 * const { signal } = abortController;
 *
 * // Cancel the async callback by sending an AbortSignal
 * const abort = () => {
 *   abortController?.abort();
 * }
 *
 * // Set the signal property to watch for abort signals
 * // and reject the promise if the signal is aborted before the promise resolves
 * reactiveUtils.whenOnce(
 *   () => !viewElement.updating,
 *   { signal }
 * )
 * .then((updating) => {
 *   console.log("Map is not updating", updating);
 * })
 * .catch((error) => {
 *   if (error.name === "AbortError") {
 *     console.log("The async callback was aborted");
 *   } else {
 *     console.error("An unexpected error occurred:", error);
 *   }
 * })
 * .finally(() => {
 *   console.log("Async callback has completed or was aborted");
 *   abortController = null;
 * });
 * ```
 *
 * See the SDK guide topic on [Async cancellation with AbortController](https://developers.arcgis.com/javascript/latest/async-cancellation-with-abortcontroller/) for more details and examples.
 *
 * </details>
 *
 * <span id="typescript"></span>
 * ### Working with TypeScript
 *
 * `reactiveUtils` also works with TypeScript. TypeScript infers the expression result type from the `getValue`
 * expression and passes it to the callback.
 * For array expressions, use explicit tuple typing when positional meaning matters.
 * Using `as const` preserves tuple types so callback parameters are strongly typed by position.
 * You can also use objects to group values and maintain strong typing without relying on position.
 *
 * <details>
 * <summary>Read More</summary>
 *
 * The first snippet shows how to use `as const` for tuple typing when tracking multiple properties in an array.
 *
 * ```ts
 * import { watch } from "@arcgis/core/core/reactiveUtils.js";
 * import type { ArcgisMap } from "@arcgis/map-components/components/arcgis-map";
 *
 * // Get a reference to the map component
 * const arcgisMap = document.querySelector<ArcgisMap>("arcgis-map");
 *
 * const handle = reactiveUtils.watch(
 *   () => [view.stationary, view.zoom] as const,
 *   ([stationary, zoom], oldValue) => {
 *     if (stationary) {
 *       const previousZoom = oldValue?.[1];
 *       console.log("Zoom:", zoom, "Previous:", previousZoom);
 *     }
 *   }
 * );
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * The second snippet shows how to use an object to group values and maintain strong typing without
 * relying on position. One advantage of this approach is the callback properties are named.
 * The order of properties in the `getValue` expression doesn't matter. This improves maintainability
 * and readability, especially when tracking multiple properties or when the expression is complex
 * because the properties are matched by the name and not position.
 *
 * ```ts
 * const handle = reactiveUtils.watch(
 *   () => ({
 *     scale: viewElement.scale,
 *     stationary: viewElement.stationary,
 *     extent: viewElement.extent
 *   }),
 *   ({ stationary, extent, scale }) => {
 *     if (stationary) {
 *       console.log("View stopped moving");
 *       console.log("Scale:", scale);
 *       console.log("Extent:", extent);
 *     }
 *   }
 * );
 * ```
 *
 * If the `getValue` expression may return `null` or `undefined`,
 * shape the expression so the callback only runs when a defined value is available.
 *
 * This avoids unnecessary callback executions and ensures TypeScript receives a
 * narrowed type.
 *
 * ```ts
 * const handle = reactiveUtils.when(
 *   () => viewElement.popupElement?.selectedFeature?.geometry,
 *   (geometry) => {
 *     console.log(geometry.type);
 *   }
 * );
 *
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * ```
 *
 * </details>
 *
 * @since 4.23
 * @see [Watch for changes guide topic](https://developers.arcgis.com/javascript/latest/watch-for-changes/)
 * @see [Sample - Watch for changes in components using reactiveUtils](https://developers.arcgis.com/javascript/latest/sample-code/watch-for-changes-reactiveutils-components/)
 * @see [Sample - Property changes with reactiveUtils](https://developers.arcgis.com/javascript/latest/sample-code/watch-for-changes-reactiveutils/)
 * @see [Samples with `reactiveUtils`](https://developers.arcgis.com/javascript/latest/sample-code/?tagged=reactiveUtils)
 */
import type { EventedMixin } from "./Evented.js";
import type { ResourceHandle } from "./Handles.js";
import type { AbortOptions } from "./promiseUtils.js";

/**
 * Options used to configure how auto-tracking is performed and how the callback
 * should be called.
 */
export interface ReactiveWatchOptions<T = unknown> {
  /**
   * Whether to fire the callback immediately after initialization, if the necessary conditions are met.
   *
   * @default false
   */
  readonly initial?: boolean;
  /**
   * Whether to fire the callback synchronously or on the next microtask.
   *
   * @default false
   */
  readonly sync?: boolean;
  /**
   * Whether to fire the callback only once.
   *
   * @default false
   */
  readonly once?: boolean;
  /** Function used to check whether two values are the same, in which case the callback isn't called. Checks whether two objects, arrays or primitive values are shallow equal, e.g. one level deep. Non-plain objects are considered equal if they are strictly equal (===). */
  readonly equals?: ReactiveEqualityFunction<T>;
}

/**
 * Function used to check whether two values are the same, in which case the
 * watch callback isn't called.
 *
 * @param newValue - The new value.
 * @param oldValue - The old value.
 * @returns Whether the new value is equal to the old value.
 */
export type ReactiveEqualityFunction<T> = (newValue: T, oldValue: T) => boolean;

/**
 * Expression which is auto-tracked and should return a value to pass to the [ReactiveWatchCallback](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#ReactiveWatchCallback).
 *
 * @returns The new value.
 */
export type ReactiveWatchExpression<T> = () => T;

/**
 * Expression which is auto-tracked and should return an event target to which
 * an event listener is to be added.
 *
 * @returns The event target.
 */
export type ReactiveOnExpression<T> = () => T;

/**
 * Function to be called when a value changes.
 *
 * @param newValue - The new value.
 * @param oldValue - The old value.
 */
export type ReactiveWatchCallback<T> = (newValue: T, oldValue?: T) => void;

/**
 * Function to be called when an event is emitted or dispatched.
 *
 * @param event - The event emitted by the target.
 */
export type ReactiveOnCallback<T> = (event: T) => void;

/**
 * Tracks any properties accessed in the `getValue` expression and calls the `callback`
 * when any of them change.
 *
 * @param getValue - Function used to get the current value. All accessed properties will be tracked.
 * @param callback - The function to call when there are changes.
 * @param options - Options used to configure how the tracking happens and how the callback is to be called.
 * @returns A watch handle.
 * @example
 * // Watching for changes in a boolean value
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.watch(
 *  () => viewElement.popupElement.open,
 *  () => {
 *    console.log(`Popup open: ${viewElement.popupElement.open}`);
 *  });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * @example
 * // Watching for changes within a Collection
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.watch(
 *  () => viewElement.map.allLayers.length,
 *  () => {
 *    console.log(`Layer collection length changed: ${viewElement.map.allLayers.length}`);
 *  });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * @example
 * // Watch for changes in a numerical value.
 * // Providing `initial: true` in ReactiveWatchOptions
 * // checks immediately after initialization
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.watch(
 *  () => viewElement.zoom,
 *  () => {
 *    console.log(`zoom changed to ${viewElement.zoom}`);
 *  },
 *  {
 *    initial: true
 *  });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * @example
 * // Watch properties from multiple sources
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.watch(
 *  () => [viewElement.stationary, viewElement.zoom],
 *  ([stationary, zoom]) => {
 *    // Only print the new zoom value when the map component is stationary
 *    if(stationary){
 *      console.log(`Change in zoom level: ${zoom}`);
 *    }
 *  }
 * );
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 */
export function watch<T, U extends T>(getValue: ReactiveWatchExpression<T>, callback: ReactiveWatchCallback<T>, options?: ReactiveWatchOptions<U>): ResourceHandle;

/**
 * Watches the value returned by the `getValue` expression and calls the `callback` when it becomes truthy.
 *
 * @param getValue - Expression used to get the current value. All accessed properties will be tracked.
 * @param callback - The function to call when the value becomes truthy.
 * @param options - Options used to configure how the tracking happens and how the callback is to be called.
 * @returns A watch handle.
 * @example
 * // Observe when a boolean property becomes not truthy
 * const handle = reactiveUtils.when(
 *   () => !layerView.updating,
 *   () => {
 *     console.log("LayerView finished updating.");
 *   });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * @example
 * // Observe when a boolean property becomes true
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.when(
 *   () => viewElement?.stationary === true,
 *   async () => {
 *     console.log("User is no longer interacting with the map");
 *     await drawBuffer();
 *   });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * @example
 * // Observe a boolean property for truthiness.
 * // Providing `once: true` in ReactiveWatchOptions
 * // only fires the callback once
 * const featuresComponent = document.querySelector("arcgis-features");
 * const handle = reactiveUtils.when(
 *   () => featuresComponent.open,
 *   () => {
 *     console.log("The features component is open");
 *   },
 *   {
 *     once: true
 *   });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 */
export function when<T, U extends T>(getValue: ReactiveWatchExpression<T | null | undefined>, callback: (newValue: T, oldValue?: T) => void, options?: ReactiveWatchOptions<U>): ResourceHandle;

/**
 * Callback to be called when an event listener is added or removed.
 *
 * @param target - The event target to which the listener was added or from which it was removed.
 */
export type ReactiveListenerChangeCallback<T> = (target: T) => void;

/** Options used to configure the behavior of [on()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#on). */
export interface ReactiveOnOptions<Target> {
  /**
   * Whether to fire the callback synchronously or on the next microtask.
   *
   * @default false
   */
  sync?: boolean;
  /**
   * Whether to fire the callback only once.
   *
   * @default false
   */
  once?: boolean;
  /** Called when the event listener is added. */
  onListenerAdd?: ReactiveListenerChangeCallback<Target>;
  /** Called when the event listener is removed. */
  onListenerRemove?: ReactiveListenerChangeCallback<Target>;
}

/**
 * Watches the value returned by the `getTarget` function for changes and
 * automatically adds or removes an event listener for a given event, as
 * needed.
 *
 * @param getTarget - Function which returns the object to which the event listener is to be added.
 * @param eventName - The name of the event to add a listener for.
 * @param callback - The event handler callback function.
 * @param options - Options used to configure how the tracking happens and how the callback is to be called.
 * @returns A watch handle.
 * @example
 * // Adds a click event on a map component when it changes
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.on(
 *   () => viewElement,
 *   "arcgisViewClick",
 *   (event) => {
 *     console.log("arcgisViewClick event emitted: ", event);
 *   });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 * @example
 * // Adds a drag event on a map component and adds a callback
 * // to check when the listener is added and removed.
 * // Providing `once: true` in the ReactiveListenerOptions
 * // removes the event after first callback.
 * const viewElement = document.querySelector("arcgis-map");
 * const handle = reactiveUtils.on(
 *   () => viewElement,
 *   "arcgisViewDrag",
 *   (event) => {
 *     console.log(`Drag event emitted: ${event}`);
 *   },
 *   {
 *     once: true,
 *     onListenerAdd: () => console.log("Drag listener added!"),
 *     onListenerRemove: () => console.log("Drag listener removed!")
 *   });
 * // Remove the handle when it's no longer needed to stop watching for changes
 * handle.remove();
 */
export function on<Target extends EventTarget | EventedMixin>(getTarget: ReactiveOnExpression<Target | null | undefined>, eventName: string, callback: ReactiveOnCallback<any>, options?: ReactiveOnOptions<Target>): ResourceHandle;

/**
 * Tracks any properties being evaluated by the `getValue` expression. When `getValue` changes, it
 * returns a promise containing the value. This method only tracks a single change.
 *
 * @param getValue - Expression to be tracked.
 * @param signal - Abort signal which can be used to cancel the promise from resolving.
 * @returns A promise which resolves when the tracked expression changes.
 * @example
 * // Observe the first time a property equals a specific string value
 * reactiveUtils.once(
 *   () => featureLayer.loadStatus === "loaded")
 *   .then(() => {
 *     console.log("featureLayer loadStatus is loaded.");
 *   });
 * @example
 * // Use a comparison operator to resolve on the first tracked change that
 * // matches this expression.
 * const viewElement = document.querySelector("arcgis-map");
 * const someFunction = async () => {
 *   await reactiveUtils.once(() => viewElement.zoom > 20);
 *   console.log("Zoom level is greater than 20!");
 * }
 * @example
 * // Use a comparison operator and optional chaining to observe a
 * // first time difference in numerical values.
 * reactiveUtils.once(
 *   () => map?.allLayers?.length > 2)
 *   .then((value) => {
 *     console.log(`The map now has ${value} layers.`);
 *   });
 */
export function once<T>(getValue: ReactiveWatchExpression<T>, signal?: AbortSignal | AbortOptions | null | undefined): Promise<T>;

/**
 * Tracks any properties being evaluated by the `getValue` expression. When `getValue` becomes truthy,
 * it returns a promise containing the value. This method only tracks a single change.
 *
 * @param getValue - Expression to be tracked.
 * @param signal - Abort signal which can be used to cancel the promise from resolving.
 * @returns A promise which resolves once the tracked expression becomes truthy.
 * @example
 * // Check for the first time a property becomes truthy
 * const viewElement = document.querySelector("arcgis-map");
 * reactiveUtils.whenOnce(
 *   () => viewElement.popupElement.open)
 *   .then(() => {
 *     console.log("Popup used for the first time");
 *   });
 * @example
 * // Check for the first time a property becomes not truthy
 * const someFunction = async () => {
 *   await reactiveUtils.whenOnce(() => !layerView.updating);
 *   console.log("LayerView is no longer updating");
 * }
 * @example
 * // Check for the first time a property becomes truthy
 * // And, use AbortController to cancel the async callback
 * const abortController = new AbortController();
 * const viewElement = document.querySelector("arcgis-map");
 *
 * // Observe the map component's updating state
 * // The updating property is false by default
 * reactiveUtils.whenOnce(
 *   () => viewElement?.updating, {signal: abortController.signal})
 *   .then((updating) => {
 *     console.log(`Map component updated.`)
 *   });
 *
 * // Cancel the async callback
 * const someFunction = () => {
 *   abortController.abort();
 * }
 */
export function whenOnce<T>(getValue: ReactiveWatchExpression<T | null | undefined>, signal?: (AbortSignal | AbortOptions) | null | undefined): Promise<T>;