import type Field from "@arcgis/core/layers/support/Field";
import type KnowledgeGraph from "@arcgis/core/rest/knowledgeGraph/KnowledgeGraph";
import type SpatialReference from "@arcgis/core/geometry/SpatialReference";
import type { IEditorProfileDefinition } from "./profile/types.js";
import type { ProfileVariableInstances, ArcadeServices } from "@arcgis/core/arcade";
import type { GeometryUnion } from "@arcgis/core/unionTypes";

/**
 * 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 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;
}

export interface IArcadeRuntimeDictionary {
  declaredClass?: string;
  arcadeDeclaredClass?: string;
  keys: () => string[];
  field: (name: string) => unknown;
  geometry?: () => GeometryUnion;
  castAsJson: () => Record<string, unknown>;
}

export interface IAbortSignal {
  aborted: boolean;
}

export type ResultType = "array" | "attachment" | "boolean" | "date" | "dateOnly" | "dictionary" | "error" | "feature" | "featureSet" | "geometry" | "knowledgeGraph" | "null" | "number" | "pixel" | "portal" | "text" | "time" | "unknown";

export interface IArcadeResultUnknown extends IArcadeResultBase {
  type: "unknown";
}

export interface IArcadeResultNull extends IArcadeResultBase {
  type: "null";
}

export interface IArcadeResultError extends IArcadeResultBase {
  type: "error";
  value: string;
  error: unknown;
}

export interface IArcadeResultArray extends IArcadeResultBase {
  type: "array";
  value: unknown[];
}

export interface IArcadeResultDate extends IArcadeResultBase {
  type: "date";
  value: Date | IArcadeRuntimeDate;
}

export interface IArcadeResultDateOnly extends IArcadeResultBase {
  type: "dateOnly";
  value: IArcadeRuntimeDateOnly;
}

export interface IArcadeResultTimeOnly extends IArcadeResultBase {
  type: "time";
  value: IArcadeRuntimeTimeOnly;
}

export interface IArcadeResultString extends IArcadeResultBase {
  type: "text";
  value: string;
}

export interface IArcadeResultNumber extends IArcadeResultBase {
  type: "number";
  value: number;
}

export interface IArcadeResultBoolean extends IArcadeResultBase {
  type: "boolean";
  value: boolean;
}

export interface IArcadeResultDictionary extends IArcadeResultBase {
  type: "dictionary";
  value: IArcadeRuntimeDictionary;
}

export interface IArcadeResultGeometry extends IArcadeResultBase {
  type: "geometry";
  value: GeometryUnion;
}

export interface IArcadeResultKnowledgeGraph extends IArcadeResultBase {
  type: "knowledgeGraph";
  value: KnowledgeGraph;
}

export interface IArcadeResultFeature extends IArcadeResultBase {
  type: "feature";
  value: IArcadeRuntimeDictionary;
}

export interface IArcadeResultPixel extends IArcadeResultBase {
  type: "pixel";
  value: IArcadeRuntimeDictionary;
}

export interface IArcadeResultFeatureSet extends IArcadeResultBase {
  type: "featureSet";
  value: IArcadeRuntimeFeatureSet;
  iterator: IArcadeRuntimeFeatureSetIterator | null;
  features: IArcadeRuntimeDictionary[];
}

export interface IArcadeResultPortal extends IArcadeResultBase {
  type: "portal";
  value: IArcadeRuntimeDictionary;
}

export interface IArcadeResultAttachment extends IArcadeResultBase {
  type: "attachment";
  value: IArcadeRuntimeDictionary;
}

export type ArcadeResult = IArcadeResultArray | IArcadeResultAttachment | IArcadeResultBoolean | IArcadeResultDate | IArcadeResultDateOnly | IArcadeResultDictionary | IArcadeResultError | IArcadeResultFeature | IArcadeResultFeatureSet | IArcadeResultGeometry | IArcadeResultKnowledgeGraph | IArcadeResultNull | IArcadeResultNumber | IArcadeResultPixel | IArcadeResultPortal | IArcadeResultString | IArcadeResultTimeOnly | IArcadeResultUnknown;

/** @param value */
export function valueToArcadeResult(value: unknown): ArcadeResult;

/**
 * @param dateObject
 * @param lang
 */
export function formatArcadeResultDate(dateObject: IArcadeResultDate, lang?: string): string;

/** @param dateObject */
export function formatArcadeResultDateOnly(dateObject: IArcadeResultDateOnly): string;

/** @param dateObject */
export function formatArcadeResultTimeOnly(dateObject: IArcadeResultTimeOnly): string;

/**
 * @param editorProfile
 * @param script
 * @param testContext
 * @param console
 */
export function executeScript(editorProfile: IEditorProfileDefinition | undefined, script: string | undefined, testContext: IEditorTestContext, console?: (log: string) => void): Promise<ArcadeResult>;

export const exportsForTests: { convertScriptOutputToArcadeResult: (scriptOutput: unknown, timeZone?: string) => Promise<ArcadeResult>; };

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

export interface IArcadeRuntimeFeatureSet {
  declaredRootClass: string;
  fields: Field[] | null;
  load: () => Promise<IArcadeRuntimeFeatureSet>;
  iterator: (abortSignal: IAbortSignal) => IArcadeRuntimeFeatureSetIterator;
}

export interface IArcadeRuntimeFeatureSetIterator {
  nextBatchAsArcadeFeatures: (bsize: number, timeZone: string) => Promise<IArcadeRuntimeDictionary[] | null>;
}

export interface IArcadeRuntimeDate {
  declaredRootClass: string;
  toDateTime: () => { setLocale: (locale: string) => { toFormat: (format: string) => string; }; };
  isUnknownTimeZone: boolean;
}

export interface IArcadeRuntimeDateOnly {
  declaredRootClass: string;
  /** To ISO Date format */
  toString: () => string;
}

export interface IArcadeRuntimeTimeOnly {
  declaredRootClass: string;
  /** To ISO Time format */
  toString: () => string;
}

export interface IArcadeResultBase {
  type: ResultType;
  timeStamp?: Date;
  compilationTime?: number;
  executionTime?: number;
}