import { isZoneInside, positionToZone, zoneToXc } from "../helpers/zones";
import { _t } from "../translation";
import { CircularDependencyError, EvaluationError, InvalidReferenceError } from "../types/errors";
import { EvalContext } from "../types/functions";
import { CoreGetters } from "../types/getters";
import { FunctionResultObject, Maybe, UID } from "../types/misc";
import { PivotCoreMeasure } from "../types/pivot";
import { Range } from "../types/range";

/**
 * Get the pivot ID from the formula pivot ID.
 */
export function getPivotId(pivotFormulaId: string, getters: CoreGetters) {
  const pivotId = getters.getPivotId(pivotFormulaId);
  if (!pivotId) {
    throw new EvaluationError(_t('There is no pivot with id "%s"', pivotFormulaId));
  }
  return pivotId;
}

export function assertMeasureExist(pivotId: UID, measure: string, getters: CoreGetters) {
  const { measures } = getters.getPivotCoreDefinition(pivotId);
  if (!measures.find((m) => m.id === measure)) {
    const validMeasures = `(${measures.map((m) => m.id).join(", ")})`;
    throw new EvaluationError(
      _t(
        "The argument %s is not a valid measure. Here are the measures: %s",
        measure,
        validMeasures
      )
    );
  }
}

export function assertDomainLength(domain: Maybe<FunctionResultObject>[]) {
  if (domain.length % 2 !== 0) {
    throw new EvaluationError(_t("Function PIVOT takes an even number of arguments."));
  }
}

export function addPivotDependencies(
  evalContext: EvalContext,
  pivotId: UID,
  forMeasures: PivotCoreMeasure[]
) {
  //TODO This function can be very costly when used with PIVOT.VALUE and PIVOT.HEADER
  const dependencies: Range[] = [];
  const coreDefinition = evalContext.getters.getPivotCoreDefinition(pivotId);

  if (coreDefinition.type === "SPREADSHEET" && coreDefinition.dataSet) {
    const { sheetId, zone } = coreDefinition.dataSet;
    const xc = zoneToXc(zone);
    const range = evalContext.getters.getRangeFromSheetXC(sheetId, xc);
    if (range === undefined || range.invalidXc || range.invalidSheetName) {
      throw new InvalidReferenceError();
    }
    if (
      evalContext.__originCellPosition &&
      range.sheetId === evalContext.__originSheetId &&
      isZoneInside(positionToZone(evalContext.__originCellPosition), zone)
    ) {
      throw new CircularDependencyError();
    }
    dependencies.push(range);
  }

  for (const measure of forMeasures) {
    if (measure.computedBy) {
      dependencies.push(...evalContext.getters.getMeasureFullDependencies(pivotId, measure));
    }
  }
  const originPosition = evalContext.__originCellPosition;
  if (originPosition && dependencies.length) {
    // The following line is used to reset the dependencies of the cell, to avoid
    // keeping dependencies from previous evaluation of the PIVOT formula (i.e.
    // in case the reference has been changed).
    evalContext.updateDependencies?.(originPosition);
    evalContext.addDependencies?.(originPosition, dependencies);
  }
}
