/**
 * Various utilities and convenience functions for executing code at various phases of browser frames.
 * The scheduling module allows you to register tasks that are executed in every animation frame. This
 * can be used to synchronize updates with ongoing animations of the view, or to animate the view
 * manually by adjusting the extent or camera in every frame.
 *
 * ```js
 * // Animate the scene view camera heading in every frame
 * let handle = scheduling.addFrameTask({
 *   update: function() {
 *     let camera = view.camera.clone();
 *     camera.heading += 0.2;
 *     view.camera = camera;
 *   }
 * });
 *
 * // Remove frame task as soon as the user starts navigating in the view
 * reactiveUtils.whenOnce(() => view.navigating, () => handle.remove());
 * ```
 *
 * @since 4.7
 */
import type { ResourceHandle } from "./Handles.js";

/** An object with timing information. */
export interface PhaseEvent {
  /** The absolute time at the start of the current animation frame. */
  time: number;
  /** The elapsed time since the last animation frame. */
  deltaTime: number;
  /**
   * The amount of time spent within the current animation frame.
   *                                       This can be used for budgeting (e.g. some tasks may already have run).
   */
  elapsedFrameTime: number;
}

/**
 * A set of [callbacks](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-scheduling.html#PhaseCallback) that will be called at
 * specific phases of the animation frame.
 */
export interface PhaseCallbacks {
  /** A callback called before rendering. */
  prepare?: PhaseCallback;
  /** A callback to execute rendering logic. */
  render?: PhaseCallback;
  /** A callback to execute state update logic. */
  update?: PhaseCallback;
}

/**
 * A function called at a specific phase of the animation frame.
 *
 * @param event - An object with timing information.
 */
export type PhaseCallback = (event: PhaseEvent) => void;

/** An object to remove or pause a frame task registered with [addFrameTask()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-scheduling.html#addFrameTask). */
export interface FrameTaskHandle {
  /** Removes the frame task. */
  remove(): void;
  /** Pause the execution the frame task at every frame. */
  pause(): void;
  /** Resumes the execution the frame task. */
  resume(): void;
}

/**
 * Schedules the execution of a `callback` function at the next web browser tick.
 * Unlike [addFrameTask()](https://developers.arcgis.com/javascript/latest/api-reference/esri-core-scheduling.html#addFrameTask), a scheduled `callback` will only run
 * once. Scheduling a task for the next execution tick can be useful when you
 * want to throttle/accumulate functionality over a single javascript execution
 * context.
 *
 * @param callback - The function to call at the next tick.
 * @returns Returns an scheduling handler with a `remove()` method that
 * can be called to prevent the callback to be called at the next tick.
 *
 * Property |   Type   | Description
 * ---------|----------|----------------
 *  remove  | Function | When called, removes the callback from the callbacks queue.
 * @example
 * // Use scheduling.schedule to log an error message at most once per tick
 * let logErrorHandle;
 *
 * function logError(error) {
 *   if (!logErrorHandle) {
 *     logErrorHandle = scheduling.schedule(function() {
 *       console.error(error);
 *       logErrorHandle = null;
 *     });
 *   }
 * });
 */
export function schedule(callback: () => void): ResourceHandle;

/**
 * Registers a frame task. An animation frame is composed of different phases to let various
 * actors execute code before, after, or during the rendering of [MapView](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html)
 * or [SceneView](https://developers.arcgis.com/javascript/latest/api-reference/esri-views-SceneView.html).
 *
 * @param phases - The callbacks for each phase of the frame.
 * @returns A handle to remove, pause, or resume the frame task.
 * @example
 * // Animate the scene view camera heading in every frame
 * let handle = scheduling.addFrameTask({
 *   update: function() {
 *     let camera = view.camera.clone();
 *     camera.heading += 0.2;
 *     view.camera = camera;
 *   }
 * });
 *
 * // Remove frame task as soon as the user starts navigating in the view
 * reactiveUtils.whenOnce(() => view.navigating, () => handle.remove());
 */
export function addFrameTask(phases: PhaseCallbacks): FrameTaskHandle;