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

/** An object specifying additional options when calling [invoke()](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#invoke) method. */
export interface InvokeOptions extends AbortOptions {
  /**
   * An array of Transferable
   * objects. Each transferable object in the array should have a corresponding entry in the data object.
   * See [Using transferables](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#using-transferables) section for more information.
   */
  transferList?: Transferable[];
}

/**
 * This class is used to execute remote methods located on the module loaded into a separate thread via
 * the [workers framework](https://developers.arcgis.com/javascript/latest/references/core/core/workers/).
 *
 * The [open()](https://developers.arcgis.com/javascript/latest/references/core/core/workers/#open) method loads the
 * script and returns a Promise
 * to the main thread. Once resolved, you gain access to an instance of Connection, which allows you to call
 * methods of the module via [invoke()](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#invoke) or [broadcast()](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#broadcast) methods.
 *
 * <span id="basic-invocation"></span>
 * ## Invoking a remote method
 *
 * To delegate work to a worker, you need to create a module that exposes functions
 * or a class that will be instantiated by the framework.
 * Each remote method can accept one parameter only. The parameter value can be any
 * [primitive type](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Primitive_values),
 * or JavaScript object handled by the
 * [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm),
 * or a [JavaScript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
 * that resolves with one of them.
 *
 * For example, let's create a simple worker that calculates the sum of an array of numbers.
 *
 * ```js
 * // Module loaded in worker : calculator.js
 * export function getSum(numbers) {
 *   let sum = 0;
 *
 *   for (let i = 0; i < numbers.length; i++) {
 *     sum += numbers[i];
 *   }
 *
 *   return sum;
 * }
 * ```
 *
 * Now we load the calculator module into a worker and invoke its `getSum` function.
 *
 * ```js
 * // Module loaded in main thread
 * export function getSumAsync(numbers) {
 *   let connection = null;
 *
 *   return workers.open("./calculator.js")
 *     .then(function(conn) {
 *       // Keep the connection reference to later close it.
 *       connection = conn;
 *
 *       return connection.invoke("getSum", numbers);
 *     })
 *     .then(function(result) {
 *       // close the connection
 *       connection.close();
 *       connection = null;
 *
 *       return result;
 *     });
 * }
 *
 * // Invoke our method.
 * getSumAsync([0, 2, 4, 6, 8])
 *   .then(function(result) {
 *     console.log("Result:", result);
 *   });
 * ```
 *
 * <span id="multiple-parameters"></span>
 * ## Passing multiple parameters
 *
 * As mentioned above, the remote methods loaded with worker can only accept one parameter.
 * However, to pass multiple parameters to the remote method, use a JavaScript object with multiple key-value pairs.
 *
 * The example below shows a function that multiplies an array of numbers by a factor.
 *
 * ```js
 * // Module loaded in worker : calculator.js
 * export function mapMultiply(params) {
 *   // params has numbers and factor values.
 *   let numbers = params.numbers;
 *   let factor = params.factor;
 *
 *   for (let i = 0; i < numbers.length; i++) {
 *     numbers[i] *= factor;
 *   }
 *
 *   return numbers;
 * }
 * ```
 *
 * To invoke this function, an object with two properties is passed into the function.
 *
 * ```js
 * // Module loaded in main thread
 * export function mapMultiplyAsync(numbers, factor) {
 *   let connection = null;
 *
 *   return workers.open("./calculator.js")
 *     .then(function(conn) {
 *       // Keep the connection reference to later close it.
 *       connection = conn;
 *
 *        // invoke mapMultiply and pass in object with two key-value pairs.
 *       return connection.invoke("mapMultiply", {
 *         numbers: numbers,
 *         factor: factor
 *       });
 *     })
 *     .then(function(result) {
 *       // close the connection after we are done.
 *       connection.close();
 *       connection = null;
 *
 *       return result;
 *     });
 * }
 *
 * // Invoke the method.
 * mapMultiplyAsync([0, 2, 4, 6, 8], 2)
 *   .then(function(result) {
 *     console.log("Result:", result);
 *   });
 * ```
 *
 * <span id="using-transferables"></span>
 * ## Using transferables
 *
 * `Transferable` objects can be used
 * to transfer data between main and worker thread to avoid copying the data. With this approach,
 * the ownership of the object is transferred to the other context. Use this technique to transfer large
 * chuck of memory with [ArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer),
 * a [MessagePort](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort),
 * or an [ImageBitmap](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap).
 *
 * > [!CAUTION]
 * >
 * > **Note**
 * >
 * > The `Transferable` interface technically no longer exists. The functionality of `Transferable` objects still exists,
 * > however, but is implemented at a more fundamental level
 * > (technically speaking, using the `[Transferable]` [WebIDL](https://developer.mozilla.org/en-US/docs/Glossary/WebIDL)
 * > extended attribute). See also [Transferable objects](https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects).
 *
 * To transfer an object to the worker, use the `transferList` parameter of the [invoke()](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#invoke) method.
 * For the remote method to return or resolve with a transferable object, the result returned must have
 * two properties: `result` and `transferList`, `result` being an object and `transferList` the array
 * of transferable objects. Note that every transferable object in `transferList` should be in the object structure
 * of `result`.
 *
 * Let's revisit the previous example to explore transferable objects.
 *
 * ```js
 * // Module loaded in worker : calculator.js
 * export function mapMultiplyFloat64(params) {
 *   // the numbers parameter is an ArrayBuffer
 *   // we create a typed array from it.
 *   let numbers = new Float64Array(params.numbers);
 *   let factor = params.factor;
 *
 *   for (let i = 0; i < numbers.length; i++) {
 *     numbers[i] *= factor;
 *   }
 *
 *   // Transfer back the buffer
 *   return {
 *     result: numbers.buffer,
 *     transferList: [numbers.buffer]
 *   };
 * }
 * ```
 *
 * On the main thread, we transfer the input typed array's buffer.
 * Then we recreate a [Float64Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) back from the returned buffer.
 *
 * ```js
 * // Module loaded in main thread
 * export function mapMultiplyFloat64Async(numbers, factor) {
 *   let connection = null;
 *
 *   return workers.open("./calculator.js")
 *     .then(function(conn) {
 *       // Keep the connection reference to later close it.
 *       connection = conn;
 *
 *       return connection.invoke("mapMultiplyFloat64", {
 *         numbers: numbers,
 *         factor: factor
 *       });
 *     })
 *     .then(function(result) {
 *       // close the connection after we are done.
 *       connection.close();
 *       connection = null;
 *
 *       return result;
 *     });
 * }
 *
 * // Invoke our method.
 * let floats = new Float64Array(5);
 * floats[0] = 0;
 * floats[1] = 2;
 * floats[2] = 4;
 * floats[3] = 6;
 * floats[4] = 8;
 * mapMultiplyFloat64Async(floats, 2)
 *   .then(function(result) {
 *     let resultFloats = new Float64Array(result);
 *     console.log("Result:", resultFloats);
 *   });
 * ```
 *
 * @since 4.2
 */
export default abstract class Connection {
  /**
   * A convenient method that invokes a method on each worker.
   *
   * @param methodName - The name of the remote method to invoke on all workers.
   * @param data - The unique parameter passed as argument of the remote method.
   * @param options - An object specifying additional options. See the
   *   object specification table below for the required properties of this object.
   * @returns An array of promises that resolves with the result of the execution on each worker.
   */
  broadcast<T>(methodName: string, data?: any, options?: AbortOptions): Promise<T>[];
  /**
   * Closes the existing connection instance to workers.
   * Notifies all workers to destroy the connection instance and dispose the remote module.
   */
  close(): void;
  /**
   * Invokes a [method](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#basic-invocation) on the remote module loaded with the worker.
   *
   * @param methodName - The name of the method to be invoked in the script.
   * @param data - The unique parameter passed as argument of the remote method. See [Passing multiple parameters](https://developers.arcgis.com/javascript/latest/references/core/core/workers/Connection/#multiple-parameters)
   * section to pass more than one parameter to the remote method.
   * @param options - An object specifying additional options.
   * @returns A Promise that resolves to the result of the method of the worker.
   * @example
   * const controller = new AbortController();
   * const signal = controller.signal;
   *
   * // invoke a function on a worker thread
   * connection.invoke("myLongRunningRemoteFunction", { someParameter: 10 }, {
   *   signal
   * })
   * .then((result) => {
   *   console.log(result);
   * })
   * .catch((error) => {
   *   console.error(error);
   * });
   *
   * // if the call it takes more than 10 secs, abort:
   * setTimeout(() => {
   *  controller.abort();
   * }, 10000);
   */
  invoke<T = any>(methodName: string, data?: any, options?: InvokeOptions): Promise<T>;
}