import type Extent from "../../../geometry/Extent.js";
import type Multipoint from "../../../geometry/Multipoint.js";
import type Point from "../../../geometry/Point.js";
import type Polygon from "../../../geometry/Polygon.js";
import type Polyline from "../../../geometry/Polyline.js";
import type Layer from "../../../layers/Layer.js";
import type MapView from "../../MapView.js";
import type ViewState from "../ViewState.js";
import type LayerView from "../../layers/LayerView.js";
import type { ScreenPoint } from "../../../core/types.js";
import type { ViewHit } from "../../types.js";
import type { TessellatedMesh } from "../types.js";
import type { LayerViewProperties } from "../../layers/LayerView.js";

export interface BaseLayerViewGL2DProperties extends LayerViewProperties, Partial<Pick<BaseLayerViewGL2D, "tiles">> {}

/**
 * A rectangle in screen-space.
 *
 * An instance of `Rect` is used to specify the screen-space geometry of the resulting marker when calling
 * [tessellatePoint()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#tessellatePoint) or [tessellateMultipoint()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#tessellateMultipoint).
 * The exact interpretation of _"screen-space"_ is ultimately implemented through a custom vertex shader;
 * a common convention is to interpret the values in the rect as being expressed in pixels or points.
 * See [TessellatedMesh](https://developers.arcgis.com/javascript/latest/references/core/views/2d/types/#TessellatedMesh) for more details.
 *
 * ![tessellation-helpers-uv](https://developers.arcgis.com/javascript/latest/assets/references/core/layers/tessellation-helpers-rect.png)
 */
export interface Rect {
  /** The `x`-coordinate of the upper-left corner of the rectangle, relative to the anchor of the marker. */
  x: number;
  /** The `y`-coordinate of the upper-left corner of the rectangle, relative to the anchor of the marker. */
  y: number;
  /** Width of the rectangle. */
  width: number;
  /** Height of the rectangle. */
  height: number;
}

/**
 * The destination to which [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render) should direct its output.
 *
 * Rendering output is saved into a [framebuffer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer),
 * possibly but not necessarily the default one, at a location
 * defined by the [viewport](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/viewport).
 *
 * An instance of this class is mantained internally by `BaseLayerViewGL2D` and can be accessed by a call to
 * [getRenderTarget()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#getRenderTarget). It is the same instance that is restored to the WebGL context by
 * calling [bindRenderTarget()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#bindRenderTarget).
 *
 * When control is handed over to [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render), the WebGL context is guaranteed to be in a default state
 * except for the currently bound framebuffer and the configured viewport. Implementations of [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render)
 * are free to change these parameters using `gl.bindFramebuffer()` and `gl.viewport()`, but they must send the
 * output of the rendering process to the render target.
 */
export interface RenderTarget {
  /** The framebuffer where the [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render) method should direct its output. */
  framebuffer: WebGLFramebuffer | null;
  /** A viewport that fully covers `framebuffer`. */
  viewport: [
      number,
      number,
      number,
      number
  ];
}

export interface RenderParameters {
  /**
   * The
   * WebGL or WebGL 2 context. Its concrete type depends on system configuration.
   * Every time that `render()` is called, the API automatically resets WebGL to a conventional
   * state which is _almost_ the default one; the only two things that may be non-default are
   * the bound framebuffer and the viewport, which is set to match the entire framebuffer.
   * _The body of `render()` **must not** change these settings_.
   */
  context: WebGL2RenderingContext;
  /** The object that describes view state. */
  state: ViewState;
  /** The stationary state of the `MapView`. */
  stationary: boolean;
}

/** Represents a tile reference. */
export interface Tile {
  /** The tile string identifier in the format `level/row/col/world` */
  id: string;
  /** The level identifier of the [LOD](https://developers.arcgis.com/javascript/latest/references/core/layers/support/LOD/) to which the tile belongs */
  level: number;
  /** The row identifier. */
  row: number;
  /** The column identifier. */
  col: number;
  /** When the projection allows world wrapping (e.g. Web Mercator), identifies the instance of the world this tile's `level`/`row`/`col`. */
  world: number;
  /** The number of map units per pixel in the tile. */
  resolution: number;
  /** The map scale at the tile's level. */
  scale: number;
  /** The coordinates of the top-left corner of the tile as an array of two numbers. The coordinates are in un-normalized map units. */
  coords: [
      number,
      number
  ];
  /** 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. */
  bounds: [
      number,
      number,
      number,
      number
  ];
}

/**
 * Represents the [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) of a [Layer](https://developers.arcgis.com/javascript/latest/references/core/layers/Layer/)
 * after it has been added to a [Map](https://developers.arcgis.com/javascript/latest/references/core/Map/) with a [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/). In contrast to the
 * related class [BaseLayerView2D](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerView2D/), this one exposes [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext)
 * rendering capabilities.
 *
 * > [!WARNING]
 * >
 * > ### Important notes
 * >
 * > **This interface is experimental**. Please read the following information carefully before using it in a product.
 * >
 * > Due to the nature of WebGL it is not possible to fully sandbox user-supplied code, and its malfunctions can affect the
 * > performance, visual quality and even stability of the entire [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/). For this reason, Esri
 * > does not provide any support for issues related to WebGL rendering in custom rendering code, or for issues that arise in
 * > [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/) rendering while using custom rendering code.
 *
 * This class may be extended to create a custom [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) for a Layer.
 * A [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) is created on demand by the [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/) when a layer is
 * added the to list of layers of its map.
 *
 * The subclass can implement multiple functions of the LayerView lifecycle.
 *
 * * First, the [attach()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#attach) method is called when the LayerView is about to start drawing the layer's content; it
 * is usually responsible for resource allocation.
 * * Then during the life of the LayerView the [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render) method is called once per frame; it must complete
 * drawing before returning.
 * * Finally the [detach()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#detach) method is called after the layer is removed from the map; it releases all allocated
 * resources and stops on-going requests.
 *
 * Each of these functions has access to the [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/)'s WebGL context through the instance property
 * [this.context](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#context). The developer must provide own shaders, meshes
 * and textures, and is responsible for setting the required GL states on the context.
 *
 * As a convenience for the developer, starting with version 4.14 of the API, `BaseLayerViewGL2D` includes tessellation
 * helper methods; the developer can supply [Point](https://developers.arcgis.com/javascript/latest/references/core/geometry/Point/), [Multipoint](https://developers.arcgis.com/javascript/latest/references/core/geometry/Multipoint/),
 * [Polyline](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polyline/), [Extent](https://developers.arcgis.com/javascript/latest/references/core/geometry/Extent/), or [Polygon](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polygon/)
 * geometries and have them converted to abstract descriptions of triangle meshes that can be easily fed to the GPU as
 * vertex and index buffers. The [SDK sample](https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-tessellation-helpers/)
 * explains in detail how to use the tessellation helpers and how to write compatible shaders.
 *
 * @since 4.11
 * @see [Sample - Tutorial: animated markers](https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-visuals/)
 * @see [Sample - Tessellation helpers](https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-tessellation-helpers/)
 * @see [Sample - Animated lines](https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-animated-lines/)
 * @see [Sample - Tiling support](https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-tiles/)
 * @see [Sample - Using deck.gl](https://developers.arcgis.com/javascript/latest/sample-code/custom-lv-deckgl/)
 * @example
 * let CustomLayerView2D = BaseLayerViewGL2D.createSubclass({
 *    render(renderParameters) {
 *      const gl = this.context;
 *
 *      ...
 *    }
 *
 *    attach() {
 *      const gl = this.context;
 *
 *      ...
 *    }
 *
 *    detach() {
 *      const gl = this.context;
 *
 *      ...
 *    }
 *  });
 *
 *  let CustomTileLayer = Layer.createSubclass({
 *    tileInfo: TileInfo.create({ spatialReference: { wkid: 3857 }}),
 *
 *    createLayerView(view) {
 *      if (view.type === "2d") {
 *        return new CustomLayerView2D({
 *          view: view,
 *          layer: this
 *        });
 *      }
 *    }
 *  });
 */
export default abstract class BaseLayerViewGL2D extends LayerView {
  constructor(properties?: BaseLayerViewGL2DProperties);
  /** The WebGL rendering context associated to this layer view. */
  get context(): WebGL2RenderingContext;
  /** References the layer this [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) represents. */
  get layer(): Layer;
  /**
   * The array of [Tile](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#Tile) objects computed to cover the MapView's visible area.
   * This array is updated when the view is animating or the user is interacting with it. Then if tiles have been added or removed,
   * [tilesChanged()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#tilesChanged) is called.
   *
   * @see [tilesChanged()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#tilesChanged)
   */
  tiles: Tile[];
  /**
   * References the [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/) this [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) belongs to.
   *
   * @since 4.28
   * @example
   * // Check for the first time layerView.updating becomes false. Then query for
   * // features that are visible within the view associated with the layer view.
   * await reactiveUtils.whenOnce(() => !layerView.updating);
   * const query = layerView.createQuery();
   * query.geometry = layerView.view.extent;
   * const result = layerView.queryFeatures(query);
   */
  get view(): MapView;
  /**
   * Method called after the [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) is created and right before it starts drawing the layer's content.
   * Typically this method is implemented to start watching property changes on the layer and to initialize WebGL objects such as
   * shaders.
   *
   * @see [detach()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#detach)
   * @example
   * // Create a shader program and a property watcher
   * attach() {
   *   let gl = this.context;
   *
   *   this._shaderProgram = gl.createProgram();
   *   ...
   *
   *   this._propertyHandle = reactiveUtils.watch(
   *     () => this.layer.opacity,
   *     () => this.requestRender()
   *   );
   * }
   */
  attach(): void;
  /**
   * Bind the designated rendering output surface and restore the correct viewport.
   *
   * This method can be used after the WebGL state has been altered by a call to
   * `gl.bindFramebuffer()` to restore the framebuffer that contains the final,
   * composited frame, i.e. the one that is guaranteed to be bound right before
   * control is handed over to [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render). Note that this *may or may not be the default framebuffer*;
   * [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/) can use various surfaces for frame compositing and there is no
   * guarantee that when [render()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#render) is called, the bound framebuffer is
   * the default one.
   *
   * Together with the framebuffer, also a matching full-size viewport is restored.
   *
   * @example
   * render() {
   *   let gl = this.context;
   *
   *   ...
   *
   *   // Bind a temporary offscreen surface
   *   gl.bindFramebuffer(gl.FRAMEBUFFER, this.myOffscreenSurface);
   *
   *   ...
   *
   *   // Render to the offscreen surface
   *
   *   ...
   *
   *   // Bind the original render surface so that the image stored
   *   // into the temporary one can be blitted/composited with the
   *   // actual frame data
   *   this.bindRenderTarget();
   *
   *   ...
   *
   *   // Your own frame composition logic
   *
   *   ...
   * }
   */
  bindRenderTarget(): void;
  /**
   * Method called after the layer is removed and the [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) is about to be removed.
   * Typically, this method is implemented to free resources like watchers and destroy WebGL objects such as shader programs.
   *
   * @see [attach()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#attach)
   * @example
   * // Remove the watchers and destroy the shader program created in attach()
   * detach() {
   *   this._propertyHandle.remove();
   *   this._propertyHandle = null;
   *
   *   const gl = this.context;
   *
   *   gl.deleteProgram(this._shaderProgram);
   *   this._shaderProgram = null;
   * }
   */
  detach(): void;
  /**
   * Get the designated rendering output surface and corresponding viewport configuration.
   *
   * The returned object is the same render target that is restored by a call to [bindRenderTarget()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#bindRenderTarget).
   */
  getRenderTarget(): RenderTarget;
  /**
   * Method to implement that is responsible for providing objects hit at the specified screen coordinates.
   * This method is called internally by the [MapView](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/) each time
   * its [MapView.hitTest()](https://developers.arcgis.com/javascript/latest/references/core/views/MapView/#hitTest) method is called.
   *
   * @param mapPoint - The point in map units.
   * @param screenPoint - The point in screen coordinates.
   * @returns A Promise that resolves to an array of graphics.
   */
  hitTest(mapPoint: Point, screenPoint: ScreenPoint): Promise<ViewHit[] | null | undefined>;
  /**
   * The method to implement that is responsible of drawing the content of the layer.
   * This method is called every time the MapView's state changes, or if [requestRender()](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#requestRender) has been called.
   *
   * @param renderParameters
   * @example
   * // Example of a render implementation that draws using a custom shader program
   * render(renderParameters) {
   *   const gl = this.context;
   *   gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
   *   gl.vertexAttribPointer(0, 2, gl.SHORT, false, 10, 0);
   *   gl.vertexAttribPointer(1, 3, gl.SHORT, true, 10, 4);
   *   gl.bindBuffer(gl.ARRAY_BUFFER, null);
   *   gl.enableVertexAttribArray(0);
   *   gl.enableVertexAttribArray(1);
   *   ...
   *   // Update uniforms as needed by calling gl.uniform...
   *   ...
   *   gl.useProgram(this._shaderProgram);
   *   gl.drawArrays(gl.TRIANGLES, 0, this._vertexCount);
   *   gl.disableVertexAttribArray(0);
   *   gl.disableVertexAttribArray(1);
   *   gl.useProgram(null);
   * }
   */
  abstract render(renderParameters: RenderParameters): void;
  /**
   * The [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/) can call this method to ask the MapView to schedule a new rendering frame.
   *
   * @example
   * // Call requestRender whenever the layer opacity has changed
   * attach() {
   *   this._propertyHandle = reactiveUtils.watch(
   *     () => this.layer.opacity,
   *     () => this.requestRender()
   *   );
   * }
   */
  requestRender(): void;
  /**
   * Tessellate an [Extent](https://developers.arcgis.com/javascript/latest/references/core/geometry/Extent/) into a rectangle.
   *
   * @param extent - The input geometry.
   * @returns A promise to a [TessellatedMesh](https://developers.arcgis.com/javascript/latest/references/core/views/2d/types/#TessellatedMesh). The output mesh is composed of two triangles.
   * @since 4.14
   * @example
   * this.tessellateExtent(g.geometry).then(function (mesh) {
   *    // do something with mesh
   *  });
   */
  tessellateExtent(extent: Extent): Promise<TessellatedMesh>;
  /**
   * Tessellate a [Multipoint](https://developers.arcgis.com/javascript/latest/references/core/geometry/Multipoint/) into quads (markers).
   *
   * @param multipoint - The input geometry. These are the geographic points where each marker will me anchored.
   * @param footprint - The rectangle that describes the geometry of each marker. Coordinates x and y
   * can be thought as being in screen-space, relative to the screen-space projection of the geographic point.
   * @returns A promise to a [TessellatedMesh](https://developers.arcgis.com/javascript/latest/references/core/views/2d/types/#TessellatedMesh). Each marker is represented by two triangles.
   * @since 4.14
   * @example
   * this.tessellateMultipoint(g.geometry, {x: 0, : -12, width: 34, height: 10}).then(function (mesh) {
   *    // do something with mesh
   *  });
   */
  tessellateMultipoint(multipoint: Multipoint, footprint: Rect): Promise<TessellatedMesh>;
  /**
   * Tessellate a [Point](https://developers.arcgis.com/javascript/latest/references/core/geometry/Point/) into a quad (marker).
   *
   * @param point - The input geometry. This is the geographic point where the marker will me anchored.
   * @param footprint - The rectangle that describes the geometry of the marker.
   * Coordinates `x` and `y` are the
   * position of the upper-left corner of the marker, and can be thought as being in screen-space, relative to the screen-space
   * projection of the geographic point; `width` and `height` are in pixels. See [Rect](https://developers.arcgis.com/javascript/latest/references/core/views/2d/layers/BaseLayerViewGL2D/#Rect)
   * for a visual explanation of marker geometry.
   * @returns A promise to a [TessellatedMesh](https://developers.arcgis.com/javascript/latest/references/core/views/2d/types/#TessellatedMesh). The output mesh is composed of two triangles.
   * @since 4.14
   * @example
   * this.tessellatePoint(g.geometry, {x: 0, : -12, width: 34, height: 10}).then(function (mesh) {
   *    // do something with mesh
   *  });
   */
  tessellatePoint(point: Point, footprint: Rect): Promise<TessellatedMesh>;
  /**
   * Tessellate a [Polygon](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polygon/) into triangles.
   *
   * @param polygon - The input geometry. *The geometry must be simple*;
   * if the input geometry is not simple, you must first create a simplified version of it using
   * [simplifyOperator](https://developers.arcgis.com/javascript/latest/references/core/geometry/operators/simplifyOperator/), and pass the simplified geometry to `tessellatePolygon`.
   * @returns A promise to a [TessellatedMesh](https://developers.arcgis.com/javascript/latest/references/core/views/2d/types/#TessellatedMesh).
   * @since 4.14
   * @example
   * this.tessellatePolygon(g.geometry).then(function (mesh) {
   *    // do something with mesh
   *  });
   */
  tessellatePolygon(polygon: Polygon): Promise<TessellatedMesh>;
  /**
   * Tessellate a [Polyline](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polyline/) into triangles.
   *
   * @param polyline - The input geometry. *The geometry must be simple*;
   * if the input geometry is not simple, you must first create a simplified version of it using
   * [simplifyOperator](https://developers.arcgis.com/javascript/latest/references/core/geometry/operators/simplifyOperator/), and pass the simplified geometry to `tessellatePolyline`.
   * @param width - The width of the line; this will be used to scale xOffset and yOffset.
   * @returns A promise to a [TessellatedMesh](https://developers.arcgis.com/javascript/latest/references/core/views/2d/types/#TessellatedMesh).
   * @since 4.14
   * @example
   * this.tessellatePolyline(g.geometry, 10).then(function (mesh) {
   *    // do something with mesh
   *  });
   */
  tessellatePolyline(polyline: Polyline, width: number): Promise<TessellatedMesh>;
  /**
   * Method to implement, which notifies of tiles being added or removed for the current view state.
   * This function can be implemented to start and stop fetching new data, or allocate and dispose resources.
   *
   * @param added - The tile objects added for the current view viewport.
   * @param removed - The tile objects removed from the view viewport.
   */
  tilesChanged(added: Tile[], removed: Tile[]): void;
}