import { CellValue } from "./cells";
import { EvaluationGetters } from "./getters";
import { Locale } from "./locale";
import { Arg, CellPosition, FunctionResultObject, Matrix, UID } from "./misc";
import { Range } from "./range";

export type ArgType =
  | "ANY"
  | "BOOLEAN"
  | "NUMBER"
  | "STRING"
  | "DATE"
  | "RANGE"
  | "RANGE<BOOLEAN>"
  | "RANGE<NUMBER>"
  | "RANGE<DATE>"
  | "RANGE<STRING>"
  | "RANGE<ANY>";

export interface ArgDefinition {
  acceptMatrix?: boolean;
  acceptMatrixOnly?: boolean;
  acceptErrors?: boolean;
  repeating?: boolean;
  optional?: boolean;
  description: string;
  name: string;
  type: ArgType[];
  default?: boolean;
  defaultValue?: any;
  proposalValues?: ArgProposal[];
}

export type ArgProposal = { value: CellValue; label?: string };

export type ComputeFunction = (
  ctx: EvalContext,
  ...args: Arg[]
) => FunctionResultObject | Matrix<FunctionResultObject>;

export type BaseFunctionDescription = {
  description: string;
  category?: string;
  args: ArgDefinition[];
  isExported?: boolean;
  hidden?: boolean;
};

type ScalarComputeFunction = (this: EvalContext, ...args: Arg[]) => FunctionResultObject;

type ArrayComputeFunction = (
  this: EvalContext,
  ...args: Arg[]
) => FunctionResultObject | Matrix<FunctionResultObject>;

type ComputeVariant =
  | { compute: ScalarComputeFunction; computeArray?: never }
  | { compute?: never; computeArray: ArrayComputeFunction };

export type AddFunctionDescription = BaseFunctionDescription & ComputeVariant;

export type FunctionDescription = AddFunctionDescription & {
  name: string;
  minArgRequired: number;
  maxArgPossible: number;
  nbrArgRepeating: number;
  nbrOptionalNonRepeatingArgs: number;
};

export type FunctionTimingEntry = { functionName: string; position: CellPosition; time: number };

export type FunctionTimingLog = FunctionTimingEntry[];

export interface RangeTiming {
  functionName: string;
  range: Range;
  time: number;
}

export interface PerfProfile {
  totalTime: number;
  totalCells: number;
  totalFunctionCalls: number;
  entries: RangeTiming[];
}

export type EvalContext = {
  __originSheetId: UID;
  __originCellPosition?: CellPosition;
  __timingEntries?: FunctionTimingLog;
  locale: Locale;
  getters: EvaluationGetters;
  getFormulaResult: (position: CellPosition) => FunctionResultObject;
  [key: string]: any;
  updateDependencies?: (position: CellPosition) => void;
  addDependencies?: (position: CellPosition, ranges: Range[]) => void;
  debug?: boolean;
  lookupCaches?: LookupCaches;
};

/**
 * used to cache lookup values for linear search
 **/
export type LookupCaches = {
  forwardSearch: Map<unknown, Map<CellValue, number>>;
  reverseSearch: Map<unknown, Map<CellValue, number>>;
};
