import type SpatialReference from "../geometry/SpatialReference.js";
import type Layer from "./Layer.js";
import type TileInfo from "./support/TileInfo.js";
import type { EsriPromiseMixin } from "../core/Promise.js";
import type { AbortOptions } from "../core/promiseUtils.js";
import type { BlendLayer, BlendLayerProperties } from "./mixins/BlendLayer.js";
import type { RefreshableLayer, RefreshableLayerProperties } from "./mixins/RefreshableLayer.js";
import type { ScaleRangeLayer, ScaleRangeLayerProperties } from "./mixins/ScaleRangeLayer.js";
import type { TileInfoProperties } from "./support/TileInfo.js";
import type { SpatialReferenceProperties } from "../geometry/SpatialReference.js";
import type { LayerProperties } from "./Layer.js";

export interface BaseTileLayerProperties extends LayerProperties, RefreshableLayerProperties, ScaleRangeLayerProperties, BlendLayerProperties {
  /**
   * The spatial reference of the layer.
   *
   * @default {@link geometry/SpatialReference#WebMercator}
   */
  spatialReference?: SpatialReferenceProperties;
  /** The tiling scheme information for the layer. */
  tileInfo?: TileInfoProperties;
}

/**
 * This class may be extended to create a custom TileLayer. It is a generic tile layer class designed to work with various tile sources,
 * not just ArcGIS cached [map services](https://enterprise.arcgis.com/en/server/latest/publish-services/windows/what-is-a-map-service.htm),
 * which are specifically handled by the [TileLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/TileLayer/) class. Tile layers are composed of images, such as satellite imagery,
 * that are composed of square tiles mosaicked together in columns and rows, giving the layer the appearance that it is one continuous image.
 * They have several levels of detail (LOD) that permit users to zoom in to any region of the map and load additional tiles that depict features
 * in higher resolution at larger map scales.
 *
 * Tile layers often provide geographic context for other layers such as
 * [FeatureLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/FeatureLayer/) and tend to perform better than other layers,
 * such as [MapImageLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/MapImageLayer/) and [ImageryLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/ImageryLayer/),
 * that request and display a single image per view.
 *
 * You can create a custom tile layer by calling [Accessor.createSubclass()](https://developers.arcgis.com/javascript/latest/references/core/core/Accessor/#createSubclass)
 * on the `BaseTileLayer` class. You may create a custom tile layer for one of the following reasons:
 *
 * * The source of the images isn't explicitly supported by the ArcGIS Maps SDK for JavaScript
 * * Images need to be preprocessed prior to display in the view
 *
 * ### Setting up a custom tile layer
 * Unlike TileLayer, the BaseTileLayer does not make assumptions about the type of data it retrieves, making it more flexible and capable of working
 * with a wider variety of tile sources. By default, the BaseTileLayer is configured to use the Web Mercator spatial reference.
 * If a custom tile source does not use the Web Mercator spatial reference, the BaseTileLayer must be explicitly configured with three key properties
 * in its constructor to ensure proper alignment with the source. Those properties are [tileInfo](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#tileInfo), [spatialReference](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#spatialReference),
 * and [fullExtent](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#fullExtent).
 *
 * ```js
 * // set the spatial reference to New Zealand Transverse Mercator
 *  const spatialReference = new SpatialReference({
 *    wkid: 2193
 * });
 *
 * // create the origin point for the tileInfo
 * // The upper left corner of the tiling scheme,
 * const origin = new Point({
 *   x: -4020900,
 *   y: 19998100,
 *   spatialReference
 * });
 *
 * // Create LODs based on the source tiles
 * const tileInfo = new TileInfo({
 *   spatialReference,
 *   origin: origin,
 *   format: "mixed",
 *   lods: [{
 *      "level": 0,
 *      "resolution": 156543.03392799935,
 *      "scale": 591657527.591552
 *     },
 *     {
 *        "level": 1,
 *        "resolution": 78271.51696399967,
 *        "scale": 295828763.795776
 *      },
 *      // other LODs
 *    ]
 *  });
 *
 * const MyCustomTileLayer = BaseTileLayer.createSubclass({
 *   constructor() {
 *     this.tileInfo = tileInfo;
 *     this.spatialReference = spatialReference;
 *     this.fullExtent = new Extent ({
 *       xmin: -1497310.4689000002,
 *       ymin: 3678220.3271,
 *       xmax: 4749968.6755,
 *       ymax: 7192314.8459,
 *        spatialReference
 *     })
 *    },
 *    // properties of the custom tile layer
 *    properties: {
 *      urlTemplate: null,
 *    }
 * });
 * ```
 *
 * ### Request images as they are defined
 *
 * To request images as they are predefined from a data source, overwrite the [getTileUrl()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#getTileUrl)
 * method so it returns the URL for the requested tile for a given level, row and column.
 *
 * ```js
 * // override getTileUrl()
 * // generate the tile url for a given level, row and column
 * getTileUrl: function (level, row, col) {
 *   return this.urlTemplate.replace("{z}", level).replace("{x}", col).replace("{y}", row);
 * }
 * ```
 *
 * ### Preprocess images prior to display
 *
 * If data needs to be preprocessed prior to display, then override the [fetchTile()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#fetchTile)
 * method. For example, if you need to apply a
 * [compositing operation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)
 * to the image returned from the server before the image is displayed then you would override this method.
 *
 * ```js
 * // override fetchTile() method to process the data returned
 * // from the server.
 * fetchTile: function (level, row, col, options) {
 *   // call getTileUrl method to construct the Url for the image
 *   // for given level, row and column
 *   let url = this.getTileUrl(level, row, col);
 *
 *   // request for the tile based on the url returned from getTileUrl() method.
 *   // the signal option ensures that obsolete requests are aborted.
 *   return esriRequest(url, {
 *     responseType: "image",
 *     signal: options && options.signal
 *   })
 *   .then(function (response) {
 *     // when esriRequest resolves successfully,
 *     // process the image that is returned
 *     let image = response.data;
 *     let width = this.tileInfo.size[0];
 *     let height = this.tileInfo.size[0];
 *
 *     // create a canvas with a filled rectangle
 *     let canvas = document.createElement("canvas");
 *     let context = canvas.getContext("2d");
 *     canvas.width = width;
 *     canvas.height = height;
 *
 *     // Apply the color provided by the layer to the fill rectangle
 *     if (this.tint) {
 *       context.fillStyle = this.tint.toCss();
 *       context.fillRect(0, 0, width, height);
 *       // apply multiply blend mode to canvas' fill color and the tile
 *       // returned from the server to darken the tile
 *       context.globalCompositeOperation = "multiply";
 *     }
 *     context.drawImage(image, 0, 0, width, height);
 *     return canvas;
 *   }.bind(this));
 * }
 * ```
 *
 * See the following samples for examples of how this works:
 *
 * * [Sample - Custom TileLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-tilelayer/)
 * * [Sample - Custom LERC Layer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-lerc-2d/)
 * * [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/)
 *
 * If the custom tile layer requires loadable resources, then you must load all loadable
 * dependencies on the layer, within the [load()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#load) method. Add the promise returned
 * from the loadable resource with the [addResolvingPromise()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#addResolvingPromise) method.
 * The layer will then wait for all of dependencies to
 * finish loading before it is considered [loaded](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loaded).
 *
 * ```js
 * // Override load method
 * load: function () {
 *  // multiply property is an array of ArcGIS cached map services
 *  this.multiply.forEach(function (layer) {
 *    // loop through each tile layers and call
 *    // load method on each layer
 *    let promise = layer.load();
 *
 *    // add the promise of each load() method to addResolvingPromise()
 *    // the custom tile layer will be loaded when every promise is resolved
 *    this.addResolvingPromise(promise);
 *  }, this);
 * }
 * ```
 * The layer is responsible for generating the tile URL and fetching tiles from the server for the
 * level, row, and column provided by the [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). The
 * LayerView displays the fetched tiles.
 *
 * @since 4.4
 * @see [Creating Custom Layers and LayerViews (slides) - 2017 Esri Dev Summit](https://proceedings.esri.com/library/userconf/devsummit17/papers/dev_int_97.pdf)
 * @see [Creating Custom Layers and LayerViews (video)](https://youtu.be/QOoZ1lgWESA)
 * @see [Sample - Custom TileLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-tilelayer/)
 * @see [Sample - Custom LERC Layer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-lerc-2d/)
 * @see [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/)
 */
export default abstract class BaseTileLayer extends BaseTileLayerSuperclass {
  /**
   * The spatial reference of the layer.
   *
   * @default {@link geometry/SpatialReference#WebMercator}
   */
  get spatialReference(): SpatialReference;
  set spatialReference(value: SpatialReferenceProperties);
  /** The tiling scheme information for the layer. */
  get tileInfo(): TileInfo;
  set tileInfo(value: TileInfoProperties);
  /**
   * For [BaseTileLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/) the type is `base-tile`.
   *
   * @default "base-tile"
   */
  get type(): "base-tile" | "bing-maps";
  /**
   * Adds a Promise to the layer's loadable chain.
   * This is typically used in the [load()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#load) method to ensure that all
   * loadable resources required for the layer
   * to function are loaded prior to this layer resolving and becoming [loaded](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loaded).
   *
   * @param promiseToLoad - A promise that must resolve for the layer
   *   to resolve and move from the `loading` [status](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loadStatus) to being
   *   [loaded](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loaded).
   * @see [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/)
   * @example
   * // The requiredLayer must load() prior to the MyCustomTileLayer
   * // resolving and moving to the "loaded" status.
   * let MyCustomTileLayer = BaseTileLayer.createSubclass({
   *   load: function() {
   *     let promise = this.requiredLayer.load();
   *     this.addResolvingPromise(promise);
   *   }
   * });
   */
  addResolvingPromise<U, V extends EsriPromiseMixin>(promiseToLoad: PromiseLike<U> | V | PromiseLike<V> | null | undefined): void;
  /**
   * This method fetches a tile for the given level, row and column present in the view. Override this method if the data or image
   * returned from the server needs to be processed before it can be displayed.
   *
   * @param level - Level of detail of the tile to fetch. This value is provided by [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/).
   * @param row - The row (y) position of the tile fetch. This value is provided by [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/).
   * @param col - The column (x) position of the tile to fetch. This value is provided by [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/).
   * @param options - Optional settings for the tile request. The options have the following properties.
   * @returns Returns a promise that resolves to an
   * [HTMLImageElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement) or
   * [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement).
   * @see [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/)
   * @see [Sample - Custom LERC Layer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-lerc-2d/)
   * @example
   * // Process the image returned from the server before
   * // it is displayed.
   * fetchTile: function (level, row, col, options) {
   *   // call getTileUrl method to construct the URL for
   *   // the image for the given level, row and col
   *   let url = this.getTileUrl(level, row, col);
   *
   *   // request for the tile based on the generated url.
   *   // the signal option ensures that obsolete requests are aborted.
   *   return esriRequest(url, {
   *     responseType: "image",
   *     signal: options && options.signal
   *   })
   *   .then(function (response) {
   *     // get the image from the response
   *     let image = response.data;
   *     let width = this.tileInfo.size[0];
   *     let height = this.tileInfo.size[0];
   *
   *     let canvas = document.createElement("canvas");
   *     canvas.width = width;
   *     canvas.height = height;
   *     let context = canvas.getContext("2d");
   *
   *     // tint is a custom property of this layer
   *     // Apply the tint color provided by the application
   *     // to the canvas
   *     if (this.tint) {
   *       context.fillStyle = this.tint.toCss();
   *       context.fillRect(0, 0, width, height);
   *
   *       // The pixels of the top layer are multiplied by the corresponding
   *       // pixel of the bottom layer. A darker picture is the result.
   *       context.globalCompositeOperation = "multiply";
   *     }
   *     context.drawImage(image, 0, 0, width, height);
   *     return canvas;
   *   }.bind(this));
   * }
   */
  fetchTile(level: number, row: number, col: number, options?: AbortOptions): Promise<HTMLImageElement | HTMLCanvasElement>;
  /**
   * Returns the bounds of the tile as an array of four numbers that be readily
   * converted to an [Extent](https://developers.arcgis.com/javascript/latest/references/core/geometry/Extent/) object.
   * The value for each item in the array is described in the following table:
   *
   * Index | Value
   * ------|------
   * 0 | Minimum x-value
   * 1 | Minimum y-value
   * 2 | Maximum x-value
   * 3 | Maximum y-value
   *
   * @param level - The level of detail (LOD) of the tile.
   * @param row - The tile's row (y) position in the dataset.
   * @param column - The tiles column (x) position in the dataset.
   * @param out - Array for storing the tile bounds or extent.
   * @returns Returns an array representing the tile bounds or extent.
   */
  getTileBounds(level: number, row: number, column: number, out?: [
      number,
      number,
      number,
      number
  ]): [
      number,
      number,
      number,
      number
  ];
  /**
   * This method returns a URL to an image for a given level, row and column.
   * Override this method to construct the URL for the image based on user interaction.
   *
   * @param level - Level of detail. This value is provided by the
   *   [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/).
   * @param row - Tile row. This value is provided by the
   *   [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/).
   * @param col - Tile column. This value is provided by the
   *   [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/).
   * @returns Returns the URL to the tile image.
   * @example
   * // generate the tile url for a given level, row and column
   * getTileUrl: function (level, row, col) {
   *   // urlTemplate is a property of the custom layer.
   *   // value is provided by the application
   *   return this.urlTemplate.replace("{z}", level).replace("{x}", col).replace("{y}", row);
   * },
   */
  getTileUrl(level: number, row: number, col: number): string | null | undefined;
}
declare const BaseTileLayerSuperclass: typeof Layer & typeof RefreshableLayer & typeof ScaleRangeLayer & typeof BlendLayer