/**
 * Copyright (c) Tiny Technologies, Inc. All rights reserved.
 * Licensed under the LGPL or a commercial license.
 * For LGPL see License.txt in the project root for license information.
 * For commercial licenses see https://www.tiny.cloud/
 */

import DOMUtils from 'tinymce/core/api/dom/DOMUtils';
import Rect from 'tinymce/core/api/geom/Rect';
import Factory from 'tinymce/core/api/ui/Factory';
import Delay from 'tinymce/core/api/util/Delay';
import Tools from 'tinymce/core/api/util/Tools';
import UiContainer from 'tinymce/ui/UiContainer';
import * as Settings from '../api/Settings';
import Toolbar from './Toolbar';

const DOM = DOMUtils.DOM;

const toClientRect = function (geomRect) {
  return {
    left: geomRect.x,
    top: geomRect.y,
    width: geomRect.w,
    height: geomRect.h,
    right: geomRect.x + geomRect.w,
    bottom: geomRect.y + geomRect.h
  };
};

const hideAllFloatingPanels = function (editor) {
  Tools.each(editor.contextToolbars, function (toolbar) {
    if (toolbar.panel) {
      toolbar.panel.hide();
    }
  });
};

const movePanelTo = function (panel, pos) {
  panel.moveTo(pos.left, pos.top);
};

const togglePositionClass = function (panel, relPos, predicate) {
  relPos = relPos ? relPos.substr(0, 2) : '';

  Tools.each({
    t: 'down',
    b: 'up'
  }, function (cls, pos) {
    panel.classes.toggle('arrow-' + cls, predicate(pos, relPos.substr(0, 1)));
  });

  Tools.each({
    l: 'left',
    r: 'right'
  }, function (cls, pos) {
    panel.classes.toggle('arrow-' + cls, predicate(pos, relPos.substr(1, 1)));
  });
};

const userConstrain = function (handler, x, y, elementRect, contentAreaRect, panelRect) {
  panelRect = toClientRect({ x, y, w: panelRect.w, h: panelRect.h });

  if (handler) {
    panelRect = handler({
      elementRect: toClientRect(elementRect),
      contentAreaRect: toClientRect(contentAreaRect),
      panelRect
    });
  }

  return panelRect;
};

const addContextualToolbars = function (editor) {
  let scrollContainer;

  const getContextToolbars = function () {
    return editor.contextToolbars || [];
  };

  const getElementRect = function (elm) {
    let pos, targetRect, root;

    pos = DOM.getPos(editor.getContentAreaContainer());
    targetRect = editor.dom.getRect(elm);
    root = editor.dom.getRoot();

    // Adjust targetPos for scrolling in the editor
    if (root.nodeName === 'BODY') {
      targetRect.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
      targetRect.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
    }

    targetRect.x += pos.x;
    targetRect.y += pos.y;

    return targetRect;
  };

  const reposition = function (match, shouldShow?) {
    let relPos, panelRect, elementRect, contentAreaRect, panel, relRect, testPositions, smallElementWidthThreshold;
    const handler = Settings.getInlineToolbarPositionHandler(editor);

    if (editor.removed) {
      return;
    }

    if (!match || !match.toolbar.panel) {
      hideAllFloatingPanels(editor);
      return;
    }

    testPositions = [
      'bc-tc', 'tc-bc',
      'tl-bl', 'bl-tl',
      'tr-br', 'br-tr'
    ];

    panel = match.toolbar.panel;

    // Only show the panel on some events not for example nodeChange since that fires when context menu is opened
    if (shouldShow) {
      panel.show();
    }

    elementRect = getElementRect(match.element);
    panelRect = DOM.getRect(panel.getEl());
    contentAreaRect = DOM.getRect(editor.getContentAreaContainer() || editor.getBody());

    const delta = UiContainer.getUiContainerDelta(panel).getOr({ x: 0, y: 0 });
    elementRect.x += delta.x;
    elementRect.y += delta.y;
    panelRect.x += delta.x;
    panelRect.y += delta.y;
    contentAreaRect.x += delta.x;
    contentAreaRect.y += delta.y;

    smallElementWidthThreshold = 25;

    if (DOM.getStyle(match.element, 'display', true) !== 'inline') {
      // We need to use these instead of the rect values since the style
      // size properites might not be the same as the real size for a table if it has a caption
      const clientRect = match.element.getBoundingClientRect();
      elementRect.w = clientRect.width;
      elementRect.h = clientRect.height;
    }

    if (!editor.inline) {
      contentAreaRect.w = editor.getDoc().documentElement.offsetWidth;
    }

    // Inflate the elementRect so it doesn't get placed above resize handles
    if (editor.selection.controlSelection.isResizable(match.element) && elementRect.w < smallElementWidthThreshold) {
      elementRect = Rect.inflate(elementRect, 0, 8);
    }

    relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, testPositions);
    elementRect = Rect.clamp(elementRect, contentAreaRect);

    if (relPos) {
      relRect = Rect.relativePosition(panelRect, elementRect, relPos);
      movePanelTo(panel, userConstrain(handler, relRect.x, relRect.y, elementRect, contentAreaRect, panelRect));
    } else {
      // Allow overflow below the editor to avoid placing toolbars ontop of tables
      contentAreaRect.h += panelRect.h;

      elementRect = Rect.intersect(contentAreaRect, elementRect);
      if (elementRect) {
        relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, [
          'bc-tc', 'bl-tl', 'br-tr'
        ]);

        if (relPos) {
          relRect = Rect.relativePosition(panelRect, elementRect, relPos);
          movePanelTo(panel, userConstrain(handler, relRect.x, relRect.y, elementRect, contentAreaRect, panelRect));
        } else {
          movePanelTo(panel, userConstrain(handler, elementRect.x, elementRect.y, elementRect, contentAreaRect, panelRect));
        }
      } else {
        panel.hide();
      }
    }

    togglePositionClass(panel, relPos, function (pos1, pos2) {
      return pos1 === pos2;
    });

    // drawRect(contentAreaRect, 'blue');
    // drawRect(elementRect, 'red');
    // drawRect(panelRect, 'green');
  };

  const repositionHandler = function (show) {
    return function () {
      const execute = function () {
        if (editor.selection) {
          reposition(findFrontMostMatch(editor.selection.getNode()), show);
        }
      };

      Delay.requestAnimationFrame(execute);
    };
  };

  const bindScrollEvent = function (panel) {
    if (!scrollContainer) {
      const reposition = repositionHandler(true);
      const uiContainer = UiContainer.getUiContainer(panel);

      scrollContainer = editor.selection.getScrollContainer() || editor.getWin();
      DOM.bind(scrollContainer, 'scroll', reposition);
      DOM.bind(uiContainer, 'scroll', reposition);

      editor.on('remove', function () {
        DOM.unbind(scrollContainer, 'scroll', reposition);
        DOM.unbind(uiContainer, 'scroll', reposition);
      });
    }
  };

  const showContextToolbar = function (match) {
    let panel;

    if (match.toolbar.panel) {
      match.toolbar.panel.show();
      reposition(match);
      return;
    }

    panel = Factory.create({
      type: 'floatpanel',
      role: 'dialog',
      classes: 'tinymce tinymce-inline arrow',
      ariaLabel: 'Inline toolbar',
      layout: 'flex',
      direction: 'column',
      align: 'stretch',
      autohide: false,
      autofix: true,
      fixed: true,
      border: 1,
      items: Toolbar.createToolbar(editor, match.toolbar.items),
      oncancel () {
        editor.focus();
      }
    });

    UiContainer.setUiContainer(editor, panel);
    bindScrollEvent(panel);

    match.toolbar.panel = panel;
    panel.renderTo().reflow();
    reposition(match);
  };

  const hideAllContextToolbars = function () {
    Tools.each(getContextToolbars(), function (toolbar) {
      if (toolbar.panel) {
        toolbar.panel.hide();
      }
    });
  };

  const findFrontMostMatch = function (targetElm) {
    let i, y, parentsAndSelf;
    const toolbars = getContextToolbars();

    parentsAndSelf = editor.$(targetElm).parents().add(targetElm);
    for (i = parentsAndSelf.length - 1; i >= 0; i--) {
      for (y = toolbars.length - 1; y >= 0; y--) {
        if (toolbars[y].predicate(parentsAndSelf[i])) {
          return {
            toolbar: toolbars[y],
            element: parentsAndSelf[i]
          };
        }
      }
    }

    return null;
  };

  editor.on('click keyup setContent ObjectResized', function (e) {
    // Only act on partial inserts
    if (e.type === 'setcontent' && !e.selection) {
      return;
    }

    // Needs to be delayed to avoid Chrome img focus out bug
    Delay.setEditorTimeout(editor, function () {
      let match;

      match = findFrontMostMatch(editor.selection.getNode());
      if (match) {
        hideAllContextToolbars();
        showContextToolbar(match);
      } else {
        hideAllContextToolbars();
      }
    });
  });

  editor.on('blur hide contextmenu', hideAllContextToolbars);

  editor.on('ObjectResizeStart', function () {
    const match = findFrontMostMatch(editor.selection.getNode());

    if (match && match.toolbar.panel) {
      match.toolbar.panel.hide();
    }
  });

  editor.on('ResizeEditor ResizeWindow', repositionHandler(true));
  editor.on('nodeChange', repositionHandler(false));

  editor.on('remove', function () {
    Tools.each(getContextToolbars(), function (toolbar) {
      if (toolbar.panel) {
        toolbar.panel.remove();
      }
    });

    editor.contextToolbars = {};
  });

  editor.shortcuts.add('ctrl+F9', '', function () {
    const match = findFrontMostMatch(editor.selection.getNode());
    if (match && match.toolbar.panel) {
      match.toolbar.panel.items()[0].focus();
    }
  });
};

export default {
  addContextualToolbars
};