/**** Libraries ****/
import React from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";

/**** Methods ****/
import viewPort from "./__viewport";
import {
  absolutePositionMapping,
  rtlAbsolutePositionMapping,
  rtlFixedPositionMapping
} from "./__popupconstants.json";
import ResizeObserver from "./__resizeObserver";

let lastOpenedGroup = [];
let popups = {};

const debounce = function (func, wait) {
  let interval;
  return function (e) {
    clearTimeout(interval);
    interval = setTimeout(func.bind(undefined, e), wait);
  };
};
export function isDescendant(parent, child) {
  let node = child.parentNode;
  while (node !== null) {
    if (node === parent) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
}

global.closeGroupPopups = function (groupName) {
  const groupPopups = popups[groupName] || [];
  groupPopups.forEach((popup) => {
    popup.state.isPopupOpen &&
      popup.setState({
        isPopupOpen: false,
        isPopupReady: false
      });
  });
};

const defaultState = {
  position: "bottomCenter",
  height: "0px",
  positions: {}, //{bottomCenter: true, bottomLeft: false, ...}
  positionsOffset: {}, //{bottomCenter: {left: ‘’,top: ‘’ }, .....}
  targetOffset: {}, //{height: ‘’, width: ‘’,}
  popupOffset: {}, //{height: ‘’, width: ‘’,}
  isAbsolutePositioningNeeded: true
};

/* eslint-disable react/no-deprecated */
/* eslint-disable react/prop-types */

const Popup = function (
  Component,
  group = "global",
  needResizeHandling = true,
  {
    isAbsolutePositioningNeeded: isAbsolutePopup = true,
    isArrow: needPopupArrow = false,
    customOrder: customPositionOrder = [],
    scrollDebounceTime: popupScrollDebounceTime = 0,
    closeOnScroll: closeOnScrollPopup = false
  } = {}
) {
  class Popup extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isPopupOpen: props.isPopupOpen || false,
        position: "bottomCenter",
        height: "0px",
        isPopupReady: props.isPopupOpen || false,
        positions: {}, //{bottomCenter: true, bottomLeft: false, ...}
        positionsOffset: {}, //{bottomCenter: {left: ‘’,top: ‘’ }, .....}
        targetOffset: {}, //{height: ‘’, width: ‘’,}
        popupOffset: {}, //{height: ‘’, width: ‘’,}
        isAbsolutePositioningNeeded: true
      };
      this.togglePopup = this.togglePopup.bind(this);
      this.documentClickHandler = this.documentClickHandler.bind(this);
      this.documentClickHandler1 = this.documentClickHandler1.bind(this);
      this.removeClose = this.removeClose.bind(this);
      this.documentKeyupHandler = this.documentKeyupHandler.bind(this);
      this.openPopupOnly = this.openPopupOnly.bind(this);
      this.closePopupOnly = this.closePopupOnly.bind(this);
      this.getTargetRef = this.getTargetRef.bind(this);
      this.getContainerRef = this.getContainerRef.bind(this);
      this.handlePopupPosition = this.handlePopupPosition.bind(this);
      this.handleResize = debounce(this.handleResize.bind(this), 300);
      this.handlePopupResize = this.handlePopupResize.bind(this);
      this.getIsAbsolutePopup = this.getIsAbsolutePopup.bind(this);
      this.getNeedArrow = this.getNeedArrow.bind(this);
      this.getCustomPositionOrder = this.getCustomPositionOrder.bind(this);
      this.handleOpenPopupPositionChange =
        this.handleOpenPopupPositionChange.bind(this);
      this.getScrollDebounceTime = this.getScrollDebounceTime.bind(this);
      this.getCloseOnScrollPopup = this.getCloseOnScrollPopup.bind(this);
      this.handleCloseLastOpenedGroup =
        this.handleCloseLastOpenedGroup.bind(this);
      this.handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
      this.handleDocumentFocus = this.handleDocumentFocus.bind(this);
      this.handleGetNeedPrevent = this.handleGetNeedPrevent.bind(this);

      this.popupObserver = new ResizeObserver(this.handlePopupResize);

      //dropBoxSize
      this.size = null;

      this.isAbsolutePopup = isAbsolutePopup;
      this.needPopupArrow = needPopupArrow;
      this.customPositionOrder = customPositionOrder;
      this.scrollDebounceTime = popupScrollDebounceTime;
      this.closeOnScroll = closeOnScrollPopup;

      const { scrollDebounceTime } = this.getScrollDebounceTime(this);
      this.handleScroll = debounce(
        this.handleScroll.bind(this),
        scrollDebounceTime
      );
    }

    componentDidMount() {
      const group = this.getGroup();
      const groupPopups = popups[group] || [];
      groupPopups.push(this);
      popups[group] = groupPopups;
      if (Object.keys(popups).length === 1 && groupPopups.length === 1) {
        document.addEventListener("click", this.documentClickHandler, false);

        document.addEventListener("keyup", this.documentKeyupHandler, false);
        // document.addEventListener('scroll', this.handleScroll, true);
        window.addEventListener("resize", this.handleResize);

        document.addEventListener("click", this.documentClickHandler1, true);
        document.addEventListener(
          "mousedown",
          this.handleDocumentMouseDown,
          true
        );
        document.addEventListener("focus", this.handleDocumentFocus, true);
      }
    }

    componentWillReceiveProps(nextProps) {
      const { isPopupOpen } = this.state;
      if (
        typeof nextProps.isPopupOpen !== "undefined" &&
        nextProps.isPopupOpen !== isPopupOpen
      ) {
        this.setState({
          isPopupOpen: nextProps.isPopupOpen,
          isPopupReady: nextProps.isPopupOpen
        });
      }
    }

    componentDidUpdate(prevProps, prevState) {
      const { isPopupReady } = this.state;
      const { isPopupReady: oldStateOpen = false } = prevState || {};
      const { dropElement } = this;
      const { needResizeHandling: propResizeHandling } = this.props;

      if (oldStateOpen !== isPopupReady) {
        if (
          isPopupReady &&
          dropElement &&
          (propResizeHandling !== undefined
            ? propResizeHandling
            : needResizeHandling)
        ) {
          this.popupObserver.replaceObservationElement(dropElement);
        } else if (!isPopupReady) {
          this.size = null;
          this.popupObserver.disconnect();
        }
      }
    }

    componentWillUnmount() {
      const group = this.getGroup();
      popups = Object.keys(popups).reduce((res, groupName) => {
        if (groupName === group) {
          const groupPopups = popups[group];
          const newGroupPopups = groupPopups.filter((popup) => popup !== this);
          newGroupPopups.length === 0 &&
            lastOpenedGroup.indexOf(group) >= 0 &&
            lastOpenedGroup.splice(lastOpenedGroup.indexOf(group), 1);
          res[group] = newGroupPopups;
        }
        return res;
      }, popups);

      let noPopups = true;
      for (const i in popups) {
        if (popups[i].length >= 1) {
          noPopups = false;
          break;
        }
      }
      if (this.popupObserver) {
        this.popupObserver.disconnect();
      }
      if (noPopups) {
        document.removeEventListener("click", this.documentClickHandler);
        document.removeEventListener("keyup", this.documentKeyupHandler);
        // document.removeEventListener('scroll', this.handleScroll);
        window.removeEventListener("resize", this.handleResize);

        document.removeEventListener("click", this.documentClickHandler1, true);
        document.removeEventListener(
          "mousedown",
          this.handleDocumentMouseDown,
          true
        );
        document.removeEventListener("focus", this.handleDocumentFocus, true);
      }
    }

    getGroup() {
      const { popupGroup } = this.props;
      return popupGroup || group;
    }

    getNeedArrow(popup) {
      const { isArrow } = popup.props;
      const { needPopupArrow } = popup;
      return isArrow !== undefined ? isArrow : needPopupArrow;
    }

    getScrollDebounceTime(popup) {
      const { scrollDebounceTime } = popup.props;
      const { scrollDebounceTime: popupScrollDebounceTime } = popup;
      return scrollDebounceTime !== undefined
        ? scrollDebounceTime
        : popupScrollDebounceTime;
    }

    getCloseOnScrollPopup(popup) {
      const { closeOnScroll } = popup.props;
      const { closeOnScroll: closeOnScrollPopup } = popup;
      return closeOnScroll !== undefined ? closeOnScroll : closeOnScrollPopup;
    }

    getIsAbsolutePopup(popup) {
      const { isAbsolutePositioningNeeded } = popup.props;
      const { isAbsolutePopup } = popup;
      return isAbsolutePositioningNeeded !== undefined
        ? isAbsolutePositioningNeeded
        : isAbsolutePopup;
    }

    getCustomPositionOrder(popup) {
      const { customOrder = [] } = popup.props;
      const { customPositionOrder } = popup;
      return customOrder.length !== 0 ? customOrder : customPositionOrder;
    }

    togglePopup(e, defaultPosition) {
      const group = this.getGroup();
      this.removeClose(e);
      const { isPopupOpen } = this.state;
      const groupPopups = popups[group] || [];
      lastOpenedGroup =
        !isPopupOpen && lastOpenedGroup.indexOf(group) === -1
          ? [group, ...lastOpenedGroup]
          : lastOpenedGroup;
      isPopupOpen && lastOpenedGroup.splice(0, 1);
      groupPopups.forEach((popup) => {
        if (popup !== this && popup.state.isPopupOpen) {
          popup.setState({
            isPopupOpen: false,
            isPopupReady: false
          });
        }
      });
      if (isPopupOpen) {
        this.setState({ isPopupOpen: false, isPopupReady: false });
      } else {
        this.handlePopupPosition(defaultPosition);
      }
    }

    openPopupOnly(e, defaultPosition) {
      const group = this.getGroup();
      this.removeClose(e);
      lastOpenedGroup =
        lastOpenedGroup.indexOf(group) === -1
          ? [group, ...lastOpenedGroup]
          : lastOpenedGroup;
      this.handlePopupPosition(defaultPosition);
    }

    closePopupOnly(e) {
      this.removeClose(e);
      lastOpenedGroup.splice(0, 1);
      const { isPopupOpen } = this.state;
      if (isPopupOpen) {
        this.setState({ isPopupOpen: false, isPopupReady: false });
      }
    }

    handleCloseLastOpenedGroup() {
      const groupPopups = lastOpenedGroup.length
        ? popups[lastOpenedGroup[0]] || []
        : [];
      lastOpenedGroup.splice(0, 1);
      groupPopups.forEach((popup) => {
        popup.state.isPopupOpen &&
          popup.setState({
            isPopupOpen: false,
            isPopupReady: false
          });
      });
    }

    handleDocumentMouseDown(e) {
      const needPrevent = this.handleGetNeedPrevent(e);
      if (needPrevent) {
        this.removeClose(e);
      }
    }

    handleDocumentFocus(e) {
      const needPrevent = this.handleGetNeedPrevent(e);
      if (needPrevent) {
        this.removeClose(e);
      }
    }

    handleGetNeedPrevent(e) {
      let needPrevent = false;
      if (lastOpenedGroup.length > 1) {
        const { target } = e;

        const groupPopups = lastOpenedGroup.length
          ? popups[lastOpenedGroup[0]]
          : [];

        let openedPopup = null;
        // eslint-disable-next-line guard-for-in
        for (const i in groupPopups) {
          const { isPopupOpen } = groupPopups[i].state;
          if (isPopupOpen) {
            openedPopup = groupPopups[i];
            break;
          }
        }

        if (openedPopup) {
          const { dropElement, placeHolderElement } = openedPopup;
          const isDropBoxChild = isDescendant(dropElement, target);
          const isTargetChild = isDescendant(placeHolderElement, target);
          // const massUpdateParent = document.querySelector(
          //   '[data-id=massUpdatePopup]'
          // );
          // const isPopupMassUpdateChild = isDescendant(
          //   massUpdateParent,
          //   dropElement
          // );

          if (
            !isDropBoxChild &&
            !isTargetChild
            // && isPopupMassUpdateChild
          ) {
            needPrevent = true;
          }
        }
      }
      return needPrevent;
    }

    documentClickHandler1(e) {
      const needPrevent = this.handleGetNeedPrevent(e);
      if (needPrevent) {
        this.removeClose(e);
        this.handleCloseLastOpenedGroup();
      }
    }

    documentClickHandler() {
      try {
        Object.keys(popups).forEach((groupName) => {
          const groupPopups = popups[groupName] || [];
          groupPopups.forEach((popup) => {
            popup.state.isPopupOpen &&
              (!popup.props.checkBeforeClose ||
                (popup.props.checkBeforeClose &&
                  popup.props.checkBeforeClose())) &&
              popup.setState({
                isPopupOpen: false,
                isPopupReady: false
              });
          });
        });
        lastOpenedGroup = [];
      } catch (e) {
        // eslint-disable-next-line no-console
        //console.error('popup component not unmounted properly', e);
      }
    }

    documentKeyupHandler(e) {
      try {
        if (e.keyCode === 27) {
          this.handleCloseLastOpenedGroup();
        }
      } catch (e) {
        // eslint-disable-next-line no-console
        //console.log('error', e);
      }
    }

    removeClose(e) {
      // e && e.preventDefault && e.preventDefault();
      e && e.stopPropagation && e.stopPropagation();
      e &&
        e.nativeEvent &&
        e.nativeEvent.stopImmediatePropagation &&
        e.nativeEvent.stopImmediatePropagation();
    }

    handlePopupPosition(defaultPosition = "bottomCenter", isResizeHandling) {
      // isResizeHandling --->>> Window resize and dropBox resize
      const { direction } = this.context;
      const { placeHolderElement, dropElement } = this;

      const needArrow = this.getNeedArrow(this);
      const isAbsolute = this.getIsAbsolutePopup(this);
      const customOrder = this.getCustomPositionOrder(this);

      if (direction === "rtl") {
        defaultPosition = isAbsolute
          ? rtlAbsolutePositionMapping[defaultPosition]
          : rtlFixedPositionMapping[defaultPosition];
      } else {
        defaultPosition = isAbsolute
          ? absolutePositionMapping[defaultPosition]
          : defaultPosition;
      }

      if (!placeHolderElement && !dropElement) {
        this.setState({ isPopupOpen: true, isPopupReady: true });
        return;
      }

      const setPosition = () => {
        requestAnimationFrame(() => {
          const { placeHolderElement, dropElement } = this;
          const { position, isPopupReady } = this.state;

          const scrollContainer =
            placeHolderElement.closest("[data-scroll=true]") || document.body;
          const betterPosition = viewPort.betterView(
            dropElement,
            placeHolderElement,
            defaultPosition,
            scrollContainer,
            { needArrow, isAbsolute, customOrder }
          );
          const { view, views, viewsOffset, targetOffset, popupOffset } =
            betterPosition || {};

          if (position !== view || !isPopupReady) {
            this.setState({
              isPopupReady: true,
              position: view,
              positions: views,
              positionsOffset: viewsOffset,
              targetOffset,
              popupOffset,
              isAbsolutePositioningNeeded: isAbsolute
            });
          }
        });
      };

      if (isResizeHandling) {
        setPosition();
      } else {
        this.defaultPosition = defaultPosition;
        this.setState(
          {
            isPopupOpen: true,
            isPopupReady: false
          },
          setPosition
        );
      }
    }

    handleOpenPopupPositionChange() {
      Object.keys(popups).forEach((groupName) => {
        const groupPopups = popups[groupName] || [];
        groupPopups.forEach((popup) => {
          if (popup.state.isPopupOpen) {
            const { placeHolderElement, dropElement, defaultPosition } = popup;
            const { position, positionsOffset = {} } = popup.state;

            if (placeHolderElement && dropElement) {
              const scrollContainer =
                placeHolderElement.closest("[data-scroll=true]") ||
                document.body;

              requestAnimationFrame(() => {
                const needArrow = this.getNeedArrow(popup);
                const isAbsolute = this.getIsAbsolutePopup(popup);
                const customOrder = this.getCustomPositionOrder(popup);

                const betterPosition = viewPort.betterView(
                  dropElement,
                  placeHolderElement,
                  defaultPosition,
                  scrollContainer,
                  { needArrow, isAbsolute, customOrder }
                );
                const {
                  view,
                  views,
                  viewsOffset = {},
                  targetOffset,
                  popupOffset
                } = betterPosition || {};

                const { left: oldLeft = "", top: oldTop = "" } =
                  positionsOffset[position] || {};
                const { left = "", top = "" } = viewsOffset[view] || {};

                const changeState = isAbsolute
                  ? position !== view
                  : oldLeft !== left || oldTop !== top;

                // let isInViewPort = viewPort.isInViewPort(
                //   placeHolderElement,
                //   scrollContainer
                // );

                if (changeState) {
                  popup.setState({
                    position: view,
                    positions: views,
                    positionsOffset: viewsOffset,
                    targetOffset,
                    popupOffset,
                    isAbsolutePositioningNeeded: isAbsolute
                  });
                }

                // if (!isInViewPort && !isAbsolute) {
                //   popup.setState({ isPopupOpen: false, isPopupReady: false });
                // } else if (view && changeState) {
                //   popup.setState({
                //     position: view,
                //     positions: views,
                //     positionsOffset: viewsOffset,
                //     targetOffset,
                //     popupOffset,
                //     isAbsolutePositioningNeeded: isAbsolute
                //   });
                // }
              });
            }
          }
        });
      });
    }

    handleResize() {
      this.handleOpenPopupPositionChange();
    }

    handleScroll(e) {
      // this.handleOpenPopupPositionChange();
      const { closeOnScroll } = this.getCloseOnScrollPopup(this);
      const { isPopupReady } = this.state;
      if (isPopupReady) {
        console.log("onscrollPopupREady");
      }
      if (isPopupReady && closeOnScroll) {
        console.log(this, "handle Scroll");
        this.togglePopup(e);
      }
    }

    handlePopupResize(popupSize) {
      const { height, width } = popupSize;
      const { height: oldHeight = 0, width: oldWidth = 0 } = this.size || {};
      const { isPopupReady, position } = this.state;

      if (
        isPopupReady &&
        this.size &&
        (oldHeight !== height || width !== oldWidth)
      ) {
        this.handlePopupPosition(position, true);
      }
      this.size = popupSize;
    }

    getTargetRef(el) {
      this.placeHolderElement = el;
    }

    getContainerRef(el) {
      this.dropElement = el;
    }

    render() {
      const { isPopupReady, isPopupOpen } = this.state;
      const localState = isPopupReady ? this.state : {};
      return (
        <Component
          {...this.props}
          {...this.state}
          openPopupOnly={this.openPopupOnly}
          closePopupOnly={this.closePopupOnly}
          togglePopup={this.togglePopup}
          removeClose={this.removeClose}
          getTargetRef={this.getTargetRef}
          getContainerRef={this.getContainerRef}
        />
      );
    }
  }
  Popup.displayName = Component.displayName || Component.name || Popup.name;

  Popup.contextTypes = {
    direction: PropTypes.string
  };

  return hoistStatics(Popup, Component);
};

export default Popup;
