import type { EventedMixin, EventedAccessor } from "./Evented.js";
import type { Types } from "./accessorSupport/types.js";

export type ReadonlyArrayOrCollection<T> = readonly T[] | ReadonlyCollection<T>;

/**
 * Event object emitted when a [Collection](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/) changes. It contains arrays of added, removed, and moved items.
 *
 * @since 5.0
 */
export interface CollectionChangeEvent<T = any> {
  /**
   * An array of items added to the collection using either [add()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#add) or [addMany()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#addMany).
   *
   * @since 5.0
   */
  added: T[];
  /**
   * An array of items removed from the collection using either [remove()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#remove),
   * [removeMany()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#removeMany), [removeAt()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#removeAt), or [removeAll()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#removeAll).
   *
   * @since 5.0
   */
  removed: T[];
  /**
   * An array of items that moved in the collection using [reorder()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#reorder).
   *
   * @since 5.0
   */
  moved: T[];
}

export interface CollectionEventObject<T> {
  /**
   * Indicates if the [@change](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#event-change) event
   *   can be cancelled.
   *
   * @default false
   */
  cancellable: boolean;
  /**
   * Indicates if this event has previously been
   *   cancelled by another event handler.
   *
   * @default false
   */
  defaultPrevented: boolean;
  /** The item to add or remove from the collection. */
  item: T;
  /**
   * A method that prevents the item from being added
   * or removed from the collection.
   */
  preventDefault(): void;
}

export type CollectionAfterItemEvent<T> = {
  /** The item to add or remove from the collection. */
  item: T;
};

export type CollectionAfterEvent<T> = {};

export interface CollectionEvents<T> {
  /**
   * Fires after an item has been [added](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#add), [reordered](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#reorder), or [removed](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#remove) from the Collection.
   * Using methods of other classes that affect properties of type Collection will also
   * cause this event to fire, such as
   * [Map.add()](https://developers.arcgis.com/javascript/latest/references/core/Map/#add), [Map.remove()](https://developers.arcgis.com/javascript/latest/references/core/Map/#remove), [Map.reorder()](https://developers.arcgis.com/javascript/latest/references/core/Map/#reorder).
   *
   * For example, `map.layers.add(newLyr)` and `map.add(newLyr)` which uses [Map.add()](https://developers.arcgis.com/javascript/latest/references/core/Map/#add)
   * to add a new layer to the `map.layers` collection will cause this event to fire.
   *
   * The change event can be used to notify the developer/user of changes to the collection.
   * The arrays in the collection event objects are reused. Make a copy of the collection event object if these objects need to be
   * processed in your application.
   *
   * @example
   * // This function will fire each time a layer is either added,
   * // moved, or removed from the map.layers Collection
   * // make a copy of the collection event object for processing
   * map.layers.on("change", function(event){
   *   const copiedEvent = JSON.parse(JSON.stringify(event));
   *   let newLayers = copiedEvent.added; // An array of layers added to the map.layers Collection
   *   let reorderedLayers = copiedEvent.moved;  // An array of layers moved in the Collection
   *   let removedLayers = copiedEvent.removed;  // An array of layers removed from map
   * });
   */
  change: CollectionChangeEvent<T>;
  /**
   * Fires before any modifications are performed on the collection. This event
   * can be used to prevent an item from being added or removed from the collection
   * by cancelling it with the `event.preventDefault()` method.
   *
   * @example
   * map.layers.on("before-changes", function(event){
   *   // prevents layers from being added/removed from the map
   *   event.preventDefault();
   * });
   */
  "before-changes": CollectionEventObject<T>;
  /**
   * Fires after an item has been added, reordered or removed from the collection.
   *
   * @example
   * map.layers.on("after-changes", function(event){
   *   console.log(event, " layer was added/removed from the map.");
   * });
   */
  "after-changes": CollectionAfterEvent<T>;
  /**
   * Fires before an item is added to the collection. This event
   * can be used to prevent an item from being added to the collection
   * by cancelling it with the `event.preventDefault()` method.
   *
   * @example
   * // prevents a layer from being added to the map more than once.
   * map.layers.on("before-add", function(event){
   *    if(map.layers.includes(event.item)){
   *      event.preventDefault();
   *      console.log("layer already exists in map.");
   *    }
   * });
   */
  "before-add": CollectionEventObject<T>;
  /**
   * Fires after an item has been added to the collection.
   *
   * @example
   * // indicates a layer has been added to the map
   * map.layers.on("after-add", function(event){
   *   console.log(event.item, " has been added to the map.");
   * });
   */
  "after-add": CollectionAfterItemEvent<T>;
  /**
   * Fires before an item has been removed from the collection. This event
   * can be used to prevent an item from being removed from the collection
   * by cancelling it with the `event.preventDefault()` method.
   *
   * @example
   * // prevents a layer from being removed from the basemap
   * map.basemap.baseLayers.on("before-remove", function(event){
   *    if(map.basemap.baseLayers.includes(event.item)){
   *      event.preventDefault();
   *      console.log("layer cannot be removed from basemap.");
   *    }
   * });
   */
  "before-remove": CollectionEventObject<T>;
  /**
   * Fires after an item has been removed from the collection.
   *
   * @example
   * // indicates a layer has been removed from the map
   * map.layers.on("after-remove", function(event){
   *   console.log(event.item, " has been removed from the map.");
   * });
   */
  "after-remove": CollectionAfterItemEvent<T>;
}

/**
 * Collection stores an array of items of the same type.
 * It provides useful utility methods for working with items in the Collection, including
 * [filter()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#filter), [find()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#find), and [reduce()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#reduce).
 *
 * A Collection can be of any type. For example, [GraphicsLayer.graphics](https://developers.arcgis.com/javascript/latest/references/core/layers/GraphicsLayer/#graphics)
 * is a collection of graphics that are stored in the GraphicsLayer. You can use the methods found in the Collection class
 * to add, remove, re-order, or manipulate graphics in a GraphicsLayer.
 *
 * Another example of a Collection is [Map.layers](https://developers.arcgis.com/javascript/latest/references/core/Map/#layers), which is a Collection of operational layers included in
 * the [Map](https://developers.arcgis.com/javascript/latest/references/core/Map/).
 *
 * ```js
 * // Removes a layer from the map using Collection.remove();
 * map.layers.remove(layer);
 * ```
 *
 * The [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#event-change) fires each time an item is [added](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#add), [moved](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#reorder), or [removed](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#remove)
 * from the Collection.
 *
 * As of version 4.18, you can iterate through the items of a Collection using [for...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of).
 *
 * ```js
 * // a collection of graphics displayed in the view
 * const graphics = view.graphics;
 *
 * for (const graphic of graphics){
 *   // do something with each view graphic
 * }
 * ```
 * As of version 4.23, [reactiveUtils](https://developers.arcgis.com/javascript/latest/references/core/core/reactiveUtils/) can be utilized to watch for property changes on items in a Collection.
 *
 * ```js
 * // reactiveUtils watch method can be used to watch the visible
 * // property of each layer within the map.allLayer's collection
 * const handle = reactiveUtils.watch(
 *   () => view.map.allLayers.every((layer) => layer.visible),
 *   (allVisible) => {
 *     console.log(`All layers are visible = ${allVisible}`);
 *   }
 * );
 * ```
 *
 * @since 4.0
 * @see [GraphicsLayer.graphics](https://developers.arcgis.com/javascript/latest/references/core/layers/GraphicsLayer/#graphics)
 * @see [Map.layers](https://developers.arcgis.com/javascript/latest/references/core/Map/#layers)
 */
export default class Collection<T = any> extends EventedAccessor {
  /**
   * @deprecated
   * Do not directly reference this property.
   * Use EventNames and EventTypes helpers from \@arcgis/core/Evented
   */
  "@eventTypes": CollectionEvents<T>;
  /**
   * Determines whether the passed value is a Collection.
   *
   * @param value - The value to be checked.
   * @returns true if the test passes, false otherwise.
   */
  static isCollection<T>(value: T): value is Extract<T, Collection<unknown>>;
  /**
   * Creates a subclass of Collection containing a typed object.
   *
   * @param type - The type to assign the Collection.
   * @returns The typed collection.
   * @example
   * const [Collection, Point] = await $arcgis.import([
   *   "@arcgis/core/core/Collection.js",
   *   "@arcgis/core/geometry/Point.js"
   * ]);
   * let PointCollection = Collection.ofType(Point);
   * let collection = new PointCollection();
   * collection.add([-100,40]);
   * let point = collection.at(0);
   * // point.x = -100; point.y = 40
   * });
   */
  static ofType<T extends Base, Base = T>(type: (new (...args: any[]) => T) | Types<T, Base>): new (items?: ReadonlyArrayOrCollection<T> | { items?: ReadonlyArrayOrCollection<T>; }) => Collection<T>;
  constructor(items?: ReadonlyArrayOrCollection<T>);
  /**
   * The number of items in the Collection. Like a native Array, setting the length of a Collection can be used to
   * remove items from the end of the collection. However, unlike a native Array, you cannot increase the length of the
   * collection to reserve space for new items.
   */
  accessor length: number;
  [Symbol.iterator](): IterableIterator<T>;
  /**
   * Adds a single item to the collection. The [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after an item is added to the Collection.
   *
   * @param item - The item to add.
   * @param index - Zero-based index of where in the collection to add the item.
   *                         If not specified, the items will be added at the end.
   * @example
   * let gpc = new Graphic();  // Creates a new graphic
   * let layer = new GraphicsLayer(); // Creates a new graphics layer
   * layer.graphics.add(gpc);  // Adds graphic to layer's graphics collection
   */
  add(item: T, index?: number): this;
  /**
   * Adds multiple items to the collection. The [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after items are added to the Collection.
   *
   * @param items - An array or collection of items to add.
   * @param index - Zero-based index of where in the collection to add the items.
   *                         If not specified, the items will be added at the end.
   * @example
   * // Creates two new graphics
   * let gpc1 = new Graphic();
   * let gpc2 = new Graphic();
   *
   * let layer = new GraphicsLayer(); // Creates a new graphics layer
   *
   * // Adds both graphics to layer's graphics collection
   * layer.graphics.addMany([gpc1, gpc2]);
   */
  addMany(items: ReadonlyArrayOrCollection<T>, index?: number): this;
  /**
   * Returns the item at the specified index, allowing for positive and negative integers.
   * Negative integers count back from the last item in the array.
   *
   * @param index - Index of the item in the Collection to retrieve.
   *   It can be a relative index from the end of the Collection.
   * @returns The item in the Collection stored at the specified index.
   * @since 4.23
   * @see [Array.prototype.at()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at)
   * @example
   * // get the layer at the first position
   * let firstLayer = map.layers.at(0);
   * // get the layer at the last position
   * let lastLayer = map.layers.at(-1);
   */
  at(index: number): T | undefined;
  /**
   * Creates a deep clone of the Collection. To create a shallow clone
   * of the collection, use [slice()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#slice).
   *
   * @returns A clone of the Collection that
   * invoked this method.
   * @see [slice()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#slice)
   * @example
   * // slides is a clone of the scene slides
   * let slides = scene.presentation.slides.clone();
   */
  clone(): Collection<T>;
  /**
   * Creates a new Collection containing the items in the original Collection joined
   * with the items in the input array or Collection.
   *
   * @param value - The array or Collection to append to
   *                                                       the existing Collection.
   * @returns A new Collection comprised of the items in the Collection that
   *   invoked this method combined with the input items.
   * @see [Array.prototype.concat()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat)
   * @example
   * // creating a collection of all the basemap's layers.
   * let basemap = map.basemap;
   * let basemapLayers = basemap.baseLayers.concat(basemap.referenceLayers);
   */
  concat(value: ReadonlyArrayOrCollection<T>): Collection<T>;
  /**
   * Removes and destroys instances of all the items of the Collection.
   *
   * @since 4.30
   */
  destroyAll(): void;
  /**
   * Removes each item in the input array and destroys it. If an item is present multiple times in the collection, only the first occurrence is removed and destroyed. The
   * [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after an item is removed from the Collection.
   *
   * @param items - The items to remove and destroy.
   * @returns The removed and destroyed items present in the collection.
   * @since 4.30
   */
  destroyMany(items: ReadonlyArrayOrCollection<T>): T[];
  /**
   * Removes all the items of the Collection and executes the input function for each item.
   *
   * @param callback - The function to call for each item in the Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @since 4.33
   */
  drain(callback: ItemCallback<T>, thisArg?: any): void;
  /**
   * Determines whether all items in the Collection pass a test defined by `callback`. Each item
   * in the Collection is passed into the callback until one returns a value of `false`.
   *
   * @param callback - The function to call for each item in the Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns `true` if every call to the `callback` function returned `true`. Returns
   *                   `false` if at least one call to the `callback` returned `false`.
   * @see [Array.prototype.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every)
   * @example
   * let meetsStandardSize = graphicsLayer.graphics.every(function(item, i){
   *   // Tests each geometry's area to see if it is greater than 1,000 acres
   *   return calculateArea(item.geometry) > 1000;
   * });
   */
  every(callback: ItemTestCallback<T>, thisArg?: any): boolean;
  /**
   * Filters the Collection's items based on a test defined by the
   * `callback` function. Each item is passed into the `callback` function, which
   *  returns `true` if the item passes the test and `false` if it does not.
   *
   * @param callback - The function that defines a test for
   *   determining whether to return the item in a new Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns a new Collection containing the items that passed the filter test.
   * @see [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
   * @example
   * // filteredLayers is a Collection of all the non-visible layers in the map
   * let filteredLayers = map.layers.filter(function(layer){
   *   return !layer.visible;
   * });
   */
  filter<S extends T>(callback: (item: T, index: number, array: T[]) => item is S, thisArg?: any): Collection<S>;
  /**
   * Filters the Collection's items based on a test defined by the
   * `callback` function. Each item is passed into the `callback` function, which
   *  returns `true` if the item passes the test and `false` if it does not.
   *
   * @param callback - The function that defines a test for
   *   determining whether to return the item in a new Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns a new Collection containing the items that passed the filter test.
   * @see [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
   * @example
   * // filteredLayers is a Collection of all the non-visible layers in the map
   * let filteredLayers = map.layers.filter(function(layer){
   *   return !layer.visible;
   * });
   */
  filter(callback: ItemTestCallback<T>, thisArg?: any): Collection<T>;
  /**
   * Returns an item in the Collection if that item passes a test as
   * defined in the `callback` function. Each item is passed into the
   * `callback` function, which
   * returns `true` if the item passes the test and `false` if it does not.
   *
   * @param callback - The testing function that will assess
   *   each item in the Collection. Returns `true` if an item passes the test
   *   and `false` if it fails.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns The first item in the Collection that satisfies the test function.
   * @see [Array.prototype.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
   * @example
   * // If the id of a map layer is already known, get the layer that matches the id
   * let myLayer = map.layers.find(function(layer){
   *   return layer.id === "speciesLyr01";
   * });
   * // myLayer references the layer in map with ID "speciesLyr01"
   */
  find(callback: ItemTestCallback<T>, thisArg?: any): T | undefined;
  /**
   * Returns the index of an item in the Collection if that item passes a test as
   * defined in the `callback` function. Each item is passed into the `callback` function, which
   * returns `true` if the item passes the test and `false` if it does not.
   *
   * @param callback - The testing function that will assess each
   *   item in the Collection. Returns `true` if an item passes the test and
   *   `false` if it fails.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns the index of the Collection item that satisfies the test function.
   *             If an item fails the test, `-1` is returned.
   * @see [Array.prototype.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)
   * @example
   * // gpcIndex is assigned the index of the first graphic whose name
   * // property is 'Redlands'. This result can be used in getItemAt()
   * let gpcIndex = graphicsLyr.graphics.findIndex(function(item){
   *   return item.attributes.name === "Redlands";
   * });
   */
  findIndex(callback: ItemTestCallback<T>, thisArg?: any): number;
  /**
   * Flattens a hierarchical Collection containing at least one child collection.
   * Each item in the collection is passed into the `callback` function, which
   * should check for child collections specified by the developer.
   * A flat collection of all items (parent and children) is returned.
   *
   * This is useful for scenarios where the user would like to search all the layers
   * in a map, including a [GroupLayer's layers](https://developers.arcgis.com/javascript/latest/references/core/layers/GroupLayer/#layers)
   * and a [MapImageLayer's sublayers](https://developers.arcgis.com/javascript/latest/references/core/layers/MapImageLayer/#sublayers).
   * The callback should return the sub-collection of the item. If multiple levels
   * of collections exist in the hierarchy, then this method recursively executes for
   * all sub-collections.
   *
   * @param callback - A function that will assess each item in the Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns a flat collection of all items in the
   * original collection and their children.
   * @example
   * // create a MapImageLayer with several sublayers and add to a map
   * // containing another GraphicsLayer
   * let layer = new MapImageLayer({ sublayers: [ ... ] });
   * let map = new Map({
   *   layers: [ layer, new GraphicsLayer() ]
   * });
   *
   * // A flat collection of all layers and sublayers
   * // (if layer is a MapImageLayer) in the map.
   * // This collection may be searched or used for other purposes
   * let allLayersAndSublayers = map.layers.flatten(function(item){
   *   return item.layers || item.sublayers;
   * });
   */
  flatten(callback: ItemFlattenCallback<T>, thisArg?: any): Collection<T>;
  /**
   * Flattens a hierarchical Collection containing at least one child collection.
   * Each item in the collection is passed into the `callback` function, which
   * should check for child collections specified by the developer.
   * A flat collection of all items (parent and children) is returned.
   *
   * This is useful for scenarios where the user would like to search all the layers
   * in a map, including a [GroupLayer's layers](https://developers.arcgis.com/javascript/latest/references/core/layers/GroupLayer/#layers)
   * and a [MapImageLayer's sublayers](https://developers.arcgis.com/javascript/latest/references/core/layers/MapImageLayer/#sublayers).
   * The callback should return the sub-collection of the item. If multiple levels
   * of collections exist in the hierarchy, then this method recursively executes for
   * all sub-collections.
   *
   * @param callback - A function that will assess each item in the Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns a flat collection of all items in the
   * original collection and their children.
   * @example
   * // create a MapImageLayer with several sublayers and add to a map
   * // containing another GraphicsLayer
   * let layer = new MapImageLayer({ sublayers: [ ... ] });
   * let map = new Map({
   *   layers: [ layer, new GraphicsLayer() ]
   * });
   *
   * // A flat collection of all layers and sublayers
   * // (if layer is a MapImageLayer) in the map.
   * // This collection may be searched or used for other purposes
   * let allLayersAndSublayers = map.layers.flatten(function(item){
   *   return item.layers || item.sublayers;
   * });
   */
  flatten<U>(callback: ItemFlattenCallback<U>, thisArg?: any): Collection<U>;
  /**
   * Executes the input function for each item in the Collection.
   *
   * @param callback - The function to call for each item in the Collection.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @see [Array.prototype.forEach()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
   * @example
   * graphicsLayer.graphics.forEach(function(item, i){
   *   // Do something here to each graphic like calculate area of its geometry
   *   calculateArea(item.geometry);
   * });
   */
  forEach(callback: ItemCallback<T>, thisArg?: any): void;
  /**
   * Returns the item at the specified index.
   *
   * @param index - Zero-based index of the item in the Collection to retrieve.
   * @returns The item in the Collection stored at the specified index.
   * @example
   * // Assigns the base layer at index 0 to baseLayer
   * let baseLayer = map.basemap.baseLayers.getItemAt(0);
   */
  getItemAt(index: number): T | undefined;
  /**
   * Tests if an item is present in the new Collection.
   *
   * @param searchElement - The item to search for in the collection.
   * @returns `true` if the item is in the collection.
   * @see [Array.prototype.includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)
   * @example
   * // check if a layer is in the map's operational layers.
   * if (view.map.layers.includes(myLayer)) {
   *   // ...
   * }
   */
  includes(searchElement: T): boolean;
  /**
   * Returns the index of an element in the collection.
   *
   * @param searchElement - Item to search for in the collection.
   * @param fromIndex - Use if you don't want to search the whole
   *                                collection or you don't want to search from the start.
   * @returns The location of the first match found in the collection, or -1 if
   *                  there is no match.
   * @see [Array.prototype.indexOf()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
   * @example
   * // index is the index of the first graphic in the
   * // graphics layer that matches the input graphic
   * let index = graphicsLayer.graphics.indexOf(graphic);
   */
  indexOf(searchElement: T, fromIndex?: number): number;
  /**
   * Creates a string representation of the items in the Collection.
   *
   * @param separator - = , - The separator used between each item in the final string.
   * @returns The string representation of the items.
   * @see [Array.prototype.join()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join)
   * @example
   * let stringCollection = new Collection(["how", "are", "you", "doing?"]);
   * let phrase = stringCollection.join(" ");
   *
   * // Prints "how are you doing?"
   * console.log(phrase);
   */
  join(separator?: string): string;
  /**
   * Returns the last index of an element in the collection.
   *
   * @param searchElement - Item to search for in the collection.
   * @param fromIndex - Use if you don't want to search the whole
   *                              collection, or you don't want to search from the end.
   * @returns The location of the last match found in the collection, or -1 if
   *                  there is no match.
   * @see [Array.prototype.lastIndexOf()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf)
   * @example
   * // index is the index of the first graphic in the
   * // graphics layer that matches the input graphic
   * let index = graphicsLayer.graphics.lastIndexOf(graphic);
   */
  lastIndexOf(searchElement: T, fromIndex?: number): number;
  /**
   * Passes each Collection item into the `callback` function and returns a new array of the
   * returned values. For example, if you have a Collection of numbers and would like to add each number
   * by 10, you can use `map()` to create a new Collection with the same numbers incremented by 10.
   *
   * @param callback - The function that processes each item
   *   in the Collection and returns a new value at the same index of the original item.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns a new collection containing the new items
   *                                       generated from the `callback`.
   * @see [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
   * @example
   * // Gets the geometries of the graphics and assigns them to a new Collection
   * let geometries = graphicsLayer.graphics.map(function(item, i){
   *   return item.geometry;
   * });
   */
  map<TRet>(callback: ItemMapCallback<T, TRet>, thisArg?: any): Collection<TRet>;
  /**
   * Removes the last item from the collection and returns it.
   *
   * @returns The last item in the collection.
   * @see [Array.prototype.pop()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop)
   * @example
   * // Removes the last layer in the map and stores it in lastLayer
   * let lastLayer = map.layers.pop();
   */
  pop(): T | undefined;
  /**
   * Adds an item(s) to the end of the collection.
   *
   * @param items - An item or comma-separated list of items to add to the end of the collection.
   * @returns The new length of the collection.
   * @see [Array.prototype.push()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push)
   * @example
   * // Adds a new graphic to the end of the graphics collection on a GraphicsLayer
   * graphicsLyr.graphics.push(newGraphic);
   * @example
   * // Adds three new graphics to the end of the GraphicsLayer's graphics collection
   * graphicsLyr.graphics.push(g1, g2, g3);
   */
  push(...items: T[]): number;
  /**
   * Reduces all items in the collection (from left to right) into a single variable using `callback`.
   *
   * @param callback - The function that processes each
   *   item in the Collection and appends it to the previous item.
   * @param initialValue - Item to use as the first element to process in `callback`.
   * @returns Returns the value representing the reduction of the Collection's items.
   * @see [Array.prototype.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)
   * @see [reduceRight()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#reduceRight)
   */
  reduce<R = T>(callback: ItemReduceCallback<T, R>, initialValue?: R): R;
  /**
   * Reduces all items in the collection (from right to left) into a single variable using `callback`.
   *
   * @param callback - The function that processes each
   *   item in the Collection and appends it to the previous item.
   * @param initialValue - Item to use as the first element to process in `callback`.
   * @returns Returns the value representing the reduction of the Collection's items.
   * @see [Array.prototype.reduceRight()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight)
   * @see [reduce()](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#reduce)
   */
  reduceRight<R = T>(callback: ItemReduceCallback<T, R>, initialValue?: R): R;
  /**
   * Removes an item from the collection. The [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after an item is removed from the Collection.
   *
   * @param item - The item to remove.
   * @example
   * let layer = map.layers.at(4);
   * // Removes the fifth layer from the map
   * map.layers.remove(layer);
   */
  remove(item: T): T | undefined;
  /**
   * Removes all items from the collection.
   *
   * @example
   * // Removes all layers from the map
   * map.layers.removeAll();
   */
  removeAll(): T[];
  /**
   * Removes an item from the collection at a specified index. The [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after an item is removed from the Collection.
   *
   * @param index - The index of the item to remove.
   * @returns The removed item if present in the collection, `undefined` otherwise.
   * @example
   * // Removes the layer at index 4 of the map
   * map.layers.removeAt(4);
   */
  removeAt(index: number): T | undefined;
  /**
   * Removes each item in the input array.
   * If an item is present multiple times in the collection, only the first occurrence is removed. The
   * [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after an item is removed from the Collection.
   *
   * @param items - The items to remove.
   * @returns The removed items present in the collection.
   * @example
   * let refLayers = [refLyr1, refLyr2, refLyr3];
   * // Removes three reference layers in the refLayers
   * // collection from the basemap's referenceLayers
   * map.basemap.referenceLayers.removeMany(refLayers);
   */
  removeMany(items: ReadonlyArrayOrCollection<T>): T[];
  /**
   * Moves an item in the Collection to a specified index. The [change event](https://developers.arcgis.com/javascript/latest/references/core/core/Collection/#CollectionEvents) is fired after an item is moved in the Collection.
   *
   * @param item - The item to move.
   * @param index - The index to move the item to.
   * @returns The item that was moved. `undefined` if `item` is not in the collection
   * @example
   * // Get the first two layers in a map
   * let layer1 = map.layers.at(0);
   * let layer2 = map.layers.at(1);
   *
   * // Moves the second layer to the first position in the map.layers Collection
   * // effectively swapping the positions of layer1 and layer2
   * map.layers.reorder(layer2, 0);
   */
  reorder(item: T, index?: number): T | undefined;
  /**
   * Reverses the collection in place.
   *
   * @returns The reversed collection.
   * @see [Array.prototype.reverse()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse)
   * @example
   * // Reverse layers from the map
   * map.layers.reverse();
   */
  reverse(): this;
  /**
   * Removes the first item from the collection (at index 0), and returns it.
   * The remaining items of the collection are then shifted down one index from
   * their previous location.
   *
   * @returns The first item in the collection.
   * @see [Array.prototype.shift()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift)
   * @example
   * // Removes the first layer in the map and stores it in firstLyr
   * let firstLyr = map.layers.shift();
   */
  shift(): T | undefined;
  /**
   * Creates a new Collection comprised of a portion of the original Collection.
   *
   * @param begin - The index of the first item to extract.
   * @param end - The index of the last item to extract.
   * @returns Returns a new Collection containing the items in the
   *                                       specified range.
   * @see [Array.prototype.slice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)
   * @example
   * // get the graphics from index 50 to 100;
   * let selection = graphicsLayer.graphics.slice(50, 100);
   */
  slice(begin?: number, end?: number): Collection<T>;
  /**
   * Determines whether an item in the Collection passes a test defined by `callback`. Each item
   * in the Collection is passed into the callback until one returns a value of `true`.
   *
   * @param callback - The function
   *   that defines the test for each Collection item.
   * @param thisArg - Value to use as `this` when executing `callback`.
   * @returns Returns `true` if any of the items in the Collection pass the test
   *                   defined in `callback`. Returns `false` if all items fail the test.
   * @see [Array.prototype.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)
   * @example
   * // If at least one of the point graphics has a geometry whose
   * // elevation is above 1000m, then passes will have a value of true.
   * // Otherwise, it will be false.
   * let passes = graphicsLayer.graphics.some(function(item, i){
   *   return item.geometry.z > 1000;
   * });
   */
  some(callback: (item: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
  /**
   * Sorts the Collection in place.
   *
   * @param compareFunction - The function that defines
   *   a comparison of two items in the collection.
   * @see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
   * @example
   * // Sort graphics based on their elevation or z-value
   * let sortedGraphics = graphicsLayer.graphics.sort(function(a, b){
   *   if(a.geometry.z > b.geometry.z){
   *     return 1;
   *   }
   *   else if (a.geometry.z < b.geometry.z){
   *     return -1;
   *   }
   *   else {
   *     return 0;
   *   }
   * });
   */
  sort(compareFunction?: ItemCompareCallback<T>): this;
  /**
   * Removes existing items and/or adds new items to the collection.
   *
   * @param start - Index at which to start changing the collection.
   * @param deleteCount - Indicates the number of collection items to remove.
   * If omitted, or if greater than or equal to the number of elements after the position specified by start,
   * then all the elements from start to the end will be deleted. If `0` or `undefined` is used, then no
   * elements are removed and at least one new item should be added in the `items` parameter.
   * @param items - The item or comma-separated list of items to add to the collection.
   * @returns An array of the deleted items formerly part of the collection.
   * @see [Array.prototype.splice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)
   * @example
   * // map.layers is a collection of 6 layers
   * // Adds a seventh layer to the map at index 3
   * map.layers.splice(3, 0, layer7);
   * @example
   * // Removes two layers starting from index 2 and adds the
   * // specified layers in the positions of the former layers
   * let oldLayers = map.layers.splice(2, 2, layer8, layer9, layer10);
   *
   * // oldLayers = [layer2, layer3]
   */
  splice(start: number, deleteCount?: number, ...items: T[]): T[];
  /**
   * Returns a new array object containing the Collection's items.
   *
   * @returns An array containing the Collection's items.
   * @example
   * // Creates an array populated with the map's layers
   * let mapLayersArray = map.layers.toArray();
   */
  toArray(): T[];
  toString(): string;
  /**
   * Adds one or more items to the beginning of the collection.
   *
   * @param items - The item(s) to add to the beginning of the collection.
   * @returns The new length of the collection.
   * @see [Array.prototype.unshift()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)
   * @example
   * // If a map's basemap has 3 baseLayers: baseLyr0, baseLyr1, baseLyr2
   * map.basemap.baseLayers.unshift(baseLyr3);
   *
   * // Now the baseLayers collection is: baseLyr3, baseLyr0, baseLyr1, baseLyr2
   */
  unshift(...items: T[]): number;
}

export interface ReadonlyCollection<T> extends EventedMixin, Pick<Collection<T>, "addHandles" | "at" | "clone" | "concat" | "declaredClass" | "destroyed" | "every" | "filter" | "find" | "findIndex" | "flatten" | "forEach" | "getItemAt" | "hasHandles" | "includes" | "initialized" | "indexOf" | "join" | "lastIndexOf" | "map" | "reduce" | "reduceRight" | "removeHandles" | "slice" | "some" | "toArray" | "toString" | "watch" | typeof Symbol.iterator> {
  readonly "@eventTypes": Collection<T>["@eventTypes"];
  readonly length: number;
}

/**
 * The function that is called for each Collection item.
 *
 * @param item - The current item being assessed in the collection.
 * @param index - The index of the item being assessed.
 */
export type ItemCallback<T> = (item: T, index: number) => void;

/**
 * @param item - The current item being assessed in the collection.
 * @param index - The index of the item being assessed.
 */
export type ItemFlattenCallback<T> = (item: T, index: number) => T[] | Collection<T>;

/**
 * The function that defines a test and is called for each Collection item.
 *
 * @param item - The current item being assessed in the collection.
 * @param index - The index of the item being assessed.
 * @param array - The entire array of items in the collection.
 * @returns true if the test passes, false otherwise.
 */
export type ItemTestCallback<T> = (item: T, index: number, array: T[]) => boolean;

/**
 * The function that defines a mapping and is called for each Collection item.
 *
 * @param item - The current item being assessed in the collection.
 * @param index - The index of the item being assessed.
 * @returns the new value that replaces item.
 */
export type ItemMapCallback<T, TRet> = (item: T, index: number) => TRet;

/**
 * The function that defines a reducer.
 *
 * @param previousValue - The item previously reduced value.
 * @param currentValue - The current item being assessed in the collection.
 * @param index - The index of the item being assessed.
 * @returns the value to be passed to the next reducer.
 */
export type ItemReduceCallback<T, R> = (previousValue: R, currentValue: T, index: number) => R;

/**
 * The function that defines a comparison.
 *
 * @param firstItem - the first item in the comparison.
 * @param secondItem - the second item in the comparison.
 * @returns -1 if firstItem is smaller than secondItem, 1 if it is
 *   larger, and 0 if both are equal.
 */
export type ItemCompareCallback<T> = (firstItem: T, secondItem: T) => number;