import { OverlayPanel } from "primereact/overlaypanel";
import React, {
  ReactNode,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from "react";
import OSHoverZone from "./OSHoverZone";

type OSHoverPanel = {
  /**
   *
   * @param element Element that triggered the notification.
   * @param data Data to be passed to {@link Props.panelBuilder} for displaying.
   * @returns
   */
  notifyEnterEvent: (element: HTMLElement, data: any) => void;

  /**
   *
   * @returns
   */
  notifyLeaveEvent: () => void;
};

interface Props {
  /**
   * Builder for contents of panel, enabling passthrough of data.
   *
   * **If using data, ensure data property has been set on OSHoverZone, or null handling on builder is correct.**
   * @param data Data to be passed to panel from {@link OSHoverZone}.
   * @returns Contents of panel.
   */
  panelBuilder: (data?: any) => ReactNode;
}

/**
 * Panel which is dynamically shown or hidden based on whether self or associated {@link OSHoverZone} elements are hovered.
 * @example
 * const panelRef = useRef<OSHoverPanel | null>(null);
 * ...
 * <OSHoverPanel ref={panelRef} panelBuilder={panel} />
 * ...
 * <OSHoverZone panelRef={panelRef}>
 *  <span>Hover over me!</span>
 * </OSHoverZone>
 * @see {@link OSHoverZone}
 */
const OSHoverPanel = React.forwardRef<OSHoverPanel, Props>(
  ({ panelBuilder }, ref) => {
    //Internal reference to underlying PrimeReact OverlayPanel
    const panelRef = useRef<OverlayPanel>(null);

    //Flag whether any linked HoverZone instances are being hovered
    const [isHoveringZone, setIsHoveringZone] = useState<boolean>(false);

    //Flag whether displayed panel is being hovered
    const [isHoveringPanel, setIsHoveringPanel] = useState<boolean>(false);

    //Triggering HoverZone - prevents unexpected moving of panel
    const [zone, setZone] = useState<HTMLElement | null>(null);

    //Displayable data to be passed from HoverZone to HoverPanel
    const [data, setData] = useState<any>(null);

    //Handle events from external ref, updating state of panel
    useImperativeHandle(ref, () => ({
      notifyEnterEvent(element, data) {
        setIsHoveringZone(true);
        setZone(element);
        setData(data);
      },
      notifyLeaveEvent() {
        setIsHoveringZone(false);
      },
    }));

    //Handle showing/hiding of panel - triggered on hover state changes for both panel and zone
    useEffect(() => {
      //Zone hovering could either be new or old
      if (isHoveringZone) {
        panelRef.current?.show(null, zone);
        return;
      }

      //Only hide if not hovering either panel or zone
      if (!(isHoveringPanel || isHoveringZone)) {
        panelRef.current?.hide();
        setData(null);
        setZone(null);
        return;
      }
    }, [isHoveringPanel, isHoveringZone]);

    /**
     * Event of mouse entering panel - used to ensure panel persists while hovering over, to allow interaction
     * @param e associated MouseEvent
     */
    const onMouseEnterPanel = () => {
      setIsHoveringPanel(true);
    };

    /**
     * Event of mouse leaving panel - used to correctly hide panel after focus lost
     * @param e associated MouseEvent
     */
    const onMouseLeavePanel = () => {
      setIsHoveringPanel(false);
    };

    const children = data !== null ? panelBuilder(data) : <></>;

    return (
      <>
        <OverlayPanel
          ref={panelRef}
          onMouseEnter={onMouseEnterPanel}
          onMouseLeave={onMouseLeavePanel}
          children={children}
        />
      </>
    );
  },
);

export default OSHoverPanel;
