import type Error from "../core/Error.js";
import type { AbortOptions } from "../core/promiseUtils.js";

/**
 * An object with the following properties that describe the request.
 *
 * @since 5.0
 */
export interface RequestOptions extends AbortOptions {
  /**
   * Indicates if and how requests to ArcGIS Services are authenticated.
   * Only applicable when [esriConfig.request.useIdentity = true](https://developers.arcgis.com/javascript/latest/references/core/config/#Config-request).
   * | Known Value | Description |
   * |-------------|-------------|
   * | auto |  The user will be signed in when a secure resource is requested. |
   * | anonymous | An error will be returned when a secure resource is requested. |
   * | immediate | The user will be signed in before the resource is requested. |
   * | no-prompt | Checks for whether the user is already signed in. If so, no additional prompts display for sign-in. |
   *
   * > [!WARNING]
   * >
   * > **Note:** This is not supported when used in a custom worker that is not using the [workers](https://developers.arcgis.com/javascript/latest/references/core/core/workers/) framework.
   *
   * @default auto
   * @since 5.0
   */
  authMode?: "anonymous" | "auto" | "immediate" | "no-prompt";
  /**
   * If uploading a file, specify the
   * form data or element used to submit the file here. If specified, the parameters of the
   * `query` will be added to the URL.
   *
   * @since 5.0
   */
  body?: FormData | HTMLFormElement | string | null;
  /**
   * If `true`, the browser will send a request to the server instead of using the
   *   browser's local cache. If `false`, the browser's default cache handling will be used.
   *
   * @default false
   * @since 5.0
   */
  cacheBust?: boolean;
  /**
   * Headers to use for the request. This is an
   * object whose property names are header names.
   *
   * @since 5.0
   */
  headers?: Record<string, string>;
  /**
   * Indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete, such as during page navigation or closing.
   * See also [`keepalive`](https://developer.mozilla.org/en-US/docs/Web/API/Request/keepalive) for more details.
   *
   * @default false
   * @since 5.0
   */
  keepAlive?: boolean;
  /**
   * Indicates if the request should be made using the
   * HTTP DELETE, HEAD, POST, or PUT method. By default, HTTP POST will be used for `auto` if the request size is longer than the `maxUrlLength` property set in [Config](https://developers.arcgis.com/javascript/latest/references/core/config/#Config-request).
   *
   * @default auto
   * @since 5.0
   */
  method?: "auto" | "delete" | "head" | "post" | "put";
  /**
   * Response format. For details on the specific types, see:
   * [json](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object),
   * [text](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String),
   * [array-buffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer),
   * [blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob),
   * [image](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement),
   * [native](https://developer.mozilla.org/en-US/docs/Web/API/Response),
   * [native-request-init](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit),
   * [document](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDocument), and
   * [xml](https://developer.mozilla.org/en-US/docs/Web/API/XMLDocument)
   *
   * @default json
   * @since 5.0
   */
  responseType?: ResponseType;
  /**
   * Indicates the amount of time in milliseconds
   * to wait for a response from the server. Set to `0` to wait for the response indefinitely.
   *
   * @default 62000
   * @since 5.0
   */
  timeout?: number;
  /**
   * Indicates the request should use the proxy. By default,
   * this is determined automatically based on the domain of the request URL.
   *
   * @default false
   * @since 5.0
   */
  useProxy?: boolean;
  /**
   * Indicates if cross-site `Access-Control` requests should use credentials.  It is also possible to push the domain to the config [trustedServers](https://developers.arcgis.com/javascript/latest/references/core/config/#Config-request) if an application requires credentials. For additional information on `withCredentials`, please refer to [this documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials).
   *
   * @default false
   * @since 5.0
   */
  withCredentials?: boolean;
  /**
   * Query parameters for the request.
   *
   * The query parameters will be added to the URL if a GET request is used, or if the `body` property is set. If the
   * `body` property is not set, the query parameters will be added to the request body when a DELETE, POST, or PUT
   * request is used.
   *
   * @since 5.0
   */
  query?: Record<string, any> | URLSearchParams | null;
}

/** @since 5.0 */
export type ResponseType = "array-buffer" | "blob" | "document" | "image" | "json" | "native" | "native-request-init" | "text" | "xml";

/**
 * Returns a promise that resolves to an object with the following specification.
 * If the request returns an [Error](https://developers.arcgis.com/javascript/latest/references/core/core/Error/), the error object will
 * include the details specified in [RequestErrorDetails](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestErrorDetails).
 *
 * @since 5.0
 * @example
 * // request GeoJson data from USGS remote server
 * let url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson";
 *
 * esriRequest(url, {
 *   responseType: "json"
 * }).then((response) => {
 *   // The requested data
 *   let geoJson = response.data;
 * });
 */
export interface RequestResponse<T> {
  /**
   * The requested data. This value should match the [RequestOptions](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestOptions) `responseType` with the data return type. Possible types are:
   * [json](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object),
   * [text](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String),
   * [array-buffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer),
   * [blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob),
   * [image](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement),
   * [native](https://developer.mozilla.org/en-US/docs/Web/API/Response),
   * [native-request-init](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit),
   * [document](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDocument), and
   * [xml](https://developer.mozilla.org/en-US/docs/Web/API/XMLDocument).
   *
   * @since 5.0
   */
  data: T;
  /**
   * Method for getting all headers sent from the server.
   *
   * @since 5.0
   */
  getAllHeaders?: GetAllHeaders;
  /**
   * Method for getting a header sent from the server.
   *
   * @since 5.0
   */
  getHeader?: GetHeader;
  /**
   * The status code of the http response.
   *
   * @since 5.0
   */
  httpStatus?: number;
  /**
   * The options specified by the user in the data request. See [RequestOptions](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestOptions) for available properties.
   *
   * @since 5.0
   */
  requestOptions?: RequestOptions;
  /**
   * Indicates if the request required https.
   *
   * @since 5.0
   */
  ssl?: boolean;
  /**
   * The URL used to request the data.
   *
   * @since 5.0
   */
  url?: string;
}

/**
 * A function to retrieve all headers sent from the server.
 * CORS only allows a few response headers to be read by default, see: [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers).
 *
 * @returns An array of all headers returned from the server. Each header is an array containing the header name and its corresponding value.
 * @since 5.0
 * @example
 * esriRequest(url, options)
 *   .then((response) => {
 *     console.log("All request headers: ", response.getAllHeaders());
 *   });
 */
export type GetAllHeaders = () => [
    string,
    string
][] | null | undefined;

/**
 * A function to retrieve headers sent from the server.
 * CORS only allows a few response headers to be read by default, see: [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers).
 *
 * @param headerName - The name of the header.
 * @returns The header value.
 * @since 5.0
 * @example
 * esriRequest(url, options)
 *   .then((response) => {
 *     // prints the content type of the request: 'application/json'
 *     console.log("header: ", response.getHeader('Content-Type'));
 *   });
 */
export type GetHeader = (headerName: string) => string | null | undefined;

/**
 * The specification of the [details object](https://developers.arcgis.com/javascript/latest/references/core/core/Error/#details) returned in an
 * [Error](https://developers.arcgis.com/javascript/latest/references/core/core/Error/) object.
 *
 * @since 5.0
 */
export interface RequestErrorDetails {
  /**
   * The URL of the request that returned an error message.
   *
   * @since 5.0
   */
  url: string;
  /**
   * The options used in the http request.
   *
   * @since 5.0
   */
  requestOptions: RequestOptions;
  /**
   * A function for retrieving all headers sent from the server.
   *
   * @since 5.0
   */
  getAllHeaders: GetAllHeaders;
  /**
   * A function to retrieve headers sent from the server.
   *
   * @since 5.0
   */
  getHeader: GetHeader;
  /**
   * The status code of the http response.
   *
   * @since 5.0
   */
  httpStatus: number;
  /**
   * The raw error object if the server returned a JSON error, or the response text otherwise.
   *
   * @since 5.0
   */
  raw: string | Object;
  /**
   * The error message subcode.
   *
   * @since 5.0
   */
  subCode: number;
  /**
   * The error message code.
   *
   * @since 5.0
   */
  messageCode: string;
  /**
   * Additional error message(s).
   *
   * @since 5.0
   */
  messages: string[];
  /**
   * Indicates if the request required https.
   *
   * @since 5.0
   */
  ssl: boolean;
}

/** @since 5.0 */
export interface RequestParameters {
  /**
   * The options specified by the user in the data request. See [RequestOptions](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestOptions) for available properties.
   *
   * @since 5.0
   */
  requestOptions: RequestOptions;
  /**
   * The request URL.
   *
   * @since 5.0
   */
  url: string;
}

/**
 * Specifies the object used for intercepting and modifying requests made via [esriRequest](https://developers.arcgis.com/javascript/latest/references/core/request/).
 *
 * @since 5.0
 * @example
 * const featureLayerUrl = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0";
 *
 * esriConfig.request.interceptors.push({
 *   // set the `urls` property to the URL of the FeatureLayer so that this
 *   // interceptor only applies to requests made to the FeatureLayer URL
 *   urls: featureLayerUrl,
 *   // use the BeforeInterceptorCallback to check if the query of the
 *   // FeatureLayer has a maxAllowableOffset property set.
 *   // if so, then set the maxAllowableOffset to 0
 *   before: function(params) {
 *     if (params.requestOptions.query.maxAllowableOffset) {
 *       params.requestOptions.query.maxAllowableOffset = 0;
 *     }
 *   },
 *   // use the AfterInterceptorCallback to check if `ssl` is set to 'true'
 *   // on the response to the request, if it's set to 'false', change
 *   // the value to 'true' before returning the response
 *   after: function(response) {
 *     if (!response.ssl) {
 *       response.ssl = true;
 *     }
 *   }
 * });
 * @example
 * const featureLayer = new FeatureLayer({
 *    url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/3"
 * });
 *
 * esriConfig.request.interceptors.push({
 *    urls: featureLayer.url,
 *    // set the error function and check if an error occurs, and if it's name is "AbortError"
 *    // if so, display a useful error about the layer, if not, ignore the error
 *    error: function(error) {
 *       if (error.name == "AbortError") {
 *          // we're only interested in aborted request errors
 *          console.error(`An error happened with layer ${featureLayer.title}`, error);
 *       }
 *       return;
 *    }
 * });
 */
export interface RequestInterceptor {
  /**
   * Makes changes to the response after the request is sent, but before it's returned to the caller.
   *
   * @since 5.0
   */
  after?: AfterInterceptorCallback;
  /**
   * Make changes to the request URL or options before the request is sent. A returned value will be used as the response data, which would prevent the request from being sent.
   *
   * @since 5.0
   */
  before?: BeforeInterceptorCallback;
  /**
   * When an error occurs during the request processing, this function is called with an [Error](https://developers.arcgis.com/javascript/latest/references/core/core/Error/) object giving the details about what happened.
   * For example, this could be used to log specific errors occurring with layers or services.
   *
   * @since 5.0
   */
  error?: ErrorInterceptorCallback;
  /**
   * Sets or adds headers into `requestOptions.headers`. See also: [RequestOptions](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestOptions).
   *
   * @since 5.0
   */
  headers?: Record<string, string>;
  /**
   * Sets or adds query parameters into `requestOptions.query`. See also: [RequestOptions](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestOptions).
   *
   * @since 5.0
   */
  query?: Object;
  /**
   * Hardcodes the [response](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestResponse). The request will not be sent. This is resolved as the response `data`.
   *
   * @since 5.0
   */
  responseData?: any;
  /**
   * Specifies the URL(s) to apply to the interceptors. If the value is type `String`, then it matches if the request URL starts with that string. If null or undefined, the interceptor will apply to all relevant requests.
   * If using a [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp), the [`g`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global) and [`y`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) flags should not be used.
   *
   * @since 5.0
   */
  urls?: RegExp | string | Array<RegExp | string>;
}

/**
 * Makes changes to the response after the request is sent, but before it's returned to the caller.
 *
 * @param response - The response object.
 * @since 5.0
 * @example
 * const featureLayerUrl = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0";
 *
 * esriConfig.request.interceptors.push({
 *   // set the `urls` property to the URL of the FeatureLayer so that this
 *   // interceptor only applies to requests made to the FeatureLayer URL
 *   urls: featureLayerUrl,
 *   // use the AfterInterceptorCallback to check if `ssl` is set to 'true'
 *   // on the response to the request, if it's set to 'false', change
 *   // the value to 'true' before returning the response
 *   after: function(response) {
 *     if (!response.ssl) {
 *       response.ssl = true;
 *     }
 *   }
 * });
 */
export type AfterInterceptorCallback = (response: RequestResponse<any>) => void;

/**
 * Makes changes to the request URL or options before the request is sent.
 * A returned value will be used as the response data, which would prevent the request from being sent.
 *
 * If `null` or `undefined` is returned, the request is sent with whatever changes were made to the parameters.
 * If an Error is returned, the request is rejected with an [esriError](https://developers.arcgis.com/javascript/latest/references/core/core/Error/).
 * If any other type is returned, the request is resolved with the returned value as the [response data](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestResponse) (request will not be sent).
 *
 * @param params - Parameters object that specifies the two properties that can be set.
 * @returns Returns: `null`, `undefined`, [Error](https://developers.arcgis.com/javascript/latest/references/core/core/Error/), [response data](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestResponse), or a Promise
 * that resolves to any one of these object types.
 * @since 5.0
 * @example
 * // modifying the query parameters
 * const featureLayerUrl = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0";
 *
 * esriConfig.request.interceptors.push({
 *   // set the `urls` property to the URL of the FeatureLayer so that this
 *   // interceptor only applies to requests made to the FeatureLayer URL
 *   urls: featureLayerUrl,
 *   // use the BeforeInterceptorCallback to check if the query of the
 *   // FeatureLayer has a maxAllowableOffset property set.
 *   // if so, then set the maxAllowableOffset to 0
 *   before: function(params) {
 *     if (params.requestOptions.query.maxAllowableOffset) {
 *       params.requestOptions.query.maxAllowableOffset = 0;
 *     }
 *   }
 * });
 * @example
 * // fetching the data in place of the requests
 * const wmsLayerUrl = "https://sampleserver6.arcgisonline.com/arcgis/services/911CallsHotspot/MapServer/WMSServer";
 *
 * esriConfig.request.interceptors.push({
 *    urls: wmsLayerUrl,
 *    before: function(params) {
 *      if (params.url === url && params.requestOptions.responseType === "xml") {
 *         if ("fetch" in window && "TextDecoder" in window) {
 *            // decode the response as ISO-8859-1 if it's not UTF-8 as expected
 *            return fetch(url + "?SERVICE=WMS&REQUEST=GetCapabilities")
 *              .then(function(response) {
 *                return response.arrayBuffer();
 *              })
 *              .then(function(buffer) {
 *                let textDecoder = new TextDecoder("ISO-8859-1"); // specified in the Capabilities XML declaration
 *                let xmlText = textDecoder.decode(buffer);
 *                let parser = new DOMParser();
 *                xml = parser.parseFromString(xmlText, "application/xml");
 *                return xml;
 *              });
 *          }
 *      }
 *    }
 * });
 */
export type BeforeInterceptorCallback = (params: RequestParameters) => any;

/**
 * The error function detailing the reason why the [request](https://developers.arcgis.com/javascript/latest/references/core/request/types/#RequestInterceptor) failed.
 *
 * @param error - The error object. See [Error](https://developers.arcgis.com/javascript/latest/references/core/core/Error/) for more information.
 * @since 5.0
 */
export type ErrorInterceptorCallback = (error: Error<unknown>) => void;