/**
 * Shared ApplySet utils for Components.
 *
 * @experimental
 * @internal
 */
import type Graphic from "../../Graphic.js";
import type FeatureLayer from "../../layers/FeatureLayer.js";
import type SubtypeGroupLayer from "../../layers/SubtypeGroupLayer.js";
import type FeatureService from "../../rest/featureService/FeatureService.js";
import type { EditsResult } from "../../layers/graphics/editingSupport.js";
import type { ServiceEditsResult } from "../../rest/featureService/types.js";
import type { MapViewOrSceneView } from "../../views/MapViewOrSceneView.js";

/**
 * @experimental
 * @internal
 */
export type ApplySetItem = ApplySetAddItem | ApplySetModifyItem | ApplySetDeleteItem;

/**
 * @experimental
 * @internal
 */
export type ApplySetSupportedLayer = FeatureLayer | SubtypeGroupLayer;

/**
 * The set of changes which are to be applied to different features and services.
 *
 * @experimental
 * @internal
 */
export interface ApplySet {
  /**
   * The list of edits held in the ApplySet
   *
   * @experimental
   */
  get items(): ApplySetItem[];
  /**
   * Add an edit to the ApplySet
   *
   * @param item
   * @experimental
   */
  push(item: ApplySetItem): void;
  /**
   * Remove all edits from the ApplySet.
   *
   * @experimental
   */
  revert(): void;
  /**
   * A feature state provider which will automatically show temporary edits
   *
   * @experimental
   */
  featureState: FeatureStateProvider | null;
}

/**
 * Creates an ApplySet to hold edits across multiple layers
 *
 * @experimental
 * @internal
 */
export function createApplySet(): ApplySet;

/**
 * The properties required to create an ApplySetItem
 *
 * @experimental
 * @internal
 */
export interface ApplySetItemProperties {
  /**
   * The graphic that is affected
   *
   * @experimental
   */
  graphic: Graphic;
  /**
   * The layer on which this change is occurring
   *
   * @experimental
   */
  layer: ApplySetSupportedLayer;
  /**
   * The reason the change is being applied
   *
   * @experimental
   */
  reason?: string;
}

/**
 * An ApplySet item that represents adding a feature to a layer
 *
 * @experimental
 * @internal
 */
export interface ApplySetAddItem extends Omit<ApplySetItemProperties, "reason"> {
  /**
   * The type of change
   *
   * @experimental
   */
  type: "add";
}

/**
 * Create an ApplySet Add Item
 *
 * @param properties
 * @experimental
 * @internal
 */
export function createApplySetAddItem(properties: ApplySetItemProperties): ApplySetAddItem;

/**
 * The properties required to create an ApplySetItem modify item
 *
 * @experimental
 * @internal
 */
export interface ApplySetModifyItemProperties {
  /**
   * The graphic that is affected
   *
   * @experimental
   */
  graphic: Graphic;
  /**
   * The layer on which this change is occurring
   *
   * @experimental
   */
  layer: ApplySetSupportedLayer;
  /**
   * The reason the change is being applied
   *
   * @experimental
   */
  reason?: string;
  /**
   * The original version of the graphic, before applying the change
   *
   * @experimental
   */
  originalGraphic: Graphic;
}

/**
 * An ApplySet item that represents modifying a feature in a layer
 *
 * @experimental
 * @internal
 */
export interface ApplySetModifyItem extends Omit<ApplySetItemProperties, "reason" | "originalGraphic"> {
  /**
   * The type of change
   *
   * @experimental
   */
  type: "modify";
  /**
   * The original version of the graphic, before applying the change
   *
   * @experimental
   */
  revertGraphic: Graphic;
}

/**
 * Create an ApplySet Modify Item
 *
 * @param properties
 * @experimental
 * @internal
 */
export function createApplySetModifyItem(properties: ApplySetModifyItemProperties): ApplySetModifyItem;

/**
 * An ApplySet item that represents deleting a feature in a layer
 *
 * @experimental
 * @internal
 */
export interface ApplySetDeleteItem extends Omit<ApplySetItemProperties, "reason"> {
  /**
   * The type of change
   *
   * @experimental
   */
  type: "delete";
}

/**
 * Create an ApplySet Delete Item
 *
 * @param properties
 * @experimental
 * @internal
 */
export function createApplySetDeleteItem(properties: ApplySetItemProperties): ApplySetDeleteItem;

/**
 * Returned results from calling applyEdits on a Layer
 *
 * @experimental
 * @internal
 */
export interface ApplySetLayerEditResult {
  /**
   * The layer on which this change is occurring
   *
   * @experimental
   */
  layer: ApplySetSupportedLayer;
  /**
   * The returned results from calling applyEdits
   *
   * @experimental
   */
  edits: EditsResult;
  /**
   * The type of applyEdits call
   *
   * @experimental
   */
  type: "layer";
}

/**
 * Returned results from calling applyEdits on a Layer
 *
 * @experimental
 * @internal
 */
export interface ApplySetServiceEditResult {
  /**
   * The layer on which this change is occurring
   *
   * @experimental
   */
  featureService: FeatureService;
  /**
   * The returned results from calling applyEdits
   *
   * @experimental
   */
  edits: ServiceEditsResult[];
  /**
   * The type of applyEdits call
   *
   * @experimental
   */
  type: "service";
}

/**
 * Writes all the changes listed in an ApplySet, to their respective services.
 *
 * @param applySet
 * @param view
 * @experimental
 * @internal
 */
export function writeApplySetToService(applySet: ApplySet, view: MapViewOrSceneView): Promise<(ApplySetLayerEditResult | ApplySetServiceEditResult)[]>;

/**
 * Manages temporary feature edits which are displayed in LayerViews.
 *
 * @experimental
 * @internal
 */
export interface FeatureStateProvider {
  /**
   * Clears all temporary feature state from the map
   *
   * @experimental
   */
  clearAll: () => void;
}

/**
 * Creates a feature state provider, that will automatically show temporary feature changes
 * on a map.
 *
 * @param view
 * @experimental
 * @internal
 */
export function createFeatureStateProvider(view: MapViewOrSceneView): FeatureStateProvider | null;