import type Layer from "../../layers/Layer.js";
import type Point from "../../geometry/Point.js";
import type LayerView from "../layers/LayerView.js";

export type PointerType = "mouse" | "touch" | "pen";

/**
 * Indicates which mouse button was clicked.
 * See [MouseEvent.button](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
 *
 * Value | Description
 * ------|------------
 * 0 | left click (or touch)
 * 1 | middle click
 * 2 | right click
 * 3 | back click
 * 4 | forward click
 * -1 | unknown button
 */
export type MouseButton = 0 | 1 | 2 | 3 | 4 | -1;

export interface ViewEvent<T extends Event = Event> {
  /**
   * A function that can be called to defer event propagation until
   * the passed in asynchronous function is completed. Calling defer will stall the entire event pipeline and should
   * be used with caution.
   */
  defer: EventDefer;
  /** A standard [DOM Event](https://developer.mozilla.org/en-US/docs/Web/API/Event). */
  native: T;
  /** Time stamp (in milliseconds) at which the event was created. */
  timestamp: number;
  /** Prevents the event bubbling up the event chain. */
  stopPropagation(): void;
}

/**
 * @param operation - An asynchronous operation.
 * @returns The result of calling the passed in operation.
 * @since 4.33
 * @example
 * view.on("click", (event) => {
 *   event.defer(async () => {
 *     const hitTestResult = await view.hitTest(event);
 *
 *     if (hitTestResult.results.length) {
 *       // stop the event from propagating
 *       event.stopPropagation();
 *       console.log("found hit, stopping propagation to prevent popup", hitTestResult.results[0]);
 *     }
 *   });
 * });
 */
export type EventDefer = <T>(operation: EventDeferredOperation<T>) => Promise<T>;

/**
 * Aynchronous function passed to the defer method of an event.
 *
 * @returns The event pipeline is deferred until the promise is resolved.
 * @since 4.33
 * @see {@link views/input/IViewEvents!EventDefer}
 */
export type EventDeferredOperation<T> = () => Promise<T>;

export type PointerId = number;

export interface ClickEvent extends ViewEvent<PointerEvent | MouseEvent> {
  /** Indicates which mouse button was clicked. */
  button: MouseButton;
  /**
   * Indicates the current mouse button state.
   *
   * Value | Description
   * ------|------------
   * 0 | left click (or touch)
   * 1 | middle click
   * 2 | right click
   */
  buttons: number;
  /**
   * The point location of the click on the view in the spatial
   * reference of the map.
   */
  mapPoint: Point;
  /** The event type. */
  type: "click";
  /** The horizontal screen coordinate of the click on the view. */
  x: number;
  /** The vertical screen coordinate of the click on the view. */
  y: number;
}

export interface DoubleClickEvent extends ViewEvent<PointerEvent | MouseEvent> {
  /** Indicates which mouse button was clicked. */
  button: MouseButton;
  /**
   * Indicates the current mouse button state.
   *
   * Value | Description
   * ------|------------
   * 0 | left click (or touch)
   * 1 | middle click
   * 2 | right click
   */
  buttons: number;
  /**
   * The point location of the click on the view in the spatial
   * reference of the map.
   */
  mapPoint: Point;
  /** The event type. */
  type: "double-click";
  /** The horizontal screen coordinate of the click on the view. */
  x: number;
  /** The vertical screen coordinate of the click on the view. */
  y: number;
}

export interface ImmediateClickEvent extends ViewEvent<PointerEvent | MouseEvent> {
  /**
   * Indicates which mouse button was clicked.
   * See [MouseEvent.button](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
   *
   * Value | Description
   * ------|------------
   * 0 | left click (or touch)
   * 1 | middle click
   * 2 | right click
   */
  button: MouseButton;
  /**
   * Indicates which buttons are pressed when the event is triggered.
   * See [MouseEvent.buttons](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons).
   */
  buttons: number;
  /**
   * The point location of the click on the view in the spatial
   * reference of the map.
   */
  mapPoint: Point;
  /** The event type. */
  type: "immediate-click";
  /** The horizontal screen coordinate of the click on the view. */
  x: number;
  /** The vertical screen coordinate of the click on the view. */
  y: number;
}

export interface ImmediateDoubleClickEvent extends ViewEvent<PointerEvent | MouseEvent> {
  /**
   * Indicates which mouse button was clicked.
   * See [MouseEvent.button](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
   *
   * Value | Description
   * ------|------------
   * 0 | left click (or touch)
   * 1 | middle click
   * 2 | right click
   */
  button: MouseButton;
  /**
   * Indicates which buttons are pressed when the event is triggered.
   * See [MouseEvent.buttons](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons).
   */
  buttons: number;
  /**
   * The point location of the click on the view in the spatial
   * reference of the map.
   */
  mapPoint: Point;
  /** The event type. */
  type: "immediate-double-click";
  /** The horizontal screen coordinate of the click on the view. */
  x: number;
  /** The vertical screen coordinate of the click on the view. */
  y: number;
}

export interface HoldEvent extends ViewEvent<PointerEvent | MouseEvent> {
  /**
   * Indicates which mouse button was held down.
   * See [MouseEvent.button](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
   *
   * Value | Description
   * ------|------------
   * 0 | left mouse button (or touch)
   * 1 | middle mouse button
   * 2 | right mouse button
   */
  button: MouseButton;
  /**
   * Indicates which mouse buttons are pressed when the event is triggered.
   * See [MouseEvent.buttons](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons).
   */
  buttons: number;
  /**
   * The point location of the click on the view in the spatial
   * reference of the map.
   */
  mapPoint: Point;
  /** The event type. */
  type: "hold";
  /** The horizontal screen coordinate of the hold on the view. */
  x: number;
  /** The vertical screen coordinate of the hold on the view. */
  y: number;
}

export interface DragEvent extends ViewEvent<PointerEvent | MouseEvent> {
  /**
   * Indicates the state of the drag.
   * The two values `added` and `removed` indicate a change in the number of pointers involved.
   */
  action: "start" | "update" | "added" | "removed" | "end";
  /** Amount of rotation (in degrees) since the last event of type `start`. */
  angle: number;
  /**
   * Indicates which mouse button was clicked at the start of the drag.
   * See [MouseEvent.button](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
   *
   * Value | Description
   * ------|------------
   * 0 | left mouse button (or touch)
   * 1 | middle mouse button
   * 2 | right mouse button
   */
  button: MouseButton;
  /**
   * Indicates which mouse buttons are pressed when the event is triggered.
   * See [MouseEvent.buttons](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons).
   */
  buttons: number;
  /** Screen coordinates of the start of the drag. */
  origin: DragEventOrigin;
  /** The radius of a sphere around the multiple pointers involved in this drag. Or 0 while only a single pointer is used. */
  radius: number;
  /** The event type. */
  type: "drag";
  /** The horizontal screen coordinate of the pointer on the view. */
  x: number;
  /** The vertical screen coordinate of the pointer on the view. */
  y: number;
}

export interface DragEventOrigin {
  /** The horizontal screen coordinate of the pointer on the view. */
  x: number;
  /** The vertical screen coordinate of the pointer on the view. */
  y: number;
}

export interface KeyDownEvent extends ViewEvent<KeyboardEvent> {
  /**
   * The key value that was pressed, according to the
   * [MDN full list of key values](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values).
   */
  key: string;
  /**
   * Indicates whether this is the first event emitted
   * due to the key press, or a repeat.
   */
  repeat: boolean;
  /** The event type. */
  type: "key-down";
}

export interface KeyUpEvent extends ViewEvent<KeyboardEvent> {
  /**
   * The key value that was released, according to the
   * [MDN full list of key values](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values).
   */
  key: string;
  /** The event type. */
  type: "key-up";
}

export interface PointerEventCommon extends ViewEvent<PointerEvent> {
  /** Indicates which mouse button was clicked. */
  button: MouseButton;
  /**
   * Indicates which mouse buttons are pressed when the event is triggered.
   * See [MouseEvent.buttons](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons).
   */
  buttons: number;
  /**
   * Uniquely identifies a pointer between multiple down, move, and up events.
   * Ids might get reused after a pointer-up event.
   */
  pointerId: PointerId;
  /** Indicates the pointer type. */
  pointerType: PointerType;
  /** The horizontal screen coordinate of the pointer on the view. */
  x: number;
  /** The vertical screen coordinate of the pointer on the view. */
  y: number;
}

export interface PointerDownEvent extends PointerEventCommon {
  /** The event type. */
  type: "pointer-down";
}

export interface PointerMoveEvent extends PointerEventCommon {
  /** The event type. */
  type: "pointer-move";
}

export interface PointerUpEvent extends PointerEventCommon {
  /** The event type. */
  type: "pointer-up";
}

export interface PointerEnterEvent extends PointerEventCommon {
  /** The event type. */
  type: "pointer-enter";
}

export interface PointerLeaveEvent extends PointerEventCommon {
  /** The event type. */
  type: "pointer-leave";
}

export interface ViewMouseWheelEvent extends ViewEvent<WheelEvent> {
  /** Number representing the vertical scroll amount. */
  deltaY: number;
  /** The event type. */
  type: "mouse-wheel";
  /** The horizontal screen coordinate of the click on the view. */
  x: number;
  /** The vertical screen coordinate of the click on the view. */
  y: number;
}

export interface ViewFocusEvent extends ViewEvent<FocusEvent> {}

export interface ViewBlurEvent extends ViewEvent<FocusEvent> {}

export interface ViewResizeEvent {
  /** The new measured view height in pixels */
  height: number;
  /** The previous view height in pixels */
  oldHeight: number;
  /** The previous view width in pixels */
  oldWidth: number;
  /** The new measured view width in pixels */
  width: number;
}

export interface LayerViewCreateEvent {
  /** The layer in the map for which the `layerView` was created. */
  layer: Layer;
  /**
   * The LayerView rendered
   *                                                in the view representing the layer in `layer`.
   */
  layerView: LayerView;
}

export interface LayerViewCreateErrorEvent {
  /** An error object describing why the layer view could not be created. */
  error: Error;
  /**
   * The layer in the map for which the view emitting this event
   *   failed to create a layer view.
   */
  layer: Layer;
}

export interface LayerViewDestroyEvent {
  /** The layer in the map for which the `layerView` was destroyed. */
  layer: Layer;
  /** The LayerView that was destroyed in the view. */
  layerView: LayerView;
}

export interface ViewBaseEventTypes {
  /**
   * Fires after each layer in the map has a corresponding [LayerView](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-layers-LayerView.html) created
   * and rendered in the view.
   *
   * @see [View#whenLayerView()](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#whenLayerView)
   * @example
   * // This function fires each time a layer view is created for a layer in
   * // the map of the view.
   * view.on("layerview-create", function(event) {
   *   // The event contains the layer and its layer view that has just been
   *   // created. Here we check for the creation of a layer view for a layer with
   *   // a specific id, and log the layer view
   *   if (event.layer.id === "satellite") {
   *     // The LayerView for the desired layer
   *     console.log(event.layerView);
   *   }
   * });
   */
  "layerview-create": LayerViewCreateEvent;
  /**
   * Fires when an error occurs during the creation of a [LayerView](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-layers-LayerView.html)
   * after a layer has been added to the map.
   *
   * @see [View#whenLayerView()](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#whenLayerView)
   * @example
   * // This function fires each time an error occurs during the creation of a layerview
   * view.on("layerview-create-error", function(event) {
   *   console.error("LayerView failed to create for layer with the id: ", event.layer.id);
   * });
   */
  "layerview-create-error": LayerViewCreateErrorEvent;
  /**
   * Fires after a [LayerView](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-layers-LayerView.html) is destroyed and is
   * no longer rendered in the view. This happens for example when a layer is
   * removed from the map of the view.
   */
  "layerview-destroy": LayerViewDestroyEvent;
  /**
   * Fires when the view's size changes.
   *
   * @see [MapView#size](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#size)
   * @see [MapView#height](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#height)
   * @see [MapView#width](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#width)
   */
  resize: ViewResizeEvent;
}

export interface ViewEventTypes extends ViewBaseEventTypes {
  /**
   * Fires when browser focus is moved away from the view.
   *
   * @since 4.7
   */
  blur: ViewBlurEvent;
  /**
   * Fires after a user clicks on the view. This event emits slightly slower than an
   * [View#@immediate-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-immediate-click) event to make sure that a [View#@double-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-double-click)
   * event isn't triggered instead. The [View#@immediate-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-immediate-click) event can be used
   * for responding to a click event without delay.
   *
   * @see [Sample: Access features with click events](https://developers.arcgis.com/javascript/latest/sample-code/view-hittest/)
   * @example
   * view.on("click", function(event){
   *   // event is the event object returned after the event fires
   *
   *   // Prints the map coordinates of the clicked location
   *   console.log(event.mapPoint);
   * });
   * @example
   * // Set up a click event handler and retrieve the screen point
   * view.on("click", function(event) {
   *  // the hitTest() checks to see if any graphics in the view
   *  // intersect the given screen x, y coordinates
   *  view.hitTest(event)
   *   .then(getGraphics);
   * });
   * @example
   * view.on("click", function(event) {
   *  // you must overwrite default click-for-popup
   *  // behavior to display your own popup
   *  view.popupEnabled = false;
   *
   *  // Get the coordinates of the click on the view
   *  let lat = Math.round(event.mapPoint.latitude * 1000) / 1000;
   *  let lon = Math.round(event.mapPoint.longitude * 1000) / 1000;
   *
   *  view.popup.open({
   *    // Set the popup's title to the coordinates of the location
   *    title: "Reverse geocode: [" + lon + ", " + lat + "]",
   *    location: event.mapPoint // Set the location of the popup to the clicked location
   *    content: "This is a point of interest"  // content displayed in the popup
   *  });
   * });
   */
  click: ClickEvent;
  /**
   * Fires after double-clicking on the view.
   *
   * @example
   * view.on("double-click", function(event) {
   *   // The event object contains the mapPoint and the screen coordinates of the location
   *   // that was clicked.
   *   console.log("screen point", event.x, event.y);
   *   console.log("map point", event.mapPoint);
   * });
   */
  "double-click": DoubleClickEvent;
  /**
   * Fires during a pointer drag on the view.
   *
   * @example
   * view.on("drag", function(event){
   *  // Print out the current state of the
   *  // drag event.
   *  console.log("drag state", event.action);
   * });
   */
  drag: DragEvent;
  /**
   * Fires when browser focus is on the view.
   *
   * @since 4.7
   */
  focus: ViewFocusEvent;
  /**
   * Fires after holding either a mouse button or a single finger on the view for a short amount of time.
   *
   * @example
   * view.on("hold", function(event) {
   *   // The event object contains the mapPoint and the screen coordinates of the location
   *   // that was clicked.
   *   console.log("hold at screen point", event.x, event.y);
   *   console.log("hold at map point", event.mapPoint);
   * });
   */
  hold: HoldEvent;
  /**
   * Fires right after a user clicks on the view. In contrast to the [View#@click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-click) event,
   * the `immediate-click` event is emitted as soon as the user clicks on
   * the view, and is not inhibited by a [View#@double-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-double-click) event. This event
   * is useful for interactive experiences that require feedback without delay.
   *
   * @since 4.7
   * @example
   * // Set up an immediate-click event handler and retrieve the screen point
   * view.on("immediate-click", function(event) {
   *  // the hitTest() checks to see if any graphics in the view
   *  // intersect the given screen x, y coordinates
   *  view.hitTest(event)
   *   .then(getGraphics);
   * });
   */
  "immediate-click": ImmediateClickEvent;
  /**
   * Is emitted after two consecutive [View#@immediate-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-immediate-click) events.
   * In contrast to [View#@double-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-double-click), an `immediate-double-click` cannot
   * be prevented by use of `stopPropagation` on the [View#@immediate-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-immediate-click)
   * event and can therefore be used to react to double-clicking independently of usage of the
   * [View#@immediate-click](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-View.html#event-immediate-click) event.
   *
   * @since 4.15
   */
  "immediate-double-click": ImmediateDoubleClickEvent;
  /**
   * Fires after a keyboard key is pressed.
   *
   * @example
   * // Zoom in when user clicks on "a" button
   * // Zoom out when user clicks on "s" button
   * view.on("key-down", function(event){
   *  console.log("key-down", event);
   *
   *  if (event.key === "a"){
   *    let zm = view.zoom + 1;
   *
   *    view.goTo({
   *      target: view.center,
   *      zoom: zm
   *    });
   *  }
   *  else if(event.key == "s"){
   *    let zm = view.zoom - 1;
   *
   *    view.goTo({
   *      target: view.center,
   *      zoom: zm
   *    });
   *  }
   * });
   */
  "key-down": KeyDownEvent;
  /** Fires after a keyboard key is released. */
  "key-up": KeyUpEvent;
  /**
   * Fires when a wheel button of a pointing device (typically a mouse) is scrolled on the view.
   *
   * @example
   * view.on("mouse-wheel", function(event){
   *  // deltaY value is positive when wheel is scrolled up
   *  // and it is negative when wheel is scrolled down.
   *  console.log(event.deltaY);
   * });
   */
  "mouse-wheel": ViewMouseWheelEvent;
  /** Fires after a mouse button is pressed, or a finger touches the display. */
  "pointer-down": PointerDownEvent;
  /** Fires after a mouse cursor enters the view, or a display touch begins. */
  "pointer-enter": PointerEnterEvent;
  /** Fires after a mouse cursor leaves the view, or a display touch ends. */
  "pointer-leave": PointerLeaveEvent;
  /**
   * Fires after the mouse or a finger on the display moves.
   *
   * @example
   * // Fires `pointer-move` event when user clicks on "Shift"
   * // key and moves the pointer on the view.
   * view.on('pointer-move', ["Shift"], function(event){
   *   let point = view.toMap({x: event.x, y: event.y});
   *   bufferPoint(point);
   * });
   */
  "pointer-move": PointerMoveEvent;
  /** Fires after a mouse button is released, or a display touch ends. */
  "pointer-up": PointerUpEvent;
}