import { bindEvents } from './ui_utils.js'
import { KeyboardManager } from "./tools.js";

class PDFAnnotation {

  static _zIndex = 1;
  static _defaultLineColor = "#ff0000"

  constructor (options) {
    this._boundFocusin = this.focusin.bind(this);
  
    this._boundFocusout = this.focusout.bind(this);
  
    this._hasBeenSelected = false;
  
    this._isEditing = false;
  
    this._isInEditMode = false;
    this.annotation = options.annotation || null;
    this.parent = options.parent
    this.id = options.id;
    this.width = this.height = null;
    this.pageIndex = options.parent.pageIndex;
    this.name = options.name;
    this.div = null;

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

    this.isAttachedToDOM = false;
  }

  /**
   * 设置zIndex在其他之下
   */
  setInBackground() {
    this.div.style.zIndex = 0;
  }

  /**
   * onfocus 事件触发函数
   */
  focusin() {
    if (!this._hasBeenSelected) {
      this.parent.setSelected(this);
    } else {
      this._hasBeenSelected = false;
    }
  }

  /**
   * onblur 事件触发函数
   * @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;
    if (target?.closest(`#${this.id}`)) {
      return;
    }

    event.preventDefault();

    if (!this.parent.isMultipleSelection) {
      this.commitOrRemove();
    }
    this.parent.unselect(this);
  }

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

  fixDims() {
    const { style } = this.div;
    const { height, width } = style;
    const widthPercent = width.endsWith("%");
    const heightPercent = height.endsWith("%");
    if (widthPercent && heightPercent) {
      return;
    }

    const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
    if (!widthPercent) {
      style.width = `${(100 * parseFloat(width)) / parentWidth}%`;
    }
    if (!heightPercent) {
      style.height = `${(100 * parseFloat(height)) / parentHeight}%`;
    }
  }

  /**
   * 提交注释数据
   */
   commit() {
    this.parent.addToAnnotationStorage(this);
  }

  /**
   * 移动注释
   * @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";
  }

  /**
   * 设置注释位置
   * @param {number} x
   * @param {number} y
   * @param {number} tx - x-translation in screen coordinates.
   * @param {number} ty - y-translation in screen coordinates.
   */
  setAt(x, y, tx, ty) {
    const [width, height] = this.parent.viewportBaseDimensions;
    [tx, ty] = this.screenToPageTranslation(tx, ty);

    this.x = (x + tx) / width;
    this.y = (y + ty) / height;

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

  /**
   * 转换基于父级的位置
   * @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];
    }
  }

  /**
   * 设置注释的尺寸
   * @param {number} width
   * @param {number} height
   */
  setDims(width, height) {
    const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
    this.div.style.width = `${(100 * width) / parentWidth}%`;
    this.div.style.height = `${(100 * height) / parentHeight}%`;
  }

  /**
   * Get the translation used to position this editor when it's created.
   * @returns {Array<number>}
   */
   getInitialTranslation() {
    return [0, 0];
  }

  /**
   * Render this editor in a div.
   * @returns {HTMLDivElement}
   */
  render() {
    this.div = document.createElement("div");
    this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
    this.div.className = this.name;
    this.div.setAttribute("id", this.id);
    this.div.setAttribute("tabIndex", 0);

    // this.setInForeground();

    this.div.addEventListener("focusin", this._boundFocusin);
    this.div.addEventListener("focusout", this._boundFocusout);

    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);
    }

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

    return this.div;
  }

  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,
    }
  }

  /**
   * Onpointerdown callback.
   * @param {PointerEvent} event
   */
   pointerdown(event) {
    const isMac = KeyboardManager.platform.isMac;
    if (event.button !== 0 || (event.ctrlKey && isMac)) {
      // Avoid to focus this editor because of a non-left click.
      event.preventDefault();
      return;
    }

    if (
      (event.ctrlKey && !isMac) ||
      event.shiftKey ||
      (event.metaKey && isMac)
    ) {
      this.parent.toggleSelected(this);
    } else {
      this.parent.setSelected(this);
    }

    this._hasBeenSelected = true;
  }

  getRect(tx, ty) {
    const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
    const [pageWidth, pageHeight] = this.parent.pageDimensions;
    const shiftX = (pageWidth * tx) / parentWidth;
    const shiftY = (pageHeight * ty) / parentHeight;
    const x = this.x * pageWidth;
    const y = this.y * pageHeight;
    const width = this.width * pageWidth;
    const height = this.height * pageHeight;

    switch (this.rotation) {
      case 0:
        return [
          x + shiftX,
          pageHeight - y - shiftY - height,
          x + shiftX + width,
          pageHeight - y - shiftY,
        ];
      case 90:
        return [
          x + shiftY,
          pageHeight - y + shiftX,
          x + shiftY + height,
          pageHeight - y + shiftX + width,
        ];
      case 180:
        return [
          x - shiftX - width,
          pageHeight - y + shiftY,
          x - shiftX,
          pageHeight - y + shiftY + height,
        ];
      case 270:
        return [
          x - shiftY - height,
          pageHeight - y - shiftX - width,
          x - shiftY,
          pageHeight - y - shiftX,
        ];
      default:
        throw new Error("Invalid rotation");
    }
  }

  getRectInCurrentCoords(rect, pageHeight) {
    const [x1, y1, x2, y2] = rect;

    const width = x2 - x1;
    const height = y2 - y1;

    switch (this.rotation) {
      case 0:
        return [x1, pageHeight - y2, width, height];
      case 90:
        return [x1, pageHeight - y1, height, width];
      case 180:
        return [x2, pageHeight - y1, width, height];
      case 270:
        return [x2, pageHeight - y2, height, width];
      default:
        throw new Error("Invalid rotation");
    }
  }

  /**
   * Enable edit mode.
   */
  enableEditMode() {
    this._isInEditMode = true;
  }

  /**
   * Disable edit mode.
   */
  disableEditMode() {
    this._isInEditMode = false;
  }

  /**
   * Check if the editor is edited.
   * @returns {boolean}
   */
  isInEditMode() {
    return this._isInEditMode;
  }

  /**
   * If it returns true, then this editor handle the keyboard
   * events itself.
   * @returns {boolean}
   */
  shouldGetKeyboardEvents() {
    return false;
  }

  /**
   * Check if this editor needs to be rebuilt or not.
   * @returns {boolean}
   */
  needsToBeRebuilt() {
    return this.div && !this.isAttachedToDOM;
  }

  /**
   * Rebuild the editor in case it has been removed on undo.
   *
   * To implement in subclasses.
   */
  rebuild() {
    this.div?.addEventListener("focusin", this._boundFocusin);
  }

  /**
   * 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);
  }

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

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

  /**
   * Update some parameters which have been changed through the UI.
   * @param {number} type
   * @param {*} value
   */
  updateParams(type, value) {}

  /**
   * When the user disables the editing mode some editors can change some of
   * their properties.
   */
  disableEditing() {}

  /**
   * When the user enables the editing mode some editors can change some of
   * their properties.
   */
  enableEditing() {}

  /**
   * Get some properties to update in the UI.
   * @returns {Object}
   */
  get propertiesToUpdate() {
    return {};
  }

  /**
   * Get the div which really contains the displayed content.
   */
  get contentDiv() {
    return this.div;
  }

  /**
   * 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);
    }
  }
  /**
   * Deserialize the editor.
   * The result of the deserialization is a new editor.
   *
   * @param {Object} data
   * @param {AnnotationEditorLayer} parent
   * @returns {AnnotationEditor}
   */
  static deserialize(data, parent) {
    const editor = new this.prototype.constructor({
      parent,
      id: parent.getNextId(),
    });
    editor.rotation = data.rotation;

    const [pageWidth, pageHeight] = parent.pageDimensions;
    const [x, y, width, height] = editor.getRectInCurrentCoords(
      data.rect,
      pageHeight
    );
    editor.x = x / pageWidth;
    editor.y = y / pageHeight;
    editor.width = width / pageWidth;
    editor.height = height / pageHeight;

    return editor;
  }

  static renderImportAnnotations(data, parent) {
    const editor = new this.prototype.constructor({
      parent,
      id: parent.getNextId(),
    });
    editor.rotation = data.rotation;
    const [pageWidth, pageHeight] = parent.pageDimensions;

    
    const annotRect = this.prototype.rectCalc(data.position, parent.viewport.height, parent.scaleFactor);
    editor.x = annotRect.top / pageWidth;
    editor.y = annotRect.left / pageHeight;
    editor.width = annotRect.width / pageWidth;
    editor.height = annotRect.height / pageHeight;

    return editor;
  }
}

export { PDFAnnotation };
