import { getPasteZones } from "../helpers/clipboard/clipboard_helpers";
import { formatValue } from "../helpers/format/format";
import { canonicalizeNumberValue } from "../helpers/locale";
import { deepEquals } from "../helpers/misc";
import { createPivotFormula } from "../helpers/pivot/pivot_helpers";
import { cellPositions, isZoneInside } from "../helpers/zones";
import {
  ClipboardCellData,
  ClipboardCopyOptions,
  ClipboardOptions,
  ClipboardPasteTarget,
} from "../types/clipboard";
import { CommandResult } from "../types/commands";
import { CellPosition, ClipboardCell, HeaderIndex, UID, Zone } from "../types/misc";
import { AbstractCellClipboardHandler } from "./abstract_cell_clipboard_handler";

interface ClipboardContent {
  cells: ClipboardCell[][];
  zones: Zone[];
  sheetId: UID;
}

export class CellClipboardHandler extends AbstractCellClipboardHandler<
  ClipboardContent,
  ClipboardCell
> {
  isCutAllowed(data: ClipboardCellData) {
    if (data.zones.length !== 1) {
      return CommandResult.WrongCutSelection;
    }
    return CommandResult.Success;
  }

  copy(
    data: ClipboardCellData,
    isCutOperation: boolean,
    mode: ClipboardCopyOptions = "copyPaste"
  ): ClipboardContent | undefined {
    const sheetId = data.sheetId;
    const { clippedZones, rowsIndexes, columnsIndexes } = data;
    const clippedCells: ClipboardCell[][] = [];
    const isCopyingOneCell = rowsIndexes.length === 1 && columnsIndexes.length === 1;
    for (const row of rowsIndexes) {
      const cellsInRow: ClipboardCell[] = [];
      for (const col of columnsIndexes) {
        const position = { col, row, sheetId };
        let cell = this.getters.getCell(position);
        const evaluatedCell = this.getters.getEvaluatedCell(position);
        const pivotId = this.getters.getPivotIdFromPosition(position);
        const spreader = this.getters.getArrayFormulaSpreadingOn(position);
        if (mode !== "shiftCells" && pivotId && spreader) {
          const pivotZone = this.getters.getSpreadZone(spreader);
          if (
            (!deepEquals(spreader, position) || !isCopyingOneCell) &&
            pivotZone &&
            !data.zones.some((z) => isZoneInside(pivotZone, z))
          ) {
            const pivotCell = this.getters.getPivotCellFromPosition(position);
            const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
            const pivotFormula = createPivotFormula(formulaPivotId, pivotCell);
            cell = {
              id: cell?.id ?? 0,
              style: cell?.style,
              format: cell?.format,
              content: pivotFormula,
              isFormula: false,
              parsedValue: evaluatedCell.value,
            };
          }
        } else if (mode !== "shiftCells" && spreader && !deepEquals(spreader, position)) {
          const isSpreaderCopied =
            rowsIndexes.includes(spreader.row) && columnsIndexes.includes(spreader.col);
          const content = isSpreaderCopied
            ? ""
            : formatValue(evaluatedCell.value, { locale: this.getters.getLocale() });
          cell = {
            id: cell?.id ?? 0,
            style: cell?.style,
            format: evaluatedCell.format,
            content,
            isFormula: false,
            parsedValue: evaluatedCell.value,
          };
        }
        cellsInRow.push({
          content: !cell?.isFormula
            ? cell?.content ?? ""
            : cell.compiledFormula.toFormulaString(this.getters),
          style: cell?.style,
          format: cell?.format,
          compiledFormula: cell?.isFormula ? cell?.compiledFormula : undefined,
          border: this.getters.getCellBorder(position) || undefined,
          evaluatedCell,
          position,
        });
      }
      clippedCells.push(cellsInRow);
    }

    return {
      cells: clippedCells,
      zones: clippedZones,
      sheetId: data.sheetId,
    };
  }

  isPasteAllowed(
    sheetId: UID,
    target: Zone[],
    content: ClipboardContent,
    clipboardOptions: ClipboardOptions
  ): CommandResult {
    if (!content.cells) {
      return CommandResult.Success;
    }
    if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
      // cannot paste only format or only value if the previous operation is a CUT
      return CommandResult.WrongPasteOption;
    }
    if (target.length > 1) {
      // cannot paste if we have a clipped zone larger than a cell and multiple
      // zones selected
      if (content.cells.length > 1 || content.cells[0].length > 1) {
        return CommandResult.WrongPasteSelection;
      }
    }
    const clipboardHeight = content.cells.length;
    const clipboardWidth = content.cells[0].length;
    for (const zone of getPasteZones(target, content.cells)) {
      if (this.getters.doesIntersectMerge(sheetId, zone)) {
        if (
          target.length > 1 ||
          !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
          clipboardHeight * clipboardWidth !== 1
        ) {
          return CommandResult.WillRemoveExistingMerge;
        }
      }
    }
    return CommandResult.Success;
  }

  /**
   * Paste the clipboard content in the given target
   */
  paste(target: ClipboardPasteTarget, content: ClipboardContent, options: ClipboardOptions) {
    const zones = target.zones;
    const sheetId = target.sheetId;
    if (!options.isCutOperation) {
      this.pasteFromCopy(sheetId, zones, content.cells, options);
    } else {
      this.pasteFromCut(sheetId, zones, content, options);
    }
  }

  getPasteTarget(
    sheetId: UID,
    target: Zone[],
    content: ClipboardContent,
    options?: ClipboardOptions
  ): ClipboardPasteTarget {
    const width = content.cells[0].length;
    const height = content.cells.length;
    if (options?.isCutOperation) {
      return {
        sheetId,
        zones: [
          {
            left: target[0].left,
            top: target[0].top,
            right: target[0].left + width - 1,
            bottom: target[0].top + height - 1,
          },
        ],
      };
    }
    if (width === 1 && height === 1) {
      return { zones: [], sheetId };
    }
    return { sheetId, zones: getPasteZones(target, content.cells) };
  }

  private pasteFromCut(
    sheetId: UID,
    target: Zone[],
    content: ClipboardContent,
    options?: ClipboardOptions
  ) {
    this.clearClippedZones(content);
    const selection = target[0];
    this.pasteZone(sheetId, selection.left, selection.top, content.cells, options);
  }

  /**
   * Clear the clipped zones: remove the cells content
   */
  private clearClippedZones(content: ClipboardContent) {
    for (const position of content.zones.flatMap((zone) => cellPositions(content.sheetId, zone))) {
      this.dispatch("UPDATE_CELL", {
        ...position,
        content: "",
      });
    }
  }

  pasteZone(
    sheetId: UID,
    col: HeaderIndex,
    row: HeaderIndex,
    cells: ClipboardCell[][],
    clipboardOptions?: ClipboardOptions
  ) {
    // then, perform the actual paste operation
    for (const [r, rowCells] of cells.entries()) {
      for (const [c, origin] of rowCells.entries()) {
        if (!origin) {
          continue;
        }
        const position = { col: col + c, row: row + r, sheetId };
        this.pasteCell(origin, position, clipboardOptions);
      }
    }
  }

  /**
   * Paste the cell at the given position to the target position
   */
  private pasteCell(
    origin: ClipboardCell,
    target: CellPosition,
    clipboardOption?: ClipboardOptions
  ) {
    const { sheetId, col, row } = target;
    const targetCell = this.getters.getCell(target);
    const targetEvaluatedCell = this.getters.getEvaluatedCell(target);
    const originFormat = origin.format || origin.evaluatedCell.format;
    // We know the target cell style is already edited by the default_clipboard
    const newStyle = { ...targetCell?.style, ...origin.style };
    const style = Object.keys(newStyle ?? {}).length === 0 ? undefined : newStyle;
    if (clipboardOption?.pasteOption === "asValue") {
      this.dispatch("UPDATE_CELL", {
        ...target,
        content: origin.evaluatedCell.value?.toString() || "",
      });
      return;
    }

    if (clipboardOption?.pasteOption === "onlyFormat") {
      this.dispatch("UPDATE_CELL", {
        ...target,
        style,
        format: originFormat ?? targetEvaluatedCell.format,
      });
      return;
    }

    let content = origin?.content;
    if (origin?.compiledFormula?.hasDependencies && !clipboardOption?.isCutOperation) {
      content = this.getters.getTranslatedCellFormula(
        sheetId,
        col - origin.position.col,
        row - origin.position.row,
        origin.compiledFormula
      );
    } else if (origin?.compiledFormula?.hasDependencies) {
      content = this.getters.getFormulaMovedInSheet(sheetId, origin.compiledFormula);
    }
    if (content !== "" || origin.format || style) {
      this.dispatch("UPDATE_CELL", {
        ...target,
        content,
        style,
        format: origin.format,
      });
    } else if (targetEvaluatedCell.type !== "empty") {
      this.dispatch("UPDATE_CELL", {
        content: "",
        ...target,
      });
    }
  }

  convertTextToClipboardData(text: string): ClipboardContent {
    const locale = this.getters.getLocale();
    const copiedData: any = {
      cells: [],
    };
    const values: string[][] = [];
    let rowLength = 0;
    for (const [i, row] of text.replace(/\r/g, "").split("\n").entries()) {
      values.push(row.split("\t"));
      if (values[i].length > rowLength) {
        rowLength = values[i].length;
      }
    }
    for (const row of values) {
      const cells: any[] = [];
      for (let i = 0; i < rowLength; i++) {
        const content = canonicalizeNumberValue(row[i] || "", locale);
        cells.push({
          content: content,
          evaluatedCell: {
            formattedValue: content,
          },
        });
      }
      copiedData.cells.push(cells);
    }
    return copiedData;
  }
}
