import type VoxelLayer from "@arcgis/core/layers/VoxelLayer";
import type ImageryLayer from "@arcgis/core/layers/ImageryLayer";
import type ImageryTileLayer from "@arcgis/core/layers/ImageryTileLayer";
import type FeatureLayer from "@arcgis/core/layers/FeatureLayer.js";
import type Layer from "@arcgis/core/layers/Layer";
import type PortalItemResource from "@arcgis/core/portal/PortalItemResource";
import type WebMap from "@arcgis/core/WebMap";
import type WebScene from "@arcgis/core/WebScene";
import type Map from "@arcgis/core/Map";
import type Domain from "@arcgis/core/layers/support/Domain.js";
import type { BundleType, ApiItem, ApiVariableType, ProfileId } from "@arcgis/languages-api-utils";

/** The editor profile strings. */
export type ProfileStrings = ReturnType<typeof profileStrings>;

/**
 * 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 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: IProfileVariable[];
  /** 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[];
}

/** The type of variables supported. */
export type ProfileVariableType = ApiVariableType;

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

/**
 * The supported profile variable types.
 * TODO: hopefully align with \@arcgis/core's Variable type in the future.
 */
export type IProfileVariable = IProfileArray | IProfileDictionary | IProfileFeature | IProfileFeatureSet | IProfileFeatureSetCollection | IProfilePixel | IProfileValue | IProfileVoxel;

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

/** Single value profile variable declaration. */
export interface IProfileValue extends IProfileVariableBase {
  readonly type: "boolean" | "date" | "dateOnly" | "geometry" | "knowledgeGraph" | "number" | "text" | "time";
}

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

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

/**
 * 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 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 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 IProfileFeatureSetCollection extends IProfileVariableBase {
  readonly type: "featureSetCollection";
  /** Describes how the featureSetCollection will find its definition. */
  definition?: FeatureSetCollectionDefinition;
}

/**
 * Voxel profile variable declaration.
 * Voxel contains values that can be accessed by a name (aka field name or attribute name).
 */
export interface IProfileVoxel extends IProfileVariableBase {
  readonly type: "voxel";
  /** Describes how the editor will find the definition for the feature. */
  definition?: VoxelDefinition;
}

/**
 * Pixel profile variable declaration.
 * Pixel contains values that can be accessed by a name (aka field name or attribute name).
 */
export interface IProfilePixel extends IProfileVariableBase {
  readonly type: "pixel";
  /** Describes how the editor will find the definition for the feature. */
  definition?: PixelDefinition;
}

export type IFeatureLikeProfile = IProfileFeature | IProfilePixel | IProfileVoxel;

/** Describes a portal item. */
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 IPortalItemDefinition {
  /** Describes a portal item. */
  portalItem: IPortalItemProperties;
}

/** FeatureLayer can be defined from a Feature Layer portal item. */
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 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 IFieldsDefinition = Pick<FeatureLayer, "fields">;

/**
 * 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 = IFeatureLayerItemDefinition | IFieldsDefinition | IUrlDefinition;

/** VoxelDefinition represents various ways a Voxel variable could be defined. */
export type VoxelDefinition = IFeatureLayerItemDefinition | IUrlDefinition | VoxelLayer;

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

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

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

export type VariableDefinitions = Record<string, FeatureDefinition | FeatureSetCollectionDefinition | FeatureSetDefinition | IProfileVariable[] | undefined>;

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[];
}

export type IndexedProfileStrings = ProfileStrings
  & Record<string, string | undefined>;

/** Describe the support source for some of the editor variables. */
export type SupportedSource = IFieldsDefinition;

/** Describe the supported source for pixel variables */
export type SupportedPixelSource = ImageryLayer | ImageryTileLayer;

/** Describe the supported source for voxel variables */
export type SupportedVoxelSource = VoxelLayer;

/** Layer that supports title. */
export type ITitleCapableSource = Pick<Layer, "title">;

/** Layer that supports url. */
export type IUrlCapableSource = Pick<FeatureLayer, "url">;

/** Layer that supports layer id. */
export type ILayerIdCapableSource = Pick<FeatureLayer, "layerId">;

/** Layer that supports queryFeatures */
export interface IFeatureLayerLikeInstance extends IFieldsDefinition, ITitleCapableSource {
  /** The declared class of the layer. */
  declaredClass: string;
  /** The unique ID assigned to the layer. */
  id: string;
  /** The layer ID, or layer index, of a Feature Service layer. */
  layerId?: number | null;
  /** Executes a Query against the feature service and returns a Promise to a FeatureSet. */
  queryFeatures: FeatureLayer["queryFeatures"];
  /** The service's metadata JSON exposed by the ArcGIS REST API. */
  sourceJSON?: unknown;
}

/** Layer that supports field domains. */
export interface IDomainsCapableSource {
  /** Returns the Domain associated with the given field name. */
  getFieldDomain: (fieldName: string) => Domain | null;
}

/** Layer that implements types */
export type IFeatureTypesCapableSource = IDomainsCapableSource
  & Pick<FeatureLayer, "typeIdField" | "types">;

/** Layer that has a subtype field */
export type ISubtypeFieldCapableSource = IDomainsCapableSource
  & Pick<FeatureLayer, "subtypeField">;

/** Layer that exposes subtypes. */
export type ISubtypesCapableSource = ISubtypeFieldCapableSource
  & Pick<FeatureLayer, "subtypes">;

/** Layer that supports relationships. */
export type IRelationshipsCapableSource = ILayerIdCapableSource
  & IUrlCapableSource
  & Pick<FeatureLayer, "relationships">;

/** Layer that is capable of being non-spatial. */
export type ITableCapableSource = Pick<FeatureLayer, "isTable">;

/** Layer that is capable of having geometry */
export type IGeometryCapableSource = Pick<FeatureLayer, "geometryType">
  & {
  isTable?: false;
};

/** An instance that requires a load operation to be performed. */
export type ILoadableSource = Pick<Layer, "load">;

/** An instance that requires a loadAll operation to be performed. */
export type ILoadAllCapable = Pick<WebMap | WebScene, "loadAll">;

/** An instance that is based on a portal item. */
export type IPortalItemCapable = Pick<PortalItemResource, "portalItem">;

/** An instance that exposes all layers and tables collections. */
export type IGroupLayerCapable = Pick<Map, "allLayers" | "allTables">;

/** Represents a string that will have to be manufactured from a Intl strings resource. */
export interface IIntlString {
  /** The intl code in the Intl strings resource. */
  code: string;
  /**
   * The properties that the Intl string may leverage during the composition of the string.
   * Example:
   * - the intl string is defined as: "webmaptitle": "Web Map - {title}"
   * - the formatValues should be an object `{title: "California Fires"}`
   */
  formatValues?: Record<string, string>;
}

/** The bare minimum interface for layer information coming from the rest API. */
export interface LayerInfo {
  id: number;
  type: string;
  capabilities?: string;
}

export interface IExtendedPredefinedProfile extends IPredefinedProfile {
  additionalVariables?: IProfileVariable[];
}

/**
 * Represents the policy for an editor profile. The policy can influence specific configuration and loading behavior of the editor profile.
 *
 * @privateremarks Currently we use this to ignore specific layers in feature set variables for arcade profiles, but the concept is extensible to other configuration and loading behavior in the future.
 */
export interface IProfilePolicy {
  /**
   * A hook to filter layers to be included in a feature set collection
   * that source from a map
   *
   * @privateremarks
   * see https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/10715 for a use case.
   * In arcade we don't want to include certain layer types in the $map variable.
   */
  filterFsCollectionLayersFromMap?: (layer: Layer) => boolean;
}

export const profileStrings: (value: {
    layer: string;
    featureservice: string;
    mapservice: string;
    map: string;
    webmap: string;
    webscene: string;
    webmapformat: string;
    item: string;
    invoking: string;
    fields: string;
    values: string;
    relationships: string;
    layers: string;
    tables: string;
    geometry: string;
    dictionary: string;
    feature: string;
    featureset: string;
    featuresetcollection: string;
    oid: string;
    number: string;
    string: string;
    date: string;
    blob: string;
    raster: string;
    guid: string;
    "global-id": string;
    xml: string;
    function: string;
    domainvalues: string;
    subtypes: string;
    domainvaluesfortypeformat: string;
    domainvaluesbysubtypes: string;
}) => {
    layer: string;
    featureservice: string;
    mapservice: string;
    map: string;
    webmap: string;
    webscene: string;
    webmapformat: string;
    item: string;
    invoking: string;
    fields: string;
    values: string;
    relationships: string;
    layers: string;
    tables: string;
    geometry: string;
    dictionary: string;
    feature: string;
    featureset: string;
    featuresetcollection: string;
    oid: string;
    number: string;
    string: string;
    date: string;
    blob: string;
    raster: string;
    guid: string;
    "global-id": string;
    xml: string;
    function: string;
    domainvalues: string;
    subtypes: string;
    domainvaluesfortypeformat: string;
    domainvaluesbysubtypes: string;
};