import { v4 as uuidv4 } from 'uuid';
import { bindEvents } from "./ui_utils.js";
import { PDFMeasureEditor } from "./pdf_measure_editor.js";

class PDFMeasureLayer {

  constructor(options) {
    this.allowClick = false;
    this.boundPointerup = this.pointerup.bind(this);
    this.boundPointerdown = this.pointerdown.bind(this);
    this.editors = new Map();
    this.hadPointerDown = false;
    this.isCleaningUp = false;
    this.uiManager = options.uiManager
    this.viewport = options.viewport

    this.annotationStorage = options.annotationStorage;
    this.pageIndex = options.pageIndex;
    this.pageDiv = options.pageDiv;
    this.div = null;
    this.render({
      annotations: options.annotations,
      viewport: this.viewport,
    })
    this.uiManager.addLayer(this)
  }

  /**
   * The mode has changed: it must be updated.
   * @param {number} mode
   */
  updateMode() {
    // this.cleanup();
    this.enableClick();
  }

  /**
   * Enable pointer events on the main div in order to enable
   * editor creation.
   */
  enable() {
    this.div.style.pointerEvents = "auto";
    for (const editor of this.editors.values()) {
      editor.enableEditing();
    }
  }

  /**
   * Disable editor creation.
   */
  disable() {
    this.div.style.pointerEvents = "none";
    for (const editor of this.editors.values()) {
      editor.disableEditing();
    }
  }

  /**
   * Set the current editor.
   * @param {AnnotationEditor} editor
   */
   setActiveEditor(editor) {
    const currentActive = this.uiManager.getActive();
    if (currentActive === editor) {
      return;
    }

    this.uiManager.setActiveEditor(editor);
  }

  enableClick() {
    this.canvas.addEventListener("pointerdown", this.boundPointerdown);
    this.canvas.addEventListener("pointerup", this.boundPointerup);
  }

  disableClick() {
    this.canvas.removeEventListener("pointerdown", this.boundPointerdown);
    this.canvas.removeEventListener("pointerup", this.boundPointerup);
  }

  /**
   * Remove an editor.
   * @param {AnnotationEditor} editor
   */
  remove(editor) {
    // Since we can undo a removal we need to keep the
    // parent property as it is, so don't null it!

    this.annotationStorage.remove(editor.id);
    editor.div.style.display = "none";
    setTimeout(() => {
      // When the div is removed from DOM the focus can move on the
      // document.body, so we just slightly postpone the removal in
      // order to let an element potentially grab the focus before
      // the body.
      editor.div.style.display = "";
      editor.div.remove();
      editor.editorDiv.remove();
      editor.isAttachedToDOM = false;
    }, 0);
  }
  
  /**
   * Set the last selected editor.
   * @param {AnnotationEditor} editor
   */
  setSelected(editor) {
    this.uiManager.setSelected(editor);
  }

  /**
   * Add a new editor in the current view.
   * @param {AnnotationEditor} editor
   */
  add(editor) {
    if (!editor.isAttachedToDOM) {
      editor.render();
      editor.isAttachedToDOM = true;
    }

    editor.onceAdded();
    this.addToAnnotationStorage(editor);
  }

  /**
   * Add an editor in the annotation storage.
   * @param {AnnotationEditor} editor
   */
  addToAnnotationStorage(editor) {
    if (!editor.isEmpty() && !this.annotationStorage.has(editor.id)) {
      this.annotationStorage.setValue(editor.id, editor);
    }
  }

  /**
   * Get an id for an editor.
   * @returns {string}
   */
  getUuid() {
    return uuidv4();
  }

  /**
   * Create a new editor
   * @param {Object} params
   * @returns {AnnotationEditor}
   */
  createNewEditor(params) {
    return new PDFAnnotationEditor(params);
  }

  /**
   * Create and add a new editor.
   * @param {PointerEvent} event
   * @returns {AnnotationEditor}
   */
  createAndAddNewEditor(event) {
    const id = this.getUuid();
    const editor = this.createNewEditor({
      parent: this,
      pageDiv: this.div,
      id,
      x: event.offsetX,
      y: event.offsetY,
    });
    if (editor) {
      this.add(editor);
    }

    return editor;
  }

  /**
   * Pointerup callback.
   * @param {PointerEvent} event
   */
  pointerup(event) {
    if (event.target !== this.div) {
      return;
    }

    if (!this.hadPointerDown) {
      // It can happen when the user starts a drag inside a text editor
      // and then releases the mouse button outside of it. In such a case
      // we don't want to create a new editor, hence we check that a pointerdown
      // occured on this div previously.
      return;
    }
    this.hadPointerDown = false;

    if (!this.allowClick) {
      this.allowClick = true;
      return;
    }

    this.createAndAddNewEditor(event);
  }

  /**
   * Pointerdown callback.
   * @param {PointerEvent} event
   */
  pointerdown(event) {
    if (event.target !== this.div) {
      return;
    }

    this.hadPointerDown = true;
    this.allowClick = true
    const editor = this.uiManager.getActive();
    this.allowClick = !editor || editor.isEmpty();
  }

  /**
   * Drag callback.
   * @param {DragEvent} event
   */
  drop(event) {
    const id = event.dataTransfer.getData("text/plain");
    const editor = this.uiManager.getEditor(id);
    if (!editor) {
      return;
    }

    event.preventDefault();
    event.dataTransfer.dropEffect = "move";

    const rect = this.div.getBoundingClientRect();
    const endX = event.clientX - rect.x;
    const endY = event.clientY - rect.y;

    editor.translate(endX - editor.startX, endY - editor.startY);
    editor.div.focus();
  }

  /**
   * Dragover callback.
   * @param {DragEvent} event
   */
  dragover(event) {
    event.preventDefault();
  }

  /**
   * Destroy the main editor.
   */
  destroy() {
    for (const editor of this.editors.values()) {
      editor.isAttachedToDOM = false;
      editor.div.remove();
      editor.parent = null;
    }
    this.div = null;
    this.editors.clear();
    this.uiManager.removeLayer(this);
  }

  cleanup() {
    // When we're cleaning up, some editors are removed but we don't want
    // to add a new one which will induce an addition in this.editors, hence
    // an infinite loop.
    this.isCleaningUp = true;
    for (const editor of this.editors.values()) {
      if (editor.isEmpty()) {
        editor.remove();
      }
    }
    this.isCleaningUp = false;
  }

  /**
   * Render the main editor.
   * @param {Object} parameters
   */
  render(parameters) {
    this.div = document.createElement("div");
    this.div.className = "annotationLayer";
    this.div.tabIndex = 0;
    this.pageDiv.append(this.div);
    this.viewport = parameters.viewport;
    bindEvents(this, this.div, ["dragover", "drop"]);
    this.setDimensions();
    for (const editor of this.getEditors(this.pageIndex, parameters.annotations)) {
      this.add(editor);
    }
    this.updateMode();
  }

  getEditors(pageIndex, annotations) {
    const editors = [];
    if (!annotations) {
      return [];
    }
    for (const annotation of annotations) {
      if (annotation.obj_attr.page === pageIndex) {
        const editor = this.createNewEditor({
          parent: this,
          id: annotation.id,
          pageDiv: this.div,
          annotationObj: annotation
        })
        editors.push(editor);
      }
    }
    return editors;
  }

  /**
   * Update the main editor.
   * @param {Object} parameters
   */
  update(parameters) {
    // Editors have their dimensions/positions in percent so to avoid any
    // issues (see 15582), we must commit the current one before changing
    // the viewport.
    this.uiManager.commitOrRemove();

    this.viewport = parameters.viewport;
    this.setDimensions();
    this.updateMode();
  }

  /**
   * Get the scale factor from the viewport.
   * @returns {number}
   */
  get scaleFactor() {
    return this.viewport.scale;
  }

  /**
   * Get page dimensions.
   * @returns {Object} dimensions.
   */
  get pageDimensions() {
    const [pageLLx, pageLLy, pageURx, pageURy] = this.viewport.viewBox;
    const width = pageURx - pageLLx;
    const height = pageURy - pageLLy;

    return [width, height];
  }

  get viewportBaseDimensions() {
    const { width, height, rotation } = this.viewport;
    return rotation % 180 === 0 ? [width, height] : [height, width];
  }

  /**
   * Set the dimensions of the main div.
   */
  setDimensions() {
    const { width, height, rotation } = this.viewport;

    const flipOrientation = rotation % 180 !== 0,
      widthStr = Math.floor(width) + "px",
      heightStr = Math.floor(height) + "px";

    this.div.style.width = flipOrientation ? heightStr : widthStr;
    this.div.style.height = flipOrientation ? widthStr : heightStr;
    this.div.setAttribute("data-main-rotation", rotation);
  }
}

export { PDFMeasureLayer };
