const defaultOrder = [
  "bottomCenter",
  "bottomLeftToRight",
  "bottomCenterToRight",
  "bottomRightToLeft",
  "bottomCenterToLeft",
  "topCenter",
  "topLeftToRight",
  "topCenterToRight",
  "topRightToLeft",
  "topCenterToLeft",
  "rightTopToBottom",
  "rightCenterToBottom",
  "rightCenter",
  "rightBottomToTop",
  "rightCenterToTop",
  "leftTopToBottom",
  "leftCenterToBottom",
  "leftCenter",
  "leftBottomToTop",
  "leftCenterToTop",
  "bottomRight",
  "bottomLeft",
  "topRight",
  "topLeft",
  "rightBottom",
  "rightTop",
  "leftBottom",
  "leftTop"
];

let viewPort = {
  frameRelativeRects: (el, customFrame, documentEle) => {
    if (!el) {
      return;
    }

    let rectTemp = el.getBoundingClientRect();
    let rect = {
      height: rectTemp.height || el.clientHeight,
      width: rectTemp.width || el.clientWidth,
      top: rectTemp.top,
      bottom: rectTemp.bottom,
      left: rectTemp.left,
      right: rectTemp.right,
      x: rectTemp.x,
      y: rectTemp.y
    };

    documentEle = documentEle ? documentEle : document.documentElement;

    let customFrameRect = {
      top: 0,
      left: 0,
      right: documentEle.clientWidth,
      bottom: documentEle.clientHeight,
      height: documentEle.clientHeight,
      width: documentEle.clientWidth,
      x: 0,
      y: 0
    };
    if (customFrame) {
      customFrameRect = customFrame.getBoundingClientRect();
    }

    let newRect = {
      top: rect.top - customFrameRect.top,
      left: rect.left - customFrameRect.left,
      height: rect.height,
      width: rect.width
    };
    newRect.bottom = newRect.top + newRect.height;
    newRect.right = newRect.left + newRect.width;
    newRect.x = newRect.left;
    newRect.y = newRect.top;

    let rectGap = {
      top: newRect.top,
      left: newRect.left,
      bottom: customFrameRect.height - newRect.bottom,
      right: customFrameRect.width - newRect.right
    };
    rectGap.center = {
      top: rectGap.top + newRect.height / 2,
      left: rectGap.left + newRect.width / 2,
      bottom: rectGap.bottom + newRect.height / 2,
      right: rectGap.right + newRect.width / 2
    };

    return { frameRect: customFrameRect, rect: newRect, rectGap };
  },

  isInViewPort: (el, customFrame) => {
    if (!el) {
      return;
    }
    let elRects = viewPort.frameRelativeRects(el, customFrame);
    let { rect } = elRects;
    let { frameRect } = elRects;
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= frameRect.height &&
      rect.right <= frameRect.width
    );
  },

  getViewPortEle: (el) => {
    return el.closest("[data-viewport-container=true]")
      ? el.closest("[data-viewport-container=true]")
      : document.documentElement;
  },

  possibilities: (
    el,
    relativeBox,
    customFrame,
    { needArrow, isAbsolute } = {}
  ) => {
    if (!el) {
      return;
    }

    let elViewPortEle = viewPort.getViewPortEle(el);
    let elRects = viewPort.frameRelativeRects(el, customFrame, elViewPortEle);
    let { rect } = elRects;

    let relativeBoxViewPortEle = viewPort.getViewPortEle(relativeBox);
    let relativeBoxRects = viewPort.frameRelativeRects(
      relativeBox,
      customFrame,
      relativeBoxViewPortEle
    );
    let { rectGap: relativeBoxGap, rect: relativeBoxRect } = relativeBoxRects;

    let relativeBoxDocumentRects = viewPort.frameRelativeRects(
      relativeBox,
      customFrame
    );
    let { rectGap: documentGap } = relativeBoxDocumentRects;

    //rect => PopOverContainer, relativeBoxGap => PopOverTarget

    //Horizontal ~ ----- X axis
    //Vertical ~ ||||||| Y axis
    let paddingSpace = 5; // space between target and relative element
    let arrowSize = needArrow ? 10 : 0;
    rect.height = rect.height + (needArrow ? arrowSize : paddingSpace);
    rect.width = rect.width + (needArrow ? arrowSize : paddingSpace);
    let arrowHorizontalBalancePx = !isAbsolute && needArrow ? 30 : 0;
    let arrowVerticalBalancePx = !isAbsolute && needArrow ? 27 : 0;

    let bottomOverFlow = rect.height > relativeBoxGap.bottom;
    let topOverFlow = rect.height > relativeBoxGap.top;
    let rightOverFlow = rect.width > relativeBoxGap.right;
    let leftOverFlow = rect.width > relativeBoxGap.left;

    let horizontalCenterOverFlow =
      rect.width / 2 > relativeBoxGap.center.right ||
      rect.width / 2 > relativeBoxGap.center.left;
    let verticalCenterOverFlow =
      rect.height / 2 > relativeBoxGap.center.top ||
      rect.height / 2 > relativeBoxGap.center.bottom;

    let horizontalLeftToRightOverFlow =
      rect.width > relativeBoxRect.width + relativeBoxGap.right;
    let horizontalCenterToRightOverFlow =
      rect.width > relativeBoxGap.center.right ||
      arrowHorizontalBalancePx > relativeBoxGap.center.left;
    let horizontalRightToLeft =
      rect.width > relativeBoxRect.width + relativeBoxGap.left;
    let horizontalCenterToLeft =
      rect.width > relativeBoxGap.center.left ||
      arrowHorizontalBalancePx > relativeBoxGap.center.right;

    let verticalTopToBottomOverFlow =
      rect.height > relativeBoxRect.height + relativeBoxGap.bottom;
    let verticalCenterToBottomOverFlow =
      rect.height > relativeBoxGap.center.bottom ||
      arrowVerticalBalancePx > relativeBoxGap.center.top;
    let verticalBottomToTopOverFlow =
      rect.height > relativeBoxRect.height + relativeBoxGap.top;
    let verticalCenterToTopOverFlow =
      rect.height > relativeBoxGap.center.top ||
      arrowVerticalBalancePx > relativeBoxGap.center.bottom;

    let views = {
      bottomCenter: !(bottomOverFlow || horizontalCenterOverFlow),
      bottomLeftToRight: !(bottomOverFlow || horizontalLeftToRightOverFlow),
      bottomCenterToRight: !(bottomOverFlow || horizontalCenterToRightOverFlow),
      bottomRight: !(
        bottomOverFlow ||
        rightOverFlow ||
        arrowHorizontalBalancePx > relativeBoxRect.right
      ),
      bottomRightToLeft: !(bottomOverFlow || horizontalRightToLeft),
      bottomCenterToLeft: !(bottomOverFlow || horizontalCenterToLeft),
      bottomLeft: !(
        bottomOverFlow ||
        leftOverFlow ||
        arrowHorizontalBalancePx > relativeBoxGap.right + relativeBoxRect.width
      ),

      topCenter: !(topOverFlow || horizontalCenterOverFlow),
      topLeftToRight: !(topOverFlow || horizontalLeftToRightOverFlow),
      topCenterToRight: !(topOverFlow || horizontalCenterToRightOverFlow),
      topRight: !(
        topOverFlow ||
        rightOverFlow ||
        arrowHorizontalBalancePx > relativeBoxRect.right
      ),
      topRightToLeft: !(topOverFlow || horizontalRightToLeft),
      topCenterToLeft: !(topOverFlow || horizontalCenterToLeft),
      topLeft: !(
        topOverFlow ||
        leftOverFlow ||
        arrowHorizontalBalancePx > relativeBoxGap.right + relativeBoxRect.width
      ),

      rightCenter: !(rightOverFlow || verticalCenterOverFlow),
      rightTopToBottom: !(rightOverFlow || verticalTopToBottomOverFlow),
      rightCenterToBottom: !(rightOverFlow || verticalCenterToBottomOverFlow),
      rightBottom: !(
        rightOverFlow ||
        bottomOverFlow ||
        arrowVerticalBalancePx > relativeBoxRect.bottom
      ),
      rightBottomToTop: !(rightOverFlow || verticalBottomToTopOverFlow),
      rightCenterToTop: !(rightOverFlow || verticalCenterToTopOverFlow),
      rightTop: !(
        rightOverFlow ||
        topOverFlow ||
        arrowVerticalBalancePx > relativeBoxGap.bottom + relativeBoxRect.height
      ),

      leftCenter: !(leftOverFlow || verticalCenterOverFlow),
      leftTopToBottom: !(leftOverFlow || verticalTopToBottomOverFlow),
      leftCenterToBottom: !(leftOverFlow || verticalCenterToBottomOverFlow),
      leftBottom: !(
        leftOverFlow ||
        bottomOverFlow ||
        arrowVerticalBalancePx > relativeBoxRect.bottom
      ),
      leftBottomToTop: !(leftOverFlow || verticalBottomToTopOverFlow),
      leftCenterToTop: !(leftOverFlow || verticalCenterToTopOverFlow),
      leftTop: !(
        leftOverFlow ||
        topOverFlow ||
        arrowVerticalBalancePx > relativeBoxGap.bottom + relativeBoxRect.height
      )
    };

    let horizontalCenter = relativeBoxGap.left + relativeBoxRect.width / 2;
    let horizontalLeft = relativeBoxGap.left;
    let horizontalRight = relativeBoxGap.left + relativeBoxRect.width;
    let verticalBottom = relativeBoxGap.top + relativeBoxRect.height;
    let verticalTop = relativeBoxGap.top;
    let verticalCenter = relativeBoxGap.top + relativeBoxRect.height / 2;
    let horizontalTop = documentGap.bottom + relativeBoxRect.height;
    let verticalCenterToTop = documentGap.bottom + relativeBoxRect.height / 2;

    let viewsOffset = {
      bottomCenter: {
        left: horizontalCenter - rect.width / 2,
        top: verticalBottom + arrowSize
      },
      bottomLeftToRight: {
        left: horizontalLeft,
        top: verticalBottom + arrowSize
      },
      bottomCenterToRight: {
        left: horizontalCenter - arrowHorizontalBalancePx,
        top: verticalBottom + arrowSize
      },
      bottomRight: {
        left: horizontalRight - arrowHorizontalBalancePx,
        top: verticalBottom + arrowSize
      },
      bottomRightToLeft: {
        left: horizontalRight - rect.width,
        top: verticalBottom + arrowSize
      },
      bottomCenterToLeft: {
        left: horizontalCenter - rect.width + arrowHorizontalBalancePx,
        top: verticalBottom + arrowSize
      },
      bottomLeft: {
        left: horizontalLeft - rect.width + arrowHorizontalBalancePx,
        top: verticalBottom + arrowSize
      },

      topCenter: {
        left: horizontalCenter - rect.width / 2,
        bottom: horizontalTop + arrowSize
      },
      topLeftToRight: {
        left: horizontalLeft,
        bottom: horizontalTop + arrowSize
      },
      topCenterToRight: {
        left: horizontalCenter - arrowHorizontalBalancePx,
        bottom: horizontalTop + arrowSize
      },
      topRight: {
        left: horizontalRight - arrowHorizontalBalancePx,
        bottom: horizontalTop + arrowSize
      },
      topRightToLeft: {
        left: horizontalRight - rect.width,
        bottom: horizontalTop + arrowSize
      },
      topCenterToLeft: {
        left: horizontalCenter - rect.width + arrowHorizontalBalancePx,
        bottom: horizontalTop + arrowSize
      },
      topLeft: {
        left: horizontalLeft - rect.width + arrowHorizontalBalancePx,
        top: verticalTop - (rect.height + arrowSize)
      },

      rightCenter: {
        left: horizontalRight + arrowSize,
        top: verticalCenter - rect.height / 2
      },
      rightTopToBottom: {
        left: horizontalRight + arrowSize,
        top: verticalTop
      },
      rightCenterToBottom: {
        left: horizontalRight + arrowSize,
        top: verticalCenter - arrowVerticalBalancePx
      },
      rightBottom: {
        left: horizontalRight + arrowSize,
        top: verticalBottom - arrowVerticalBalancePx
      },
      rightBottomToTop: {
        left: horizontalRight + arrowSize,
        bottom: documentGap.bottom
      },
      rightCenterToTop: {
        left: horizontalRight + arrowSize,
        bottom: verticalCenterToTop - arrowVerticalBalancePx
      },
      rightTop: {
        left: horizontalRight + arrowSize,
        bottom: horizontalTop - arrowVerticalBalancePx
      },

      leftCenter: {
        left: horizontalLeft - (rect.width + arrowSize),
        top: verticalCenter - rect.height / 2
      },
      leftTopToBottom: {
        left: horizontalLeft - (rect.width + arrowSize),
        top: verticalTop
      },
      leftCenterToBottom: {
        left: horizontalLeft - (rect.width + arrowSize),
        top: verticalCenter - arrowVerticalBalancePx
      },
      leftBottom: {
        left: horizontalLeft - (rect.width + arrowSize),
        top: verticalBottom - arrowVerticalBalancePx
      },
      leftBottomToTop: {
        left: horizontalLeft - (rect.width + arrowSize),
        bottom: documentGap.bottom
      },
      leftCenterToTop: {
        left: horizontalLeft - (rect.width + arrowSize),
        bottom: verticalCenterToTop - arrowVerticalBalancePx
      },
      leftTop: {
        left: horizontalLeft - (rect.width + arrowSize),
        bottom: horizontalTop - arrowVerticalBalancePx
      }
    };

    return {
      views,
      viewsOffset,
      targetOffset: relativeBoxRect,
      popupOffset: rect
    };
  },

  getArrowAdjustments: (el, relativeBox, customFrame) => {
    if (!el) {
      return;
    }
    let elRects = viewPort.frameRelativeRects(el, customFrame);
    let { rectGap } = elRects;
    let relativeBoxRects = viewPort.frameRelativeRects(
      relativeBox,
      customFrame
    );
    let relativeBoxGap = relativeBoxRects.rectGap;
    return {
      left: relativeBoxGap.center.left - rectGap.left,
      right: relativeBoxGap.center.right - rectGap.right,
      top: relativeBoxGap.center.top - rectGap.top,
      bottom: relativeBoxGap.center.bottom - rectGap.bottom
    };
  },

  betterView: (
    popup,
    relativeBox,
    defaultView = "",
    customFrame,
    { needArrow, isAbsolute, customOrder = [] } = {}
  ) => {
    let customScrollFrame = isAbsolute ? customFrame : "";

    let viewPortPossibilities = viewPort.possibilities(
      popup,
      relativeBox,
      customScrollFrame,
      { needArrow, isAbsolute }
    );

    if (!viewPortPossibilities) {
      return;
    }

    let { views, viewsOffset, targetOffset, popupOffset } =
      viewPortPossibilities;

    let view = null;
    let isViewFound = false;

    if (!isViewFound && defaultView && views[defaultView]) {
      view = defaultView;
      isViewFound = true;
    }

    if (!isViewFound) {
      if (customOrder.length > 0) {
        isViewFound = customOrder.some((position) => {
          if (views[position]) {
            view = position;
            return true;
          }
        });
      } else {
        isViewFound = defaultOrder.some((position) => {
          if (views[position]) {
            view = position;
            return true;
          }
        });
      }
    }

    if (!isViewFound) {
      view = defaultView;
    }

    return { view, views, viewsOffset, targetOffset, popupOffset };
  }
};

export default {
  betterView: viewPort.betterView,
  frameRelativeRects: viewPort.frameRelativeRects,
  getArrowAdjustments: viewPort.getArrowAdjustments,
  isInViewPort: viewPort.isInViewPort
};
