import type { ResourceHandle } from "./Handles.js";

export type GroupKey<T> = Exclude<T, ResourceHandle>;

/**
 * Accessor is an abstract class that facilitates the access to instance properties as well as a mechanism to watch for property changes.
 * It is possible to watch for a property changes by using the [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/Accessor/#watch) method.
 *
 * @since 4.0
 * @see [Guide - Implementing Accessor](https://developers.arcgis.com/javascript/latest/implementing-accessor/)
 * @see [Guide - Watching for component changes](https://developers.arcgis.com/javascript/latest/watch-for-changes/#watch-for-changes-in-web-components)
 * @see [Guide - Watching for changes](https://developers.arcgis.com/javascript/latest/watch-for-changes/#watch-for-changes-in-the-api)
 * @see [Get started - TypeScript](https://developers.arcgis.com/javascript/latest/get-started/#typescript)
 */
export default class Accessor {
  /**
   * Creates a subclass of the class calling this method.
   *
   * @param classDefinition - An object with properties and methods to mix in to the newly created class.
   * @returns Returns a constructor of the newly created class.
   * @since 4.21
   * @see [Guide - Implementing Accessor](https://developers.arcgis.com/javascript/latest/implementing-accessor/)
   */
  static createSubclass(classDefinition?: any): typeof Accessor;
  constructor(...args: any[]);
  /**
   * The name of the class. The declared class name is formatted as `esri.folder.className`.
   *
   * @since 4.7
   */
  readonly declaredClass: string;
  get destroyed(): boolean;
  /** Set to `true` once the initialize function has executed. */
  get initialized(): boolean;
  /**
   * Gets the value stored for a property. It can be used when creating a class extending Accessor to get the value stored internally without calling the property getter. For example, this may be used to return a default value for a property when the property is accessed before a value is set on it.
   *
   * @param propertyName - The name of the property to get.
   * @returns The property's value.
   * @example
   * ```js
   * get propertyWithDefault() {
   *   return this._get("propertyWithDefault") ?? defaultValue;
   * }
   * ```
   */
  protected _get<T>(propertyName: string): T;
  /**
   * Sets the stored value of a property. Calling `_set` directly updates the stored value and skip checks like defined in the `@property()` decoration like `readOnly` or `type`. This is typically used to update the stored value in a property setter, or to update a read only property.
   *
   * @param propertyName - The name of the property to set.
   * @param value - The new value to set on the property.
   * @example
   * ```js
   * \@subclass("Counter")
   * class Counter {
   *   \@property({ readOnly: true })
   *   value = 0;
   *
   *   increment() {
   *     this._set("value", this.value + 1);
   *   }
   * }
   * ```
   */
  protected _set(propertyName: any, value: any): this;
  /**
   * Adds one or more handles which are to be tied to the lifecycle of the object. The handles will
   * be removed when the object is destroyed.
   *
   * ```js
   * // Manually manage handles
   * const handle = reactiveUtils.when(
   *   () => !view.updating,
   *   () => {
   *     wkidSelect.disabled = false;
   *   },
   *   { once: true }
   * );
   *
   * this.addHandles(handle);
   *
   * // Destroy the object
   * this.destroy();
   * ```
   *
   * @param handleOrHandles - Handles marked for removal once the object is destroyed.
   * @param groupKey - Key identifying the group to which the handles should be added. All the handles in the group
   *    can later be removed with [removeHandles()](https://developers.arcgis.com/javascript/latest/references/core/core/Accessor/#removeHandles).
   *    If no key is provided the handles are added to a default group.
   * @since 4.25
   */
  addHandles<T>(handleOrHandles: ResourceHandle | ResourceHandle[], groupKey?: GroupKey<T>): void;
  destroy(): void;
  /**
   * Returns true if a named group of handles exist.
   *
   * @param groupKey - A group key.
   * @returns Returns `true` if a named group of handles exist.
   * @since 4.25
   * @example
   * // Remove a named group of handles if they exist.
   * if (obj.hasHandles("watch-view-updates")) {
   *   obj.removeHandles("watch-view-updates");
   * }
   */
  hasHandles<T>(groupKey?: GroupKey<T>): boolean;
  /**
   * Marks a property has changed. The next time the property is accessed,
   * the getter for that property will be invoked.
   * If the property is watched, the watcher callbacks will be called
   * if the new value of the property has effectively changed.
   *
   * @param propertyName - The name of the property that may have changed.
   */
  protected notifyChange(propertyName: string): void;
  /**
   * Removes a group of handles owned by the object.
   *
   * @param groupKey - A group key or an array or collection of group keys to remove.
   * @since 4.25
   * @example
   * obj.removeHandles(); // removes handles from default group
   *
   * obj.removeHandles("handle-group");
   * obj.removeHandles("other-handle-group");
   */
  removeHandles<T>(groupKey?: GroupKey<T>): void;
  /**
   * Sets the value of a property.
   *
   * Call `set()` with a property name and a value to change the value of the property.
   *
   * ```js
   * // setting the basemap of the map
   * map.set("basemap", "topo-vector");
   * // is equivalent to
   * map.basemap = "topo-vector";
   *
   * // currying set
   * let updateViewScale = view.set.bind(view, "scale");
   * updateViewScale(5000);
   * ```
   *
   * `set()` can be called with the path to a property and a value.
   * The property is not set if a property in the path doesn't exist.
   *
   * ```js
   * // updating the title of the basemap
   * map.set("basemap.title", "World Topographic Map");
   *
   * // is equivalent to
   * if (map.basemap != null) {
   *   map.basemap.title = "World Topographic Map";
   * }
   * ```
   *
   * @param path - The path to the property to set
   * @param value - The new value to set on the property.
   * @returns The instance.
   */
  set<T>(path: string, value: T): this;
  /**
   * An object with key-value pairs may be passed into `set()` to update multiple properties at once.
   *
   * ```js
   * // setting a viewpoint on the view
   * view.set({
   *   center: [-4.4861, 48.3904],
   *   scale: 5000
   * });
   *
   * // currying set
   * let updateView = view.set.bind(view);
   *
   * updateView({
   *   center: [-4.4861, 48.3904],
   *   scale: 5000
   * });
   * ```
   *
   * @param props - an object of key-value pairs
   * @returns The instance.
   */
  set(props: Record<string, any>): this;
  /**
   * Watches for property changes on the instance. For additional capabilities when observing changes on properties, see [reactiveUtils](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/).
   *
   *
   * Watching for property changes is essential for tracking changes on objects.
   * To start watching for changes on a property, call `watch()` with the property
   * name and a callback function that will execute each time the property changes.
   *
   * ```js
   * let handle = mapview.watch("scale", function(newValue, oldValue, propertyName, target) {
   *   console.log(propertyName + " changed from " + oldValue + " to " + newValue);
   * });
   * ```
   *
   * To stop watching for changes, call the `remove()` method on the object that `watch()` returns.
   *
   * ```js
   * handle.remove();
   * ```
   *
   * It is important to store the resulting objects from `watch()` to properly clean up the references.
   *
   * ```js
   * let viewHandles = [];
   * function setView(view) {
   *   // remove the handles for the current view.
   *   viewHandles.forEach(function(handle) {
   *     handle.remove();
   *   });
   *   viewHandles.length = 0;
   *
   *   this.view = view;
   *
   *   // watch for properties on the newly set view.
   *   if (view) {
   *     viewHandles.push(
   *       view.watch("scale", scaleWatcher);
   *     );
   *   }
   * }
   *
   * setView(mapView);
   * setView(null);
   * ```
   *
   * Like `get()` and `set()`, it is possible to watch for a property deep in the object hierarchy by passing a path.
   * If a property in the path doesn't exist the watch callback is called with `undefined`.
   *
   * ```js
   * let view = new SceneView({
   *   map: new Map({
   *     basemap: "streets-vector"
   *   })
   * });
   *
   * view.watch("map.basemap.title", (newValue, oldValue) => {
   *   console.log("basemap's title changed from " + oldValue + " to " + newValue);
   * });
   *
   * view.map.basemap = "topo-vector";
   * // output: "basemap's title changed from Streets to Topographic"
   *
   * view.map = null;
   * // output: "basemap's title changed from Topographic to undefined"
   * ```
   *
   * Pass a comma delimited list of property paths, or an array of property paths, to watch multiple properties with the same callback.
   * Use the third parameter of the callback call to determine what property changed.
   *
   * ```js
   * view.watch("center, scale, rotation", (newValue, oldValue, propertyName) => {
   *   console.log(propertyName + " changed");
   * });
   *
   * // equivalent of
   * view.watch(["center", "scale", "rotation"], (newValue, oldValue, propertyName) => {
   *   console.log(propertyName + " changed");
   * });
   *
   * // equivalent of
   * let callback = (newValue, oldValue, propertyName) => {
   *   console.log(propertyName + " changed");
   * }
   * view.watch("center", callback);
   * view.watch("scale", callback);
   * view.watch("rotation", callback);
   * ```
   *
   * `Accessor` doesn't call the watch callbacks for a property immediately after its value changes.
   * Instead, when a property's value changes and if that property is watched, `Accessor` schedules a
   * notification which is then processed at a later time.
   * Properties that change frequently like `view.scale` can be watched without having to throttle the callback.
   *
   * ```js
   * // Divides the view.scale three times
   * view.watch("scale", (newValue, oldValue) => {
   *   console.log("view's scale changed from " + oldValue + " to " + newValue);
   * });
   * console.log("current view scale: " + view.scale);
   * view.scale = view.scale / 2;
   * view.scale = view.scale / 2;
   * view.scale = view.scale / 2;
   * console.log("current view scale: " + view.scale);
   *
   * // output the following:
   * // current view scale: 36978595.474472
   * // current view scale: 4622324.434309
   * // view's scale changed from 36978595.474472 to 4622324.434309
   * ```
   *
   * @deprecated since version 4.32. Use [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch) instead.
   * @param path - The property or properties to watch. Multiple properties can be specified as a comma-separated list.
   * @param callback - The callback to execute when the property value has changed.
   * @returns A watch handle
   */
  watch(path: any, callback: WatchCallback): ResourceHandle;
}

/**
 * Callback to be called when a watched property changes.
 *
 * @param newValue - The new value of the watched property.
 * @param oldValue - The old value of the watched property.
 * @param propertyName - The property name.
 * @param target - The object containing the property being watched.
 * @deprecated since version 4.32. Use [watch()](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/#watch) instead.
 */
export type WatchCallback = (newValue: any, oldValue: any, propertyName: string, target: Accessor) => void;