import { FIGURE_ID_SPLITTER } from "../../constants";
import { deepCopy } from "../../helpers/misc";
import { CommandResult, CoreCommand } from "../../types/commands";
import { FigureSize } from "../../types/figure";
import { FileStore } from "../../types/files";
import { Image } from "../../types/image";
import { HeaderIndex, PixelPosition, UID } from "../../types/misc";
import { DOMDimension } from "../../types/rendering";
import { ExcelWorkbookData, FigureData, WorkbookData } from "../../types/workbook_data";
import { CorePlugin, CorePluginConfig } from "../core_plugin";

interface ImageState {
  readonly images: Record<UID, Record<UID, Image | undefined> | undefined>;
}

export class ImagePlugin extends CorePlugin<ImageState> implements ImageState {
  static getters = ["getImage", "getImagePath", "getImageSize", "getImageFile"] as const;
  readonly fileStore?: FileStore;
  readonly images: Record<UID, Record<UID, Image | undefined> | undefined> = {};
  /**
   * paths of images synced with the file store server.
   */
  readonly syncedImages: Set<Image["path"]> = new Set();

  constructor(config: CorePluginConfig) {
    super(config);
    this.fileStore = config.external.fileStore;
  }

  // ---------------------------------------------------------------------------
  // Command Handling
  // ---------------------------------------------------------------------------

  allowDispatch(cmd: CoreCommand) {
    switch (cmd.type) {
      case "CREATE_IMAGE":
        if (this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
          return CommandResult.InvalidFigureId;
        }
        return CommandResult.Success;
      default:
        return CommandResult.Success;
    }
  }

  handle(cmd: CoreCommand) {
    switch (cmd.type) {
      case "CREATE_IMAGE":
        if (!this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
          this.addFigure(cmd.figureId, cmd.sheetId, cmd.col, cmd.row, cmd.offset, cmd.size);
        }
        this.history.update("images", cmd.sheetId, cmd.figureId, cmd.definition);
        this.syncedImages.add(cmd.definition.path);
        break;
      case "DUPLICATE_SHEET": {
        const sheetFiguresFrom = this.getters.getFigures(cmd.sheetId);
        for (const fig of sheetFiguresFrom) {
          if (fig.tag === "image") {
            const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
            const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
            const image = this.getImage(fig.id);
            if (image) {
              const size = { width: fig.width, height: fig.height };
              this.dispatch("CREATE_IMAGE", {
                sheetId: cmd.sheetIdTo,
                figureId: duplicatedFigureId,
                offset: fig.offset,
                col: fig.col,
                row: fig.row,
                size,
                definition: deepCopy(image),
              });
            }
          }
        }
        break;
      }
      case "DELETE_FIGURE":
        this.history.update("images", cmd.sheetId, cmd.figureId, undefined);
        break;
      case "DELETE_SHEET":
        this.history.update("images", cmd.sheetId, undefined);
        break;
    }
  }

  // ---------------------------------------------------------------------------
  // Getters
  // ---------------------------------------------------------------------------

  getImage(figureId: UID): Image {
    for (const sheet of Object.values(this.images)) {
      if (sheet && sheet[figureId]) {
        return sheet[figureId]!;
      }
    }
    throw new Error(`There is no image with the given figureId: ${figureId}`);
  }

  getImagePath(figureId: UID): string {
    return this.getImage(figureId).path;
  }

  getImageSize(figureId: UID): FigureSize {
    return this.getImage(figureId).size;
  }

  async getImageFile(figureId: UID) {
    return this.fileStore?.getFile(this.getImagePath(figureId));
  }

  // ---------------------------------------------------------------------------
  // Private
  // ---------------------------------------------------------------------------

  private addFigure(
    figureId: UID,
    sheetId: UID,
    col: HeaderIndex,
    row: HeaderIndex,
    offset: PixelPosition,
    size: DOMDimension
  ) {
    this.dispatch("CREATE_FIGURE", { sheetId, figureId, col, row, offset, size, tag: "image" });
  }

  import(data: WorkbookData) {
    for (const sheet of data.sheets) {
      const images = (sheet.figures || []).filter((figure) => figure.tag === "image");
      for (const image of images) {
        this.history.update("images", sheet.id, image.id, image.data);
        this.syncedImages.add(image.data.path);
      }
    }
  }

  export(data: WorkbookData) {
    for (const sheet of data.sheets) {
      const images = sheet.figures.filter((figure) => figure.tag === "image");
      for (const image of images) {
        image.data = this.images[sheet.id]?.[image.id];
      }
    }
  }

  exportForExcel(data: ExcelWorkbookData) {
    for (const sheet of data.sheets) {
      if (!sheet.images) {
        sheet.images = [];
      }
      const figures = this.getters.getFigures(sheet.id);
      const images: FigureData<Image>[] = [];
      for (const figure of figures) {
        if (figure?.tag === "image") {
          const image = this.getImage(figure.id);
          if (image) {
            images.push({
              ...figure,
              data: deepCopy(image),
            });
          }
        }
      }
      sheet.images = [...sheet.images, ...images];
    }
  }
}
