import type Extent from "../geometry/Extent.js";
import type SpatialReference from "../geometry/SpatialReference.js";
import type Layer from "./Layer.js";
import type ElevationSampler from "./support/ElevationSampler.js";
import type ElevationTileData from "./support/ElevationTileData.js";
import type TileInfo from "./support/TileInfo.js";
import type { EsriPromiseMixin } from "../core/Promise.js";
import type { AbortOptions } from "../core/promiseUtils.js";
import type { ElevationQueryResult, ElevationQueryGeometry, ElevationQueryOptions, CreateElevationSamplerOptions } from "./support/types.js";
import type { TileInfoProperties } from "./support/TileInfo.js";
import type { SpatialReferenceProperties } from "../geometry/SpatialReference.js";
import type { LayerProperties } from "./Layer.js";

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

export interface FetchTileOptions extends AbortOptions {
  /**
   * The value representing pixels
   *   in the tile that don't contain an elevation value.
   */
  noDataValue?: number;
}

/**
 * BaseElevationLayer is intended to be extended for creating custom elevation layers.
 * You create a custom
 * [ElevationLayer](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-ElevationLayer.html) by calling
 * [Accessor#createSubclass()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-Accessor.html#createSubclass) on the
 * `BaseElevationLayer`.
 *
 * If the new layer needs to fetch and prepare resources, you can initialize properties
 * asynchronously prior to loading the layer. This is handled in the [load()](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#load) method.
 * This method is called once, either by you or by the view, when the layer is about to be displayed.
 * In the body of the method you can call [addResolvingPromise()](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#addResolvingPromise)
 * to add a promise that has to be resolved before the layer is considered [loaded](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#loaded).
 *
 * You must overwrite the logic in the [fetchTile()](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#fetchTile)
 * method to return the values of the custom elevation data.
 * This can be done to exaggerate actual elevation values or for mapping
 * thematic data as an elevation layer.
 * When transforming the values of the elevation data it is recommended to
 * keep the no data value unchanged.
 *
 * ```js
 * const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
 *   load: function() {
 *     // add loadable dependencies here and include
 *     // their returned promises in the
 *     // addResolvingPromise() method
 *     this._elevation = new ElevationLayer({
 *       url: "//elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
 *     });
 *     this.addResolvingPromise(this._elevation.load());
 *   },
 *
 *   fetchTile: function(level, row, col, options) {
 *     // must resolve to an object with the following properties:
 *     // values <number[]>: an array of elevation values for each pixel
 *     // width <number>: the width of the tile in pixels
 *     // height <number>: the height of the tile in pixels
 *     // noDataValue <number>: value of pixels where no elevation data is present
 *     return this._elevation.fetchTile(level, row, col, options).then(function(data) {
 *       let exaggeration = this.exaggeration;
 *       // `data` is an object that contains the width of the tile in pixels,
 *       // the height of the tile in pixels, and the values of each pixel
 *       for (let i = 0; i < data.values.length; i++) {
 *          // each value represents an elevation sample for the
 *          // given pixel position in the tile
 *          // check if the value is a no data value
 *          if (data.values[i] !== data.noDataValue) {
 *            // multiply the elevation value by the exaggeration value
 *            data.values[i] *= exaggeration;
 *          }
 *       }
 *       return data;
 *     }.bind(this))
 *   }
 * });
 * ```
 *
 * Once the layer is created, you must add it to the layers of the
 * [Map#ground](https://developers.arcgis.com/javascript/latest/api-reference/esri-Map.html#ground) property and add the map to a
 * [SceneView](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-SceneView.html) instance.
 *
 * ```js
 * let map = new Map({
 *   basemap: "satellite",
 *   ground: {
 *     layers: [ new ExaggeratedElevationLayer() ]
 *   }
 * });
 * sceneView.map = map;
 * ```
 *
 * @since 4.4
 * @see [Creating Custom Elevation Layers (slides) - 2017 Esri Dev Summit](https://proceedings.esri.com/library/userconf/devsummit17/papers/dev_int_97.pdf#page=13)
 * @see [Creating Custom Layers and LayerViews (video)](https://www.youtube.com/watch?v=QOoZ1lgWESA&t=20m11s)
 * @see [Sample - Custom ElevationLayer: Exaggerating elevation](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-exaggerated/)
 * @see [Sample - Custom ElevationLayer: Thematic data as elevation](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-thematic/)
 * @see [Tiled Elevation Service documentation](https://developers.arcgis.com/documentation/tiled-elevation-service/)
 */
export default abstract class BaseElevationLayer extends Layer {
  constructor(properties?: BaseElevationLayerProperties);
  /**
   * 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);
  /**
   * The layer type provides a convenient way to check the type of the layer
   * without the need to import specific layer modules.
   */
  get type(): "base-elevation";
  /**
   * Adds a promise to the layer's loadable chain.
   * This is typically used in the [load()](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#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/api-reference/esri-layers-BaseElevationLayer.html#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/api-reference/esri-layers-BaseElevationLayer.html#loadStatus) to being
   *   [loaded](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#loaded).
   * @example
   * // the _elevationLayer must load() prior to the ExaggeratedElevationLayer
   * // resolving and moving to the "loaded" status
   * const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
   *   load: function() {
   *     this._elevationLayer = new ElevationLayer({
   *       url: "//elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
   *     });
   *
   *     this.addResolvingPromise(this._elevationLayer.load());
   *   }
   * });
   */
  addResolvingPromise<U, V extends EsriPromiseMixin>(promiseToLoad: PromiseLike<U> | V | PromiseLike<V> | null | undefined): void;
  /**
   * Creates an elevation sampler for the given [Extent](https://developers.arcgis.com/javascript/latest/api-reference/esri-geometry-Extent.html) by querying the service layer for
   * elevation data and caching it so values may be sampled quickly afterwards.
   *
   * The resolution of the cached data can be set using the `demResolution` option. In many cases, `auto` demResolution
   * can be used to get high quality elevation samples without the need to know exactly where the data in the service
   * is located. This is particularly useful for services which combine elevation data from many sources (such as the
   * world elevation service). If more control, or higher quality samples are required, use either `finest-contiguous`
   * or a fixed `{number}` resolution.
   *
   * @param extent - The extent for which to create the sampler.
   * @param options - Additional query options. See the table below.
   * @returns An elevation sampler.
   * @since 4.12
   */
  createElevationSampler(extent: Extent, options?: CreateElevationSamplerOptions): Promise<ElevationSampler>;
  /**
   * Fetches a tile at the given level, row, and column present in the view.
   * This method must be overwritten to display custom elevation values in the
   * [Map#ground](https://developers.arcgis.com/javascript/latest/api-reference/esri-Map.html#ground). Note that this method must return
   * a promise that resolves to an object with the properties defined in
   * [ElevationTileData](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-support-ElevationTileData.html).
   *
   * See the following samples for
   * examples of how to overwrite this method:
   *
   * * [Sample - Custom ElevationLayer: Exaggerating elevation](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-exaggerated/)
   * * [Sample - Custom ElevationLayer: Thematic data as elevation](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-thematic/)
   *
   * @param level - The level of detail of the tile to fetch.
   * @param row - The row (y) position of the tile to fetch.
   * @param column - The column (x) position of the tile to fetch.
   * @param options - Optional settings for the tile request.
   * @returns Resolves to an instance of [ElevationTileData](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-support-ElevationTileData.html).
   */
  fetchTile(level: number, row: number, column: number, options?: FetchTileOptions): Promise<ElevationTileData>;
  /**
   * Returns the bounds of the tile as an array of four numbers that can
   * be readily converted to an [Extent](https://developers.arcgis.com/javascript/latest/api-reference/esri-geometry-Extent.html) object.
   * See the table in the `returns` section below for more details about the
   * values returned by this method.
   *
   * This function can be used inside [fetchTile()](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-BaseElevationLayer.html#fetchTile) so you can get the
   * bounds of the current tile, convert it to an extent object, and request
   * the desired data for the given extent. See the
   * [Custom ElevationLayer: Thematic data as elevation](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-thematic/) sample for an example of how this method works.
   *
   * @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. The array
   *   has four items, each representing one bound of the extent. The values of each
   *   item are described in the table below.
   *
   * Index | Value
   * ------|------
   * 0 | Minimum x-value
   * 1 | Minimum y-value
   * 2 | Maximum x-value
   * 3 | Maximum y-value
   * @see [Creating custom layers (slides) - 2017 Esri Dev Summit](https://proceedings.esri.com/library/userconf/devsummit17/papers/dev_int_97.pdf#page=15)
   * @see [Sample - Custom ElevationLayer: Thematic data as elevation](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-thematic/)
   */
  getTileBounds(level: number, row: number, column: number, out?: [
      number,
      number,
      number,
      number
  ]): [
      number,
      number,
      number,
      number
  ];
  /**
   * Queries the service layer for elevation values for the given geometry.
   *
   * The returned result contains a copy of the geometry with z-values sampled from elevation data from the service.
   * The resolution from which the elevation is queried can be set using the `demResolution` option. In many cases,
   * `auto` demResolution can be used to get high quality elevation samples without the need to know exactly where the
   * data in the service is located. This is particularly useful for services which combine elevation data from many
   * sources (such as the world elevation service). If more control, or higher quality samples are required, use either
   * `finest-contiguous` or a fixed `{number}` resolution.
   *
   * @param geometry - The geometry to use for sampling elevation data.
   * @param options - Additional query options. See the table below.
   * @returns Resolves to an object with the sampled geometry, resolution information, and no data value.
   * @since 4.12
   * @see [ElevationLayer#queryElevation()](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-ElevationLayer.html#queryElevation)
   */
  queryElevation(geometry: ElevationQueryGeometry, options?: ElevationQueryOptions): Promise<ElevationQueryResult<ElevationQueryGeometry>>;
}