import React, { useState, ReactElement, useEffect, useRef, useImperativeHandle } from "react";
import ReactDOM from "react-dom";
import cn from "classnames";
import { Modifiers } from "popper.js";
import { Manager, Reference, Popper, PopperProps } from "react-popper";
import PopoverBody from "./popover-body";

export interface IPopoverProps {
  actions?: React.MutableRefObject<{
    close?: () => void;
    open?: () => void;
  }>;
  avoidPreRender?: boolean;
  className?: string | object;
  content?: ReactElement | ReactElement[] | string;
  flexibleContent?: boolean;
  modifiers?: Modifiers;
  onToggle?: (isShow: boolean) => void;
  placement: PopperProps["placement"];
  reference: ReactElement;
  usePortal?: boolean;
  ["data-testid"]?: string;
}

export interface IPopoverWrapperProps {
  scheduleUpdate: () => void;
}

type IUseClickOutsideHandlerResult = [
  boolean,
  React.Dispatch<React.SetStateAction<boolean>>,
  React.MutableRefObject<HTMLElement | null>,
  React.MutableRefObject<HTMLElement | null>
];

function usePopoverContainer() {
  const portalContainerRef = useRef<HTMLDivElement>();

  useEffect(() => {
    let portalContainer = document.querySelector("#honeyui-popover-container") as HTMLDivElement;

    if (!portalContainer) {
      portalContainer = document.createElement("div");
      portalContainer.setAttribute("id", "honeyui-popover-container");

      document.body.appendChild(portalContainer);
    }

    portalContainerRef.current = portalContainer;
  }, []);

  return portalContainerRef;
}

function useClickOutsideHandler(): IUseClickOutsideHandlerResult {
  const [isShow, setIsShow] = useState(false);
  const referenceRef = useRef<HTMLElement | null>(null);
  const popoverRef = useRef<HTMLElement | null>(null);

  const refs = [referenceRef, popoverRef];

  useEffect(() => {
    if (!isShow) {
      return;
    }
    function handleClickOutside({ target }: MouseEvent) {
      if (!refs.find(ref => ref.current && ref.current.contains(target as Node))) {
        setIsShow(false);
      }
    }

    const clickOptions = { capture: true };
    document.addEventListener("click", handleClickOutside, clickOptions);
    return () => {
      document.removeEventListener("click", handleClickOutside, clickOptions);
    };
  }, [isShow]);

  return [isShow, setIsShow, referenceRef, popoverRef];
}

const PopoverWrapper: React.FC<IPopoverWrapperProps> = props => {
  const { scheduleUpdate, children } = props;

  const mounted = useRef(false);

  useEffect(() => {
    if (!mounted.current) {
      mounted.current = true;
    } else {
      scheduleUpdate();
    }
  }, [children]);

  return <>{children}</>;
};

const Popover: React.FC<IPopoverProps> = props => {
  const {
    actions,
    avoidPreRender,
    children,
    className,
    content,
    flexibleContent,
    modifiers,
    onToggle,
    placement,
    reference,
    usePortal
  } = props;
  const [isShow, setIsShow, referenceRef, popoverRef] = useClickOutsideHandler();
  const testId = props["data-testid"] || "honeyui-popover";
  const portalContainerRef = usePopoverContainer();

  useEffect(() => {
    if (onToggle) {
      onToggle(isShow);
    }
  }, [isShow]);

  // This creates actions object parent can execute
  useImperativeHandle(actions, () => ({
    close: () => setIsShow(false),
    open: () => setIsShow(true)
  }));

  const renderPopper = () => (
    <Popper
      placement={placement}
      modifiers={modifiers}
      positionFixed={flexibleContent}
      innerRef={node => (popoverRef.current = node)}
    >
      {({ ref, style, arrowProps, placement: actualPlacement, scheduleUpdate }) => {
        return (
          <div
            ref={ref}
            style={Object.assign({ ...style }, flexibleContent ? { maxWidth: "none" } : {})}
            data-placement={actualPlacement}
            data-testid={testId}
            className={cn(
              "popover",
              actualPlacement ? actualPlacement.split("-").map(p => `bs-popover-${p}`) : null,
              {
                fade: !isShow,
                show: isShow,
                visible: isShow,
                invisible: !isShow
              },
              className
            )}
            role="tooltip"
          >
            <div ref={arrowProps.ref} style={arrowProps.style} className="arrow" />
            <PopoverWrapper {...{ scheduleUpdate }}>
              {content ? <PopoverBody>{content}</PopoverBody> : children}
            </PopoverWrapper>
          </div>
        );
      }}
    </Popper>
  );

  return (
    <Manager>
      <Reference innerRef={node => (referenceRef.current = node)}>
        {({ ref }) => {
          return React.cloneElement(React.Children.only(reference), {
            ref,
            onClick: (event: React.SyntheticEvent<HTMLElement>) => {
              event.preventDefault();
              event.stopPropagation();

              setIsShow(!isShow);
            }
          });
        }}
      </Reference>
      {/* eslint-disable @typescript-eslint/indent */}
      {usePortal
        ? isShow && portalContainerRef.current
          ? ReactDOM.createPortal(renderPopper(), portalContainerRef.current)
          : null
        : avoidPreRender
        ? isShow && renderPopper()
        : renderPopper()}
      {/* eslint-enable */}
    </Manager>
  );
};

Popover.displayName = "Popover";

Popover.defaultProps = {
  flexibleContent: false,
  usePortal: false
};

export default Popover;
