import type VoxelLayer from "@arcgis/core/layers/VoxelLayer.js";
import type ImageryLayer from "@arcgis/core/layers/ImageryLayer.js";
import type ImageryTileLayer from "@arcgis/core/layers/ImageryTileLayer.js";
import type FeatureLayer from "@arcgis/core/layers/FeatureLayer.js";
import type WebMap from "@arcgis/core/WebMap.js";
import type WebScene from "@arcgis/core/WebScene.js";
import type Map from "@arcgis/core/Map.js";
import type SpatialReference from "@arcgis/core/geometry/SpatialReference.js";
import type { Diagnostic as ArcadeLanguageServiceDiagnostic } from "@arcgis/arcade-languageservice";
import type { ApiItem, BundleType, ProfileId } from "@arcgis/languages-api-utils";
import type { ProfileVariableInstances, ArcadeServices } from "@arcgis/core/arcade.js";
import type { DataCatalogDatastoreInfo } from "../utils/data-catalog-datastore.js";

export type Diagnostic = ArcadeLanguageServiceDiagnostic;

/**
 * Editor Profile definition used for configuring the editor:
 * - presenting to the user the Arcade API
 * - validating the expression
 * - proposing code completion
 * - formatting code
 */
export interface EditorProfileDefinition {
  /** Optional label for the profile. Used in the editor when displaying the variables panel. */
  label?: string;
  /**
   * The collection of variables that will be passed to the expression at execution time.
   * The variable declarations are used to help the editor provide validation and completion.
   */
  variables: ProfileVariable[];
  /** The version of the Arcade API that the editor should use during validation and completion. */
  apiVersion?: string;
  /** The Arcade API bundles that the editor should use during validation and completion. */
  bundles?: BundleType[];
  /** A collection of additional Arcade API items that will be provided at runtime. */
  additionalApiItems?: ApiItem[];
  /** A collection of api item names to hide. */
  hiddenApiItems?: string[];
}

/** @deprecated since 5.1. Use EditorProfileDefinition instead. */
export interface IEditorProfileDefinition {
  /** Optional label for the profile. Used in the editor when displaying the variables panel. */
  label?: string;
  /**
   * The collection of variables that will be passed to the expression at execution time.
   * The variable declarations are used to help the editor provide validation and completion.
   */
  variables: ProfileVariable[];
  /** The version of the Arcade API that the editor should use during validation and completion. */
  apiVersion?: string;
  /** The Arcade API bundles that the editor should use during validation and completion. */
  bundles?: BundleType[];
  /** A collection of additional Arcade API items that will be provided at runtime. */
  additionalApiItems?: ApiItem[];
  /** A collection of api item names to hide. */
  hiddenApiItems?: string[];
}

export interface PredefinedProfile {
  /** The well known profile ID. */
  id: ProfileId;
  /** The variables that should be disabled for the profile. */
  disabledVariables?: string[];
  /** Provides the definitions for the definition based variables (feature, featureSet, featureSetCollection). */
  definitions: VariableDefinitions;
  /** A collection of api item names to hide. */
  hiddenApiItems?: string[];
}

/** @deprecated since 5.1. Use PredefinedProfile instead. */
export interface IPredefinedProfile {
  /** The well known profile ID. */
  id: ProfileId;
  /** The variables that should be disabled for the profile. */
  disabledVariables?: string[];
  /** Provides the definitions for the definition based variables (feature, featureSet, featureSetCollection). */
  definitions: VariableDefinitions;
  /** A collection of api item names to hide. */
  hiddenApiItems?: string[];
}

/** The type of value variables supported. */
export type ProfileVariableValueType = "boolean" | "date" | "dateOnly" | "geometry" | "knowledgeGraph" | "number" | "text" | "time";

/**
 * The supported profile variable types.
 *
 * @internalremarks TODO: hopefully align with \@arcgis/core's Arcade variable type in the future
 */
export type ProfileVariable = ProfileArray | ProfileDataCatalogDatastore | ProfileDictionary | ProfileFeature | ProfileFeatureSet | ProfileFeatureSetCollection | ProfilePixel | ProfileValue | ProfileVoxel;

/** @deprecated since 5.1. Use ProfileVariable instead. */
export type IProfileVariable = ProfileVariable;

/** Properties common to all profile variables. */
export interface ProfileVariableBase {
  /** Name of the variable */
  name: string;
  /** Description for the variable */
  description?: string;
}

/** @deprecated since 5.1. Use ProfileVariableBase instead. */
export interface IProfileVariableBase {
  /** Name of the variable */
  name: string;
  /** Description for the variable */
  description?: string;
}

/** Single value profile variable declaration. */
export interface ProfileValue extends ProfileVariableBase {
  readonly type: ProfileVariableValueType;
}

/** @deprecated since 5.1. Use ProfileValue instead. */
export interface IProfileValue extends IProfileVariableBase {
  readonly type: ProfileVariableValueType;
}

/** Dictionary profile variable declaration. Properties of a dictionary are profile variables. */
export interface ProfileDictionary extends ProfileVariableBase {
  readonly type: "dictionary";
  /** The dictionary properties. Properties are variables as well. */
  properties?: ProfileVariable[];
}

/** @deprecated since 5.1. Use ProfileDictionary instead. */
export interface IProfileDictionary extends IProfileVariableBase {
  readonly type: "dictionary";
  /** The dictionary properties. Properties are variables as well. */
  properties?: ProfileVariable[];
}

/** Dictionary profile variable declaration. Properties of a dictionary are profile variables. */
export interface ProfileArray extends ProfileVariableBase {
  readonly type: "array";
  /** The type for the array elements. */
  elementType?: ProfileVariable;
}

/** @deprecated since 5.1. Use ProfileArray instead. */
export interface IProfileArray extends IProfileVariableBase {
  readonly type: "array";
  /** The type for the array elements. */
  elementType?: ProfileVariable;
}

/**
 * Feature profile variable declaration.
 * Features contains values that can be accessed by a name (aka field name or attribute name).
 * Optionally features can have a geometry that can be accessed by a specialized Arcade function.
 */
export interface ProfileFeature extends ProfileVariableBase {
  readonly type: "feature";
  /** Describes how the editor will find the definition for the feature. */
  definition?: FeatureDefinition;
}

/** @deprecated since 5.1. Use ProfileFeature instead. */
export interface IProfileFeature extends IProfileVariableBase {
  readonly type: "feature";
  /** Describes how the editor will find the definition for the feature. */
  definition?: FeatureDefinition;
}

/**
 * FeatureSet profile variable declaration.
 * FeatureSet represents a collection of Features.
 * Individual feature or subset of features can be accessed by using specialized Arcade functions.
 */
export interface ProfileFeatureSet extends ProfileVariableBase {
  readonly type: "featureSet";
  /** Describes how the editor will find the definition for the feature set. */
  definition?: FeatureSetDefinition;
}

/** @deprecated since 5.1. Use ProfileFeatureSet instead. */
export interface IProfileFeatureSet extends IProfileVariableBase {
  readonly type: "featureSet";
  /** Describes how the editor will find the definition for the feature set. */
  definition?: FeatureSetDefinition;
}

/**
 * FeatureSetCollection profile variable declaration.
 * FeatureSetCollections are used to represent Feature Services or Web Maps in Arcade expressions.
 * FeatureSetCollections expose two collections of Layers and Tables.
 */
export interface ProfileFeatureSetCollection extends ProfileVariableBase {
  readonly type: "featureSetCollection";
  /** Describes how the featureSetCollection will find its definition. */
  definition?: FeatureSetCollectionDefinition;
}

/** @deprecated since 5.1. Use ProfileFeatureSetCollection instead. */
export interface IProfileFeatureSetCollection extends IProfileVariableBase {
  readonly type: "featureSetCollection";
  /** Describes how the featureSetCollection will find its definition. */
  definition?: FeatureSetCollectionDefinition;
}

/** Voxel profile variable declaration. */
export interface ProfileVoxel extends ProfileVariableBase {
  readonly type: "voxel";
  /** Describes how the editor will find the definition for the voxel. */
  definition?: VoxelDefinition;
}

/** @deprecated since 5.1. Use ProfileVoxel instead. */
export interface IProfileVoxel extends IProfileVariableBase {
  readonly type: "voxel";
  /** Describes how the editor will find the definition for the voxel. */
  definition?: VoxelDefinition;
}

/** Pixel profile variable declaration. */
export interface ProfilePixel extends ProfileVariableBase {
  readonly type: "pixel";
  /** Describes how the editor will find the definition for the pixel. */
  definition?: PixelDefinition;
}

/** @deprecated since 5.1. Use ProfilePixel instead. */
export interface IProfilePixel extends IProfileVariableBase {
  readonly type: "pixel";
  /** Describes how the editor will find the definition for the pixel. */
  definition?: PixelDefinition;
}

export type FeatureLikeProfile = ProfileFeature | ProfilePixel | ProfileVoxel;

/** @deprecated since 5.1. Use FeatureLikeProfile instead. */
export type IFeatureLikeProfile = IProfileFeature | IProfilePixel | IProfileVoxel;

/**
 * Data catalog datastore profile variable declaration.
 *
 * @internal
 */
export interface ProfileDataCatalogDatastore extends ProfileVariableBase {
  readonly type: "dataCatalogDatastore";
  /** Describes how the editor will find the definition for the data catalog datastore. */
  definition?: DataCatalogDatastoreSource;
}

/** @internal */
export type DataCatalogDatabasePlatform = DataCatalogDatastoreInfo["databasePlatform"];

/** @internal */
export interface DataCatalogDatastoreInfoLike {
  /** The unique data catalog datastore id. */
  datastoreId: string;
  /** The datastore name. */
  name: string;
  /** The datastore type. */
  type: string;
  /** The database platform identifier. */
  databasePlatform: DataCatalogDatabasePlatform;
}

/** @internal */
export interface DataCatalogDatasetInfoLike {
  /** The dataset name. */
  name: string;
  /** The dataset type. */
  type: string;
}

/** @internal */
export interface DataCatalogFieldInfoLike {
  /** The field name. */
  name: string;
  /** The normalized field type used by the editor. */
  type: FeatureLayer["fields"][number]["type"];
  /** The raw field type returned by the service. */
  serverType: string;
  /** Indicates whether the field allows null values. */
  nullable: boolean;
}

/** @internal */
export interface DataCatalogTableDescriptionLike {
  /** The fields available in the table. */
  fields: DataCatalogFieldInfoLike[];
}

/**
 * Data catalog datastore instance shape accepted by the editor.
 *
 * Consumers can supply either a serialized datastore definition or a live datastore-like
 * object exposing the methods needed to fetch datastore metadata, datasets, and table descriptions.
 *
 * @internal
 */
export interface DataCatalogDatastoreInstance {
  /** The unique data catalog datastore id. */
  itemId?: string;
  /** The url for the data catalog service on the portal. */
  serviceUrl?: string;
  /** The portal hosting the data catalog service. */
  portal?: { url: string; };
  /** Fetches datastore metadata. */
  fetchInfo: () => Promise<DataCatalogDatastoreInfoLike>;
  /** Fetches datasets exposed by the datastore. */
  fetchDatasets: () => Promise<DataCatalogDatasetInfoLike[]>;
  /** Fetches a table description for the provided dataset name. */
  fetchTableDescription: (tableName: string) => Promise<DataCatalogTableDescriptionLike>;
}

/** @internal */
export type DataCatalogDatastoreSource = DataCatalogDatastoreDefinition | DataCatalogDatastoreInstance;

/**
 * Data catalog datastore definition.
 *
 * @internal
 */
export interface DataCatalogDatastoreDefinition {
  /** The unique data catalog datastore id. */
  id: string;
  /** The url for the data catalog service on the portal */
  serviceUrl: string;
  /** The portal url. This is required for accessing the data catalog service */
  portal: { url: string; };
}

/** Describes a portal item. */
export interface PortalItemProperties {
  /** The unique portal item id. */
  id: string;
  /** The optional portal url. Default: www.arcgis.com. */
  portal?: { url: string; };
}

/** @deprecated since 5.1. Use PortalItemProperties instead. */
export interface IPortalItemProperties {
  /** The unique portal item id. */
  id: string;
  /** The optional portal url. Default: www.arcgis.com. */
  portal?: { url: string; };
}

/** WebMap and Service FeatureSetCollection can be defined by a portal item. */
export interface PortalItemDefinition {
  /** Describes a portal item. */
  portalItem: PortalItemProperties;
}

/** @deprecated since 5.1. Use PortalItemDefinition instead. */
export interface IPortalItemDefinition {
  /** Describes a portal item. */
  portalItem: PortalItemProperties;
}

/** FeatureLayer can be defined from a Feature Layer portal item. */
export interface FeatureLayerItemDefinition extends PortalItemDefinition {
  /** The layer id in the feature layer portal item. Default to 0. */
  layerId?: number;
}

/** @deprecated since 5.1. Use FeatureLayerItemDefinition instead. */
export interface IFeatureLayerItemDefinition extends IPortalItemDefinition {
  /** The layer id in the feature layer portal item. Default to 0. */
  layerId?: number;
}

/** FeatureLayers and GroupLayers can be created by providing the url to the resource in a feature service. */
export interface UrlDefinition {
  /** The url of the resource. */
  url?: string | null;
}

/** @deprecated since 5.1. Use UrlDefinition instead. */
export interface IUrlDefinition {
  /** The url of the resource. */
  url?: string | null;
}

/** The most basic way to define a feature or feature set is by passing a collection of fields */
export type FieldsDefinition = Pick<FeatureLayer, "fields">;

/** @deprecated since 5.1. Use FieldsDefinition instead. */
export type IFieldsDefinition = FieldsDefinition;

/**
 * FeatureDefinition represents various ways a Feature variable could be defined.
 * Layer instance is the most common way to define a feature,
 * the minimum required is that the layer instance exposes a collection of fields.
 * An object with just fields can be used to define a feature from a collection of fields.
 * An object with a url property can be used to define a feature from a feature service.
 * An object with minimal portal item properties and a layer id can be used to define a feature from a feature layer portal item.
 * For the last two options, a feature layer instance will be created by the editor profile.
 */
export type FeatureDefinition = FeatureLayerItemDefinition | FieldsDefinition | UrlDefinition;

/**
 * VoxelDefinition represents various ways a Voxel variable could be defined.
 * Layer instance is the most common way to define a voxel.
 */
export type VoxelDefinition = FeatureLayerItemDefinition | UrlDefinition | VoxelLayer;

/** PixelDefinition represents various ways a Pixel variable could be defined. */
export type PixelDefinition = FeatureLayerItemDefinition | ImageryLayer | ImageryTileLayer | UrlDefinition;

/**
 * FeatureSetDefinition represents the various ways a FeatureSet variable could be defined.
 * See FeatureDefinition for more details.
 */
export type FeatureSetDefinition = FeatureLayerItemDefinition | FieldsDefinition | UrlDefinition;

/**
 * FeatureSetCollectionDefinition represents the various ways a FeatureSetCollection could be defined
 * as a feature service or a web map.
 */
export type FeatureSetCollectionDefinition = Map | PortalItemDefinition | UrlDefinition | WebMap | WebScene;

/** A collection of variables that make up a profile definition. */
export type VariableDefinitions = Record<string, FeatureDefinition | FeatureSetCollectionDefinition | FeatureSetDefinition | ProfileVariable[] | undefined>;

export interface ExtendedPredefinedProfile extends PredefinedProfile {
  additionalVariables?: ProfileVariable[];
}

/** @deprecated since 5.1. Use ExtendedPredefinedProfile instead. */
export interface IExtendedPredefinedProfile extends IPredefinedProfile {
  additionalVariables?: ProfileVariable[];
}

export interface EditorCodeSuggestionGroup {
  /** Label for the suggestion group */
  label: string;
  /** List of suggestions for the group */
  suggestions: EditorCodeSuggestion[];
}

/** @deprecated since 5.1. Use EditorCodeSuggestionGroup instead. */
export interface IEditorCodeSuggestionGroup {
  /** Label for the suggestion group */
  label: string;
  /** List of suggestions for the group */
  suggestions: EditorCodeSuggestion[];
}

export interface EditorCodeSuggestion {
  /** A short label for the suggestion. Can be thought as a title */
  label: string;
  /** A short description that will be showed below the label in the list of suggestions panel */
  description?: string;
  /** Markdown string that will be rendered along with the code in the detail panel */
  documentation?: string;
  /** The code for the suggestion. Will be injected in the editor is selected */
  code: string;
}

/** @deprecated since 5.1. Use EditorCodeSuggestion instead. */
export interface IEditorCodeSuggestion {
  /** A short label for the suggestion. Can be thought as a title */
  label: string;
  /** A short description that will be showed below the label in the list of suggestions panel */
  description?: string;
  /** Markdown string that will be rendered along with the code in the detail panel */
  documentation?: string;
  /** The code for the suggestion. Will be injected in the editor is selected */
  code: string;
}

export type SidePanelName = "api" | "none" | "suggestions" | "variables";

export interface ContextReferences {
  /**
   * Spatial reference object used to define the spatial reference for the arcade runtime.
   * By defaults, the spatial reference is set to Web Mercator (wkid: 3857).
   */
  spatialReference?: SpatialReference | { wkid: number; } | null;
  /**
   * Defines the default time zone in which to create and display Arcade date types.
   * By default, the time zone is set to "system".
   */
  timeZone?: string;
  /** Defines the "services" passed to the arcade executor. */
  services?: ArcadeServices;
}

/** @deprecated since 5.1. Use ContextReferences instead. */
export interface IContextReferences {
  /**
   * Spatial reference object used to define the spatial reference for the arcade runtime.
   * By defaults, the spatial reference is set to Web Mercator (wkid: 3857).
   */
  spatialReference?: SpatialReference | { wkid: number; } | null;
  /**
   * Defines the default time zone in which to create and display Arcade date types.
   * By default, the time zone is set to "system".
   */
  timeZone?: string;
  /** Defines the "services" passed to the arcade executor. */
  services?: ArcadeServices;
}

/**
 * If a profile doesn't contain a map, the spatial reference of geometries will be defaulted to
 * wkid: 3857.
 * The test context objects allows to set the execution spatial reference for such scenario.
 */
export interface EditorTestContext extends ContextReferences {
  /**
   * An object of key/value pairs where the key is the name of a profile variable.
   * The key's value must be of type ArcGIS core ProfileVariableInstance.
   */
  profileVariableInstances: ProfileVariableInstances;
}

/** @deprecated since 5.1. Use EditorTestContext instead. */
export interface IEditorTestContext extends IContextReferences {
  /**
   * An object of key/value pairs where the key is the name of a profile variable.
   * The key's value must be of type ArcGIS core ProfileVariableInstance.
   */
  profileVariableInstances: ProfileVariableInstances;
}