import { bindEvents } from './ui_utils.js'

export class PDFAnnotationEditor {
  constructor (options) {
    this.boundEditorDivBlur = this.editorDivBlur.bind(this);
  
    this.boundEditorDivFocus = this.editorDivFocus.bind(this);
  
    this.boundFocusin = this.focusin.bind(this);

    this.boundFocusout = this.focusout.bind(this);
    this._isEditing = false
    this.boundEditorDivInput = this.editorDivInput.bind(this);
    this.isAttachedToDOM = false;
    this.hasBeenSelected = false
    this.parent = options.parent
    this.id = options.id;
    this.width = this.height = null;
    this.pageDiv = options.pageDiv
    this.pageIndex = options.parent.pageIndex;
    this.name = options.name;
    this.div = null
    this.hasAlreadyBeenCommitted = false
    this.annotationObj = null

    const [width, height] = this.parent.viewportBaseDimensions;
    this.x = options.x / width;
    this.y = options.y / height;
    this.rotation = this.parent.viewport.rotation;

    this.isAttachedToDOM = false;
    this.content = ""
  }

  rectCalc (
    position,
    viewHeight,
    scale
  ) {
    return {
      top: viewHeight - position.top * scale,
      left: position.left * scale,
      width: (position.right - position.left) * scale,
      height: (position.top - position.bottom) * scale,
    }
  }

  editorDivBlur(event) {
    this.isEditing = false;
  }
  editorDivFocus(event) {
    this.isEditing = true;
  }

  editorDivInput(event) {
    this.parent.div.classList.toggle("freeTextEditing", this.isEmpty());
  }

  /** @inheritdoc */
  isEmpty() {
    return !this.editorDiv || this.editorDiv.innerText.trim() === "";
  }

  commit() {
    this.parent.addToAnnotationStorage(this);
    if (!this.hasAlreadyBeenCommitted) {
      // This editor has something and it's the first time
      // it's commited so we can add it in the undo/redo stack.
      this.hasAlreadyBeenCommitted = true;
    }

    this.disableEditMode();
    this.content = this.extractText().trimEnd();
  }

  extractText() {
    const divs = this.editorDiv.getElementsByTagName("div");
    if (divs.length === 0) {
      return this.editorDiv.innerText;
    }
    const buffer = [];
    for (const div of divs) {
      const first = div.firstChild;
      if (first?.nodeName === "#text") {
        buffer.push(first.data);
      } else {
        buffer.push("");
      }
    }
    return buffer.join("\n");
  }

  render() {
    if (this.div) {
      return this.div;
    }
    this.div = document.createElement("div");
    this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
    this.div.setAttribute("tabIndex", 0);

    bindEvents(this, this.div, ["dragstart"]);

    if (this.annotation) {
      const annotRect = this.rectCalc(position, this.parent.viewport.height, scale);
      this.div.style.top = annotRect.top + 'px'
      this.div.style.left = annotRect.left + 'px'
    } else {
      const [tx, ty] = this.getInitialTranslation();
      this.translate(tx, ty);
    }
    this.div.classList = "icon-wrapper"
    this.div.innerHTML= `<svg data-status="normal" width="32" height="32" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><path d="M0 0h32v32H0z"></path><path d="M28 5.333V24H14.667L8 28v-4H4V5.333h24zM18.667 18H9.333v1.333h9.334V18zm4-4H9.333v1.333h13.334V14zm0-4H9.333v1.333h13.334V10z" fill="#BE0C0E"></path></g></svg>`
    if (!this.pageDiv) {
      this.pageDiv = this.parent.createWrapper()
    }
    this.pageDiv.append(this.div)
    this.editorDiv = document.createElement("div");
    this.editorDiv.className = "editor-container";

    this.editorDiv.setAttribute("id", this.id);
    this.editorDiv.addEventListener("focusin", this.boundFocusin);
    this.editorDiv.addEventListener("focusout", this.boundFocusout);

    this.editorDiv.contentEditable = true;

    const { style } = this.editorDiv;
    if (this.annotation) {
      const annotRect = this.rectCalc(position, this.parent.viewport.height, scale);
      style.top = annotRect.top + 36 + 'px'
      style.left = annotRect.left - 106 + 'px'
    } else {
      style.top = `calc(${100 * this.y}% + 36px`
      style.left = `calc(${100 * this.x}% - 134px)`
    }

    this.pageDiv.append(this.editorDiv);

    // TODO: implement paste callback.
    // The goal is to sanitize and have something suitable for this
    // editor.
    bindEvents(this, this.div, ["dblclick"]);

    for (const line of this.content.split("\n")) {
      const div = document.createElement("div");
      div.append(
        line ? document.createTextNode(line) : document.createElement("br")
      );
      this.editorDiv.append(div);
    }

    this.div.draggable = true;
    this.editorDiv.contentEditable = false;
  }

  /** @inheritdoc */
  getInitialTranslation() {
    // The start of the base line is where the user clicked.
    return [0, 0];
  }

  /**
   * onfocus callback.
   */
  focusin(event) {
    if (!this.hasBeenSelected) {
      this.parent.setSelected(this);
    } else {
      this.hasBeenSelected = false;
    }
  }

  /**
   * onblur callback.
   * @param {FocusEvent} event
   */
  focusout(event) {
    if (!this.isAttachedToDOM) {
      return;
    }

    // In case of focusout, the relatedTarget is the element which
    // is grabbing the focus.
    // So if the related target is an element under the div for this
    // editor, then the editor isn't unactive.
    const target = event.relatedTarget;
    event.preventDefault();

    this.commitOrRemove();
  }
  
  commitOrRemove() {
    if (this.isEmpty()) {
      this.remove();
    } else {
      this.commit();
    }
  }

  /**
   * Select this editor.
   */
  select() {
    this.div?.classList.add("selectedEditor");
  }

  /**
   * Unselect this editor.
   */
  unselect() {
    this.div?.classList.remove("selectedEditor");
  }

  /**
   * Remove this editor.
   * It's used on ctrl+backspace action.
   */
  remove() {
    this.div.removeEventListener("focusin", this.boundFocusin);
    this.div.removeEventListener("focusout", this.boundFocusout);

    if (!this.isEmpty()) {
      // The editor is removed but it can be back at some point thanks to
      // undo/redo so we must commit it before.
      this.commit();
    }
    this.parent.remove(this);
  }
  
  /**
   * Translate the editor position within its parent.
   * @param {number} x - x-translation in screen coordinates.
   * @param {number} y - y-translation in screen coordinates.
   */
   translate(x, y) {
    const [width, height] = this.parent.viewportBaseDimensions;
    [x, y] = this.screenToPageTranslation(x, y);

    this.x += x / width;
    this.y += y / height;

    this.div.style.left = `${100 * this.x}%`;
    this.div.style.top = `${100 * this.y}%`;
  }

  /**
   * Convert a screen translation into a page one.
   * @param {number} x
   * @param {number} y
   */
  screenToPageTranslation(x, y) {
    const { rotation } = this.parent.viewport;
    switch (rotation) {
      case 90:
        return [y, -x];
      case 180:
        return [-x, -y];
      case 270:
        return [-y, x];
      default:
        return [x, y];
    }
  }

  onceAdded() {
    if (this.width) {
      // The editor was created in using ctrl+c.
      return;
    }
    this.enableEditMode();
    this.editorDiv.focus();
  }

  dblclick(event) {
    this.enableEditMode();
    this.editorDiv.focus();
  }

  /**
   * We use drag-and-drop in order to move an editor on a page.
   * @param {DragEvent} event
   */
  dragstart(event) {
    const rect = this.parent.div.getBoundingClientRect();
    this.startX = event.clientX - rect.x;
    this.startY = event.clientY - rect.y;
    event.dataTransfer.setData("text/plain", this.id);
    event.dataTransfer.effectAllowed = "move";
  }

  /** @inheritdoc */
  enableEditMode() {
    if (this.isInEditMode) {
      return;
    }

    this.isInEditMode = true;
    this.overlayDiv?.classList.remove("enabled");
    this.editorDiv.contentEditable = true;
    this.editorDiv.style.display = 'initial';
    this.div.draggable = false;
    this.div.removeAttribute("aria-activedescendant");
    this.editorDiv.addEventListener("focus", this.boundEditorDivFocus);
    this.editorDiv.addEventListener("blur", this.boundEditorDivBlur);
    this.editorDiv.addEventListener("input", this.boundEditorDivInput);
  }

  /** @inheritdoc */
  disableEditMode() {
    if (!this.isInEditMode) {
      return;
    }

    this.isInEditMode = false;
    this.editorDiv.contentEditable = false;
    this.div.draggable = true;
    this.editorDiv.removeEventListener("focus", this.boundEditorDivFocus);
    this.editorDiv.removeEventListener("blur", this.boundEditorDivBlur);
    this.editorDiv.removeEventListener("input", this.boundEditorDivInput);
    this.editorDiv.style.display = "none"
    // On Chrome, the focus is given to <body> when contentEditable is set to
    // false, hence we focus the div.
    this.div.focus();

    // In case the blur callback hasn't been called.
    this.isEditing = false;
    this.parent.div.classList.add("freeTextEditing");
  }

  /**
   * If true then the editor is currently edited.
   * @type {boolean}
   */
   get isEditing() {
    return this._isEditing;
  }

  /**
   * When set to true, it means that this editor is currently edited.
   * @param {boolean} value
   */
  set isEditing(value) {
    this._isEditing = value;
    if (value) {
      this.parent.setSelected(this);
      this.parent.setActiveEditor(this);
    } else {
      this.parent.setActiveEditor(null);
    }
  }
}
