import type { AnchorSpec, MakeshiftAnchorSpec, NodeAnchorSpec, SelectionAnchorSpec } from '@ephox/alloy';
import { Optional } from '@ephox/katamari';
import { SugarElement } from '@ephox/sugar';

import DOMUtils from 'tinymce/core/api/dom/DOMUtils';
import type Editor from 'tinymce/core/api/Editor';

export type AnchorType = 'node' | 'selection' | 'point';

interface Position {
  readonly x: number;
  readonly y: number;
}

const nu = (x: number, y: number): MakeshiftAnchorSpec => ({
  type: 'makeshift',
  x,
  y
});

const transpose = (pos: Position, dx: number, dy: number) => {
  return nu(pos.x + dx, pos.y + dy);
};

const isTouchEvent = (e: MouseEvent | TouchEvent): e is TouchEvent => e.type === 'longpress' || e.type.indexOf('touch') === 0;

const fromPageXY = (e: MouseEvent | TouchEvent) => {
  if (isTouchEvent(e)) {
    const touch = e.touches[0];
    return nu(touch.pageX, touch.pageY);
  } else {
    return nu(e.pageX, e.pageY);
  }
};

const fromClientXY = (e: MouseEvent | TouchEvent) => {
  if (isTouchEvent(e)) {
    const touch = e.touches[0];
    return nu(touch.clientX, touch.clientY);
  } else {
    return nu(e.clientX, e.clientY);
  }
};

const transposeContentAreaContainer = (element: HTMLElement, pos: Position) => {
  const containerPos = DOMUtils.DOM.getPos(element);
  return transpose(pos, containerPos.x, containerPos.y);
};

export const getPointAnchor = (editor: Editor, e: MouseEvent | TouchEvent): MakeshiftAnchorSpec | SelectionAnchorSpec => {
  // If the contextmenu event is fired via the editor.dispatch() API or some other means, fall back to selection anchor
  if (e.type === 'contextmenu' || e.type === 'longpress') {
    if (editor.inline) {
      return fromPageXY(e);
    } else {
      return transposeContentAreaContainer(editor.getContentAreaContainer(), fromClientXY(e));
    }
  } else {
    return getSelectionAnchor(editor);
  }
};

export const getSelectionAnchor = (editor: Editor): SelectionAnchorSpec => {
  return {
    type: 'selection',
    root: SugarElement.fromDom(editor.selection.getNode())
  };
};

export const getNodeAnchor = (editor: Editor): NodeAnchorSpec => ({
  type: 'node',
  node: Optional.some(SugarElement.fromDom(editor.selection.getNode())),
  root: SugarElement.fromDom(editor.getBody())
});

export const getAnchorSpec = (editor: Editor, e: MouseEvent | TouchEvent, anchorType: AnchorType): AnchorSpec => {
  switch (anchorType) {
    case 'node':
      return getNodeAnchor(editor);
    case 'point':
      return getPointAnchor(editor, e);
    case 'selection':
      return getSelectionAnchor(editor);
  }
};
