import type FeatureLayer from "../../layers/FeatureLayer.js";
import type SubtypeGroupLayer from "../../layers/SubtypeGroupLayer.js";
import type FeatureService from "./FeatureService.js";
import type { EditedFeatures, Edits, FeatureEditResult } from "../../editing/types.js";
import type { CurveType } from "../../geometry/types.js";
import type { FeatureLayerCapabilities } from "../../layers/types.js";
import type { AssociationJsonType } from "../../networks/support/typeUtils.js";
import type { GeometryType } from "../support/types.js";

/** @since 5.0 */
export interface DataCapabilities {
  /**
   * Indicates if the feature service is versioned.
   *
   * @since 5.0
   */
  isDataVersioned: boolean;
  /**
   * Indicates if the feature service is branch versioned.
   *
   * @since 5.0
   */
  isDataBranchVersioned: boolean;
  /**
   * Indicates if the service supports geometries with true curves.
   *
   * @since 5.0
   */
  supportsTrueCurve: boolean;
  /**
   * The types of true curves the service supports.
   *
   * @since 5.0
   */
  supportedCurveTypes: readonly CurveType[];
}

/** @since 5.0 */
export interface SyncDataOptions {
  /**
   * Indicates whether annotations should be included in the sync data.
   *
   * @since 5.0
   */
  annotations: boolean;
  /**
   * Indicates whether dimensions should be included in the sync data.
   *
   * @since 5.0
   */
  dimensions: boolean;
  /**
   * Indicates whether contingent values should be included in the sync data.
   *
   * @since 5.0
   */
  contingentValues: boolean;
  /**
   * Indicates whether attribute rules should be included in the sync data.
   *
   * @since 5.0
   */
  attributeRules: boolean;
  /**
   * Indicates whether the utility network system should be included in the sync data.
   *
   * @since 5.0
   */
  utilityNetworkSystem: boolean;
  /**
   * Indicates whether the full annotation model should be included in the sync data.
   *
   * @since 5.0
   */
  annotationFullModel: boolean;
  /**
   * Indicates whether 3D objects should be included in the sync data.
   *
   * @since 5.0
   */
  include3DObjects: boolean;
  /**
   * Indicates whether missing utility network layers should be included in the sync data.
   *
   * @since 5.0
   */
  utilityNetworkMissingLayers: boolean;
  /**
   * Indicates whether true curves should be preserved in the sync data.
   *
   * @since 5.0
   */
  preserveTrueCurves: boolean;
}

/** @since 5.0 */
export interface SyncCapabilities {
  /**
   * Indicates whether the service supports asynchronous synchronization.
   *
   * @since 5.0
   */
  supportsAsync: boolean;
  /**
   * Indicates the supported sync data options for the service
   *
   * @since 5.0
   */
  supportedSyncDataOptions: SyncDataOptions;
}

/** @since 5.0 */
export interface OperationCapabilities {
  /**
   * Indicates if new features can be [added](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/FeatureService/#applyEdits) to the service.
   *
   * @since 5.0
   */
  supportsAdd: boolean;
  /**
   * Indicates whether the service supports change tracking for features.
   *
   * @since 5.0
   */
  supportsChangeTracking: boolean;
  /**
   * Indicates if features can be [deleted](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/FeatureService/#applyEdits) from the service.
   *
   * @since 5.0
   */
  supportsDelete: boolean;
  /**
   * Indicates if features in the service can be [edited](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/FeatureService/#applyEdits).
   *  Use `supportsAdd`, `supportsUpdate` and `supportsDelete` to determine which editing operations are supported.
   *
   * @since 5.0
   */
  supportsEditing: boolean;
  /**
   * Indicates if features in the service can be queried.
   *
   * @since 5.0
   */
  supportsQuery: boolean;
  /**
   * Indicates whether the service supports querying data elements.
   *
   * @since 5.0
   */
  supportsQueryDataElements: boolean;
  /**
   * Indicates whether the service supports querying domains.
   *
   * @since 5.0
   */
  supportsQueryDomains: boolean;
  /**
   * Indicates whether the service supports querying contingent values.
   *
   * @since 5.0
   */
  supportsQueryContingentValues: boolean;
  /**
   * Indicates whether the service supports synchronization.
   *
   * @since 5.0
   */
  supportsSync: boolean;
  /**
   * Indicates whether the service supports updating features.
   *
   * @since 5.0
   */
  supportsUpdate: boolean;
}

/** @since 5.0 */
export interface QueryCapabilities {
  /**
   * The maximum number of records that will be returned for a given query.
   *
   * @since 5.0
   */
  maxRecordCount?: number | null;
  /**
   * This property is used to limit the number of records returned based on a factor of the `query.maxRecordCount`.
   *
   * @since 5.0
   */
  maxRecordCountFactor?: number | null;
  /**
   * Whether PBF-encoded true curves are supported.
   *
   * @since 5.0
   */
  supportsTrueCurveWithPBF: boolean;
}

/** @since 5.0 */
export interface EditingCapabilities {
  /**
   * Indicates if the service supports async apply edits.
   *
   * @since 5.0
   */
  supportsAsyncApplyEdits: boolean;
  /**
   * Indicates if the `globalId` values provided by the client are used in [FeatureService.applyEdits()](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/FeatureService/#applyEdits).
   *
   * @since 5.0
   */
  supportsGlobalId: boolean;
  /**
   * Indicates that the service supports returning edits in the source spatial reference of the layer they belong to.
   *
   * @since 5.0
   */
  supportsReturnServiceEditsInSourceSpatialReference: boolean;
  /**
   * Indicates whether the service supports splitting features.
   *
   * @since 5.0
   */
  supportsSplit: boolean;
  /**
   * Indicates if feature geometries containing true curves can be updated.
   *
   * @since 5.0
   */
  supportsTrueCurveUpdate: boolean;
  /**
   * Indicates whether updates to true curves should be restricted to clients
   * that support true editing. If `true`, `applyEdits` calls that update true
   * curves must have the `trueCurveClient` parameter set to `true`.
   *
   * @since 5.0
   */
  supportsTrueCurveUpdateByTrueCurveClientsOnly: boolean;
}

/**
 * Describes the layer's supported capabilities.
 *
 * @since 4.28
 */
export interface ServiceCapabilities {
  /**
   * Describes characteristics of the data in the service.
   *
   * @since 5.0
   */
  data: DataCapabilities;
  /**
   * Describes operations that can be performed on features in the layer.
   *
   * @since 5.0
   */
  operations: OperationCapabilities;
  /**
   * Describes [Query](https://developers.arcgis.com/javascript/latest/references/core/rest/support/Query/)
   * operations that can be performed on the Feature Service.
   *
   * @since 5.0
   */
  query: QueryCapabilities;
  /**
   * Describes editing capabilities that can be performed on the features in the layer via [FeatureService.applyEdits()](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/FeatureService/#applyEdits).
   *
   * @since 5.0
   */
  editing: EditingCapabilities;
  /**
   * Describes the synchronization capabilities of a service.
   *
   * @since 5.0
   */
  sync: SyncCapabilities;
}

/**
 * Results returned from the [createFeatureServices()](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/utils/#createFeatureServices) method.
 *
 * @since 4.28
 */
export interface FeatureServiceResourcesBundle {
  /**
   * Returns a featureService object.
   *
   * @since 5.0
   */
  featureService: FeatureService;
  /**
   * An array of [FeatureLayers](https://developers.arcgis.com/javascript/latest/references/core/layers/FeatureLayer/)
   *
   * @since 5.0
   */
  layers: (FeatureLayer | SubtypeGroupLayer)[];
}

/**
 * Contains information for a table in the Feature Service.
 *
 * @since 4.30
 */
export interface TableInfo {
  /**
   * The id of a table.
   *
   * @since 5.0
   */
  id: number;
  /**
   * The name of a table.
   *
   * @since 5.0
   */
  name: string;
  /**
   * The type of the table.
   *
   * @since 5.0
   */
  type: string;
  /**
   * The service url
   *
   * @since 5.0
   */
  url: string;
}

/**
 * Contains information for a layer in the Feature Service.
 *
 * @since 4.30
 */
export interface LayerInfo extends TableInfo {
  /**
   * The geometry type of a layer.
   *
   * @since 5.0
   */
  geometryType?: GeometryType;
}

/**
 * Contains information for a layer in the Feature Service.
 *
 * @since 4.30
 */
export interface LayerDefinition extends LayerInfo {
  /**
   * The capabilities of the layer.
   *
   * @since 5.0
   */
  capabilities: FeatureLayerCapabilities;
}

/**
 * Contains information for a table in the Feature Service.
 *
 * @since 4.30
 */
export interface TableDefinition extends TableInfo {
  /**
   * The capabilities of the table.
   *
   * @since 5.0
   */
  capabilities: FeatureLayerCapabilities;
}

/**
 * Contains information returned from the service containing layerInfos, tableInfos, and server capabilities.
 *
 * @since 4.30
 */
export interface ServiceContents {
  /**
   * Contains the Layer Definition of a layer.
   *
   * @since 5.0
   */
  layers: LayerDefinition[];
  /**
   * Contains the Table Definition of a layer.
   *
   * @since 5.0
   */
  tables: TableDefinition[];
}

/**
 * Specifies a traditional or foreign-key association to delete.
 *
 * This object can be used in three ways:
 *
 * 1. Specify all fields: Can delete any association, `globalId` can optionally be specified
 *    for traditional associations for improved performance.
 * 2. Only global ID: Can only delete traditional associations with non-null global IDs.
 * 3. Omit global ID: Primarily used to delete foreign-key associations.
 *    Can also delete traditional associations, but with reduced performance.
 *
 * @beta
 * @since 4.34
 */
export type DeleteAssociationEdit = DeleteAssociationEditAllFields | DeleteAssociationEditOnlyGlobalId | DeleteAssociationEditOmitGlobalId;

/**
 * Specifies a traditional or foreign-key association to delete.
 * To delete a foreign-key association, `globalId` must be the null GUID `{00000000-0000-0000-0000-000000000000}`.
 * When deleting a traditional association, `globalId` is optional, but improves performance when specified.
 *
 * @beta
 * @since 5.0
 */
export interface DeleteAssociationEditAllFields {
  /**
   * The global ID of the association to delete.
   *
   * @since 5.0
   */
  globalId: string;
  /**
   * The type of association.
   *
   * @since 5.0
   */
  associationType: AssociationJsonType;
  /**
   * The source ID of the "from" feature.
   *
   * @since 5.0
   */
  fromSourceId: number;
  /**
   * The global ID of the "from" feature.
   *
   * @since 5.0
   */
  fromGlobalId: string;
  /**
   * The terminal ID of the "from" feature.
   *
   * @since 5.0
   */
  fromTerminalId?: number | null;
  /**
   * The source ID of the "to" feature.
   *
   * @since 5.0
   */
  toSourceId: number;
  /**
   * The global ID of the "to" feature.
   *
   * @since 5.0
   */
  toGlobalId: string;
  /**
   * The terminal ID of the "to" feature.
   *
   * @since 5.0
   */
  toTerminalId?: number | null;
}

/**
 * Specifies a traditional association to delete by the association's global ID.
 *
 * @beta
 * @since 5.0
 */
export type DeleteAssociationEditOnlyGlobalId = Pick<DeleteAssociationEditAllFields, "globalId">;

/**
 * Specifies a foreign-key association to delete.
 * Can also be used to delete a traditional association, but is less efficient than specifying
 * the traditional association's global ID.
 *
 * @beta
 * @since 5.0
 */
export type DeleteAssociationEditOmitGlobalId = Omit<DeleteAssociationEditAllFields, "globalId">;

/**
 * Specifies grouped objects to combine in a telecom domain network.
 *
 * @beta
 * @since 4.34
 */
export interface CombineGroupedObjectsEdit {
  /**
   * The global IDs of the grouped features to combine.
   *   The objects being combined must have consecutive unit IDs, exist in the same table and unit container,
   *   and have the same attribute values.
   *
   * @since 5.0
   */
  globalIds: string[];
}

/**
 * Specifies a grouped object to divide, and the units to divide the object into.
 *
 * @beta
 * @since 4.34
 */
export interface DivideGroupedObjectsEdit {
  /**
   * The global ID of the grouped object to divide.
   *
   * @since 5.0
   */
  globalId: string;
  /**
   * The units to divide the grouped object into.
   *   Each number in this array describes the number of units to assign an object resulting from the division.
   *   E.g., to divide one object with 6 units into an object with 5 units and another object with 1 unit, specify `[5, 1]`.
   *
   * @since 5.0
   */
  numUnits: number[];
}

/**
 * Results returned from the [UtilityNetwork.generateAddAssociations()](https://developers.arcgis.com/javascript/latest/references/core/networks/UtilityNetwork/#generateAddAssociations), [UtilityNetwork.generateDeleteAssociations()](https://developers.arcgis.com/javascript/latest/references/core/networks/UtilityNetwork/#generateDeleteAssociations),
 * [UnitIdentifierManager.generateCombineNetworkElements()](https://developers.arcgis.com/javascript/latest/references/core/networks/UnitIdentifierManager/#generateCombineNetworkElements), and [UnitIdentifierManager.generateDivideNetworkElements()](https://developers.arcgis.com/javascript/latest/references/core/networks/UnitIdentifierManager/#generateDivideNetworkElements).
 *
 * @since 4.29
 */
export interface ServiceEdits extends Edits {
  /**
   * The layerId of the feature layer.
   *
   * @since 5.0
   */
  id: number;
  /**
   * An array of objects specifying traditional or foreign-key associations to delete.
   *
   * Each object can be type [DeleteAssociationEditAllFields](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/types/#DeleteAssociationEditAllFields),
   * [DeleteAssociationEditOnlyGlobalId](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/types/#DeleteAssociationEditOnlyGlobalId), or [DeleteAssociationEditOmitGlobalId](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/types/#DeleteAssociationEditOmitGlobalId),
   * depending on the association to delete.
   *
   * @since 5.0
   */
  deleteAssociations?: DeleteAssociationEdit[];
  /**
   * An array of objects specifying
   *   grouped objects to combine in a telecom domain network. The objects being combined must have consecutive unit IDs, exist in the same table and unit container,
   *   and have the same attribute values.
   *
   * @since 5.0
   */
  combineGroupedObjects?: CombineGroupedObjectsEdit[];
  /**
   * An array of objects specifying
   *   a grouped objects to divide, and the units to divide the objects into, in a telecom domain network.
   *
   * @since 5.0
   */
  divideGroupedObjects?: DivideGroupedObjectsEdit[];
  /**
   * Identifier for globalId and objectId.
   *
   * @since 5.0
   */
  identifierFields?: ServiceEditsIdentifierFields;
}

/** @since 5.0 */
export interface ServiceEditsIdentifierFields {
  /**
   * The globalId identifier.
   *
   * @since 5.0
   */
  globalIdField?: string | null;
  /**
   * The objectId identifier.
   *
   * @since 5.0
   */
  objectIdField: string;
}

/** @since 5.0 */
export interface ServiceEditOptions {
  /**
   * Attests that the edits were prepared in a way that avoids the loss of true curve geometries. An app should only set this parameter to `true` if it can
   * guarantee that the entire client-side chain of custody handles true curves properly.
   *
   * Setting this parameter is only necessary when the feature service is configured to restrict updating of true curve geometries to clients that
   * explicitly indicate support for true curve editing.
   *
   * Considerations for curve-aware editing include:
   *   - Prior to editing, the feature must be obtained from the service with its true curves intact, e.g. by a query with the [Query.returnTrueCurves](https://developers.arcgis.com/javascript/latest/references/core/rest/support/Query/#returnTrueCurves) parameter set to `true`.
   *   - Ensure all spatial operations performed on the geometry preserve curves — i.e., avoids densifying curves into linear approximations.
   *   - Beware of code that references a geometry's [Polygon.rings](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polygon/#rings) or [Polyline.paths](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polyline/#paths) properties directly. Curve-aware iteration over the vertices of a geometry requires a conditional logic whereby [Polygon.curveRings](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polygon/#curveRings) or [Polyline.curvePaths](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polyline/#curvePaths) are used when defined, and [Polygon.rings](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polygon/#rings) or [Polyline.paths](https://developers.arcgis.com/javascript/latest/references/core/geometry/Polyline/#paths) are used otherwise.
   *
   * @since 5.0
   */
  editsRespectTrueCurves?: boolean;
  /**
   * The geodatabase version to apply the edits. This parameter applies only if the
   * [capabilities.data.isDataVersioned](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/types/#DataCapabilities) property of the layer is `true`.
   *
   * @since 5.0
   */
  gdbVersion?: string;
  /**
   * Indicates whether the edits can be applied using globalIds of features or attachments.
   * This parameter applies only if the layer's [capabilities.editing.supportsGlobalId](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/types/#EditingCapabilities) property is `true`.
   * When `false`, globalIds submitted with the features are ignored and the service assigns new globalIds
   * to the new features. When `true`, the globalIds must be submitted with the new features. When updating existing features, if the `globalIdUsed` is
   * `false`, the objectIds of the features to be updated must be provided. If the `globalIdUsed` is `true`, globalIds of features to be updated must be provided.
   * When deleting existing features, set this property to `false` as deletes operation only accepts `objectIds` at the current version of the API.
   *
   * When adding, updating or deleting attachments, `globalIdUsed` parameter must be set to `true` and the attachment globalId must be set.
   * For new attachments, the user must provide globalIds. In order for an attachment to be updated or deleted, clients must include its globalId.
   * Attachments are not supported in an edit payload when `globalIdUsed` is `false`.
   *
   * @since 5.0
   */
  globalIdUsed?: boolean;
  /**
   * Added at 10.5 and works with ArcGIS Server services only.
   * This parameter specifies whether to apply edits in the order they are submitted in the JSON.
   * If true, edits will apply in the submitted order. If false, which is the default, edits will apply in ascending layer-ID order.
   * All the edits for the layer with the lowest ID will apply first.
   * The `honorSequenceOfEdits` option controls the execution order of edit operations across the edit arrays, not the execution order of individual features within a single edit operation.
   *
   * @example
   * // Updating multiple features with a single edit object. Execution order of individual updates is not guaranteed
   * [
   *   {
   *     id: 21,
   *     updateFeatures: [
   *       { attributes: { objectid: 71816, isMain: 0 } },
   *       { attributes: { objectid: 71416, isMain: 1 } }
   *     ]
   *   }
   * ]
   *
   * // Updating multiple features with multiple edit objects to guarantee execution order of individual updates
   * [
   *   {
   *     id: 21,
   *     updateFeatures: [
   *       { attributes: { objectid: 71816, isMain: 0 } }
   *     ]
   *   },
   *   {
   *     id: 21,
   *     updateFeatures: [
   *       { attributes: { objectid: 71416, isMain: 1 } }
   *     ]
   *   }
   * ]
   * @since 5.0
   */
  honorSequenceOfEdits?: boolean;
  /**
   * This option was added at 10.6. This is set by a client during long transaction editing on a branch version.
   * This parameter is used to apply the edits with the same edit moment as the previous set of edits.
   * This allows an editor to apply a single block of edits partially, complete another task, and then complete the block of edits.
   * When set to true, the edits are applied with the same edit moment as the previous sets of edits.
   * When set to false the edits are applied with a new edit moment. By default, this parameter is set to false.
   *
   * @since 5.0
   */
  usePreviousEditMoment?: boolean;
}

/**
 * Represents the result of dividing a grouped object in a telecom domain network.
 *
 * @beta
 * @since 4.34
 */
export interface DivideGroupedObjectResult {
  /**
   * Global ID of the original object before division.
   *
   * @since 5.0
   */
  globalId: string;
  /**
   * The global IDs of objects resulting from the division, including the original object.
   *   I.e., the GUIDs that the target object was divided into.
   *
   * @since 5.0
   */
  dividedGlobalIds: string[];
  /**
   * If the divided object was a junction object that was directly connected
   *   to one or more edge objects prior to the division, the edge objects are also divided to maintain connectivity.
   *   This array contains GUIDs of any edge objects that are connected to the divided junction objects,
   *   including those that were connected prior to the division.
   *
   * @since 5.0
   */
  connectedEdgeGlobalIds: string[];
}

/**
 * Represents the result of combining one or more grouped objects in a telecom domain network.
 *
 * @beta
 * @since 4.34
 */
export interface CombineGroupedObjectResult {
  /**
   * The global ID of the object resulting from the combine operation.
   *
   * @since 5.0
   */
  combinedGlobalId: string;
  /**
   * The global IDs of the objects deleted to perform the combination.
   *
   * @since 5.0
   */
  deletedGlobalIds: string[];
}

/**
 * Results returned from the [FeatureService.applyEdits()](https://developers.arcgis.com/javascript/latest/references/core/rest/featureService/FeatureService/#applyEdits) method. It contains features
 * that were added, deleted or updated in different feature layers. It also contains the edit moment.
 *
 * @since 4.28
 */
export interface ServiceEditsResult {
  /**
   * The layerId of the feature layer where features were edited.
   *
   * @since 5.0
   */
  id: number;
  /**
   * Results returned from an add operation.
   *
   * @since 5.0
   */
  addFeatureResults: FeatureEditResult[];
  /**
   * Results returned from an update operation.
   *
   * @since 5.0
   */
  updateFeatureResults: FeatureEditResult[];
  /**
   * Results returned from a delete operation.
   *
   * @since 5.0
   */
  deleteFeatureResults: FeatureEditResult[];
  /**
   * Results returned from a divide operation.
   *
   * @since 5.0
   */
  divideGroupedObjectResults?: DivideGroupedObjectResult[];
  /**
   * Results returned from a combine operation.
   *
   * @since 5.0
   */
  combineGroupedObjectResults?: CombineGroupedObjectResult[];
  /**
   * Results returned from a split operation.
   *
   * @since 5.0
   */
  splitsResults?: SplitsResult[];
  /**
   * Results returned from an add attachments operation.
   *
   * @since 5.0
   */
  addAttachmentResults: FeatureEditResult[];
  /**
   * Results returned from an update attachments operation.
   *
   * @since 5.0
   */
  updateAttachmentResults: FeatureEditResult[];
  /**
   * Results returned from a delete attachments operation.
   *
   * @since 5.0
   */
  deleteAttachmentResults: FeatureEditResult[];
  /**
   * Object containing all edited features belonging to the specified layer.
   *
   * @since 5.0
   */
  editedFeatures?: EditedFeatures;
  /**
   * Returns the editMoment of an edit.
   *
   * @since 5.0
   */
  editMoment?: number;
}

/**
 * Represents the result of a split operation.
 *
 * @since 5.0
 */
export interface SplitsResult {
  /**
   * The object ID of the feature that was split.
   *
   * @since 5.0
   */
  objectId: number;
  /**
   * The object ID of the original feature and the feature(s) created from the split.
   *
   * @since 5.0
   */
  results: number[];
}