import React, { useState, useRef, useEffect, Fragment } from "react";
import { createPortal } from "react-dom";

export interface HoverPopupProps {
    
    /**
     * The content to show when not being hovered
     */
    defaultContent: React.ReactNode;

    /**
     * The hover content to render on hover.
     * Note: Hovering re-renders the component
     */
    hoverContent: React.ReactNode;

    /**
     * Screen position of hovered content
     * @todo Improve
     */
    position?: "top" | "right" | "bottom" | "left" | "center";

    /**
     * Time in ms to delay rendering the hover content on hover.
     */
    delay?: number;

    /**
     * Positional offset 
     */
    offset?: number;

    /**
     * Whether the popup should follow the cursor instead of being positioned relative to the element
     */
    followCursor?: boolean;

    /**
     * Cursor offset when followCursor is true
     */
    cursorOffset?: { x: number; y: number };
}

/**
 * Component used for rendering a new node when an existing node is hovered.
 * Utilizes {@link https://react.dev/reference/react-dom/createPortal}
 */
const OSHoverV2: React.FC<HoverPopupProps> = ({ ...props }) => {
    const [isHovered, setIsHovered] = useState(false);
    const [popupStyle, setPopupStyle] = useState({});
    const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

    const triggerRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
        if (!props.followCursor || !isHovered) return;

        const handleMouseMove = (e: MouseEvent) => {
            setMousePosition({ x: e.clientX, y: e.clientY });
        };

        document.addEventListener('mousemove', handleMouseMove);
        return () => document.removeEventListener('mousemove', handleMouseMove);
    }, [props.followCursor, isHovered]);

    useEffect(() => {
        if (isHovered) {
            let style: React.CSSProperties = {
                position: "fixed",
                /**
                 * Reason for this high value is because of inconsistent z-index scaling
                 * when dealing with modals in modals.
                 * @todo Improve
                 */
                zIndex: 10000,
            };

            if (props.followCursor) {
                // relative to cursor
                const cursorOffset = props.cursorOffset || { x: 10, y: 10 };
                style.left = mousePosition.x + cursorOffset.x;
                style.top = mousePosition.y + cursorOffset.y;
                style.transform = "none";
            } else if (triggerRef.current) {
                // custom positioning
                const rect = triggerRef.current.getBoundingClientRect();
                const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
                const scrollTop = window.scrollY || document.documentElement.scrollTop;

                if (props.position === "center") {
                    style.left = "50%";
                    style.top = "50%";
                    style.transform = "translate(-50%, -50%)";
                } else {
                    switch (props.position) {
                        case "top":
                            style.left = rect.left + scrollLeft + rect.width / 2;
                            style.top = rect.top + scrollTop - (props.offset ?? 15);
                            style.transform = "translate(-50%, -100%)";
                            break;
                        case "right":
                            style.left = rect.right + scrollLeft + (props.offset ?? 15);
                            style.top = rect.top + scrollTop + rect.height / 2;
                            style.transform = "translateY(-50%)";
                            break;
                        case "bottom":
                            style.left = rect.left + scrollLeft + rect.width / 2;
                            style.top = rect.bottom + scrollTop + (props.offset ?? 15);
                            style.transform = "translateX(-50%)";
                            break;
                        case "left":
                            style.left = rect.left + scrollLeft - (props.offset ?? 15);
                            style.top = rect.top + scrollTop + rect.height / 2;
                            style.transform = "translate(-100%, -50%)";
                            break;
                        default:
                            style.left = rect.left + scrollLeft + rect.width / 2;
                            style.top = rect.top + scrollTop - (props.offset ?? 15);
                            style.transform = "translate(-50%, -100%)";
                    }
                }
            }
            setPopupStyle(style);
        }
    }, [isHovered, props.position, props.offset, props.followCursor, mousePosition, props.cursorOffset]);

    const handleMouseEnter = () => {
        setTimeout(() => setIsHovered(true), props.delay);
    };

    const handleMouseLeave = () => {
        setTimeout(() => setIsHovered(false), props.delay);
    };

    return (
        <Fragment>
            <div
                ref={triggerRef}
                className="relative inline-block"
                onMouseEnter={handleMouseEnter}
                onMouseLeave={handleMouseLeave}
            >
                <div className="cursor-pointer">{props.defaultContent}</div>
            </div>

            {isHovered &&
                createPortal(
                    <div
                        style={popupStyle}
                        className="bg-white rounded-md shadow-lg p-4 min-w-64 border border-(--cool-grey) transition-opacity duration-200 ease-in-out pointer-events-none"
                        onMouseEnter={props.followCursor ? undefined : handleMouseEnter}
                        onMouseLeave={props.followCursor ? undefined : handleMouseLeave}
                    >
                        {props.hoverContent}
                    </div>,
                    document.body,
                )}
        </Fragment>
    );
};

export default OSHoverV2;