import * as React from "react";
import { isElement } from "../../../../../@floating-ui/utils/dist/floating-ui.utils.dom.js";
import { useTimeout } from "../../../../utils/esm/useTimeout.js";
import { useValueAsRef } from "../../../../utils/esm/useValueAsRef.js";
import { useStableCallback } from "../../../../utils/esm/useStableCallback.js";
import { useIsoLayoutEffect } from "../../../../utils/esm/useIsoLayoutEffect.js";
import { useFloatingTree, useFloatingParentNodeId } from "../components/FloatingTree.js";
import { createChangeEventDetails } from "../../utils/createBaseUIEventDetails.js";
import { createAttribute } from "../utils/createAttribute.js";
import { TYPEABLE_SELECTOR } from "../utils/constants.js";
import { getDocument, getTarget, contains } from "../utils/element.js";
import { isMouseLikePointerType } from "../utils/event.js";
import { triggerHover } from "../../utils/reason-parts.js";
const safePolygonIdentifier = createAttribute("safe-polygon");
const interactiveSelector = `button,[role="button"],select,[tabindex]:not([tabindex="-1"]),${TYPEABLE_SELECTOR}`;
function isInteractiveElement(element) {
  return element ? Boolean(element.closest(interactiveSelector)) : false;
}
function getDelay(value, prop, pointerType) {
  if (pointerType && !isMouseLikePointerType(pointerType)) {
    return 0;
  }
  if (typeof value === "number") {
    return value;
  }
  if (typeof value === "function") {
    const result = value();
    if (typeof result === "number") {
      return result;
    }
    return result?.[prop];
  }
  return value?.[prop];
}
function getRestMs(value) {
  if (typeof value === "function") {
    return value();
  }
  return value;
}
function useHover(context, props = {}) {
  const store = "rootStore" in context ? context.rootStore : context;
  const open = store.useState("open");
  const floatingElement = store.useState("floatingElement");
  const domReferenceElement = store.useState("domReferenceElement");
  const {
    dataRef,
    events
  } = store.context;
  const {
    enabled = true,
    delay = 0,
    handleClose = null,
    mouseOnly = false,
    restMs = 0,
    move = true,
    triggerElement = null,
    externalTree
  } = props;
  const tree = useFloatingTree(externalTree);
  const parentId = useFloatingParentNodeId();
  const handleCloseRef = useValueAsRef(handleClose);
  const delayRef = useValueAsRef(delay);
  const restMsRef = useValueAsRef(restMs);
  const pointerTypeRef = React.useRef(void 0);
  const interactedInsideRef = React.useRef(false);
  const timeout = useTimeout();
  const handlerRef = React.useRef(void 0);
  const restTimeout = useTimeout();
  const blockMouseMoveRef = React.useRef(true);
  const performedPointerEventsMutationRef = React.useRef(false);
  const unbindMouseMoveRef = React.useRef(() => {
  });
  const restTimeoutPendingRef = React.useRef(false);
  const isHoverOpen = useStableCallback(() => {
    const type = dataRef.current.openEvent?.type;
    return type?.includes("mouse") && type !== "mousedown";
  });
  const isClickLikeOpenEvent = useStableCallback(() => {
    if (interactedInsideRef.current) {
      return true;
    }
    return dataRef.current.openEvent ? ["click", "mousedown"].includes(dataRef.current.openEvent.type) : false;
  });
  React.useEffect(() => {
    if (!enabled) {
      return void 0;
    }
    function onOpenChangeLocal(details) {
      if (!details.open) {
        timeout.clear();
        restTimeout.clear();
        blockMouseMoveRef.current = true;
        restTimeoutPendingRef.current = false;
      }
    }
    events.on("openchange", onOpenChangeLocal);
    return () => {
      events.off("openchange", onOpenChangeLocal);
    };
  }, [enabled, events, timeout, restTimeout]);
  React.useEffect(() => {
    if (!enabled) {
      return void 0;
    }
    if (!handleCloseRef.current) {
      return void 0;
    }
    if (!open) {
      return void 0;
    }
    function onLeave(event) {
      if (isClickLikeOpenEvent()) {
        return;
      }
      if (isHoverOpen()) {
        store.setOpen(false, createChangeEventDetails(triggerHover, event, event.currentTarget ?? void 0));
      }
    }
    const html = getDocument(floatingElement).documentElement;
    html.addEventListener("mouseleave", onLeave);
    return () => {
      html.removeEventListener("mouseleave", onLeave);
    };
  }, [floatingElement, open, store, enabled, handleCloseRef, isHoverOpen, isClickLikeOpenEvent]);
  const closeWithDelay = React.useCallback((event, runElseBranch = true) => {
    const closeDelay = getDelay(delayRef.current, "close", pointerTypeRef.current);
    if (closeDelay && !handlerRef.current) {
      timeout.start(closeDelay, () => store.setOpen(false, createChangeEventDetails(triggerHover, event)));
    } else if (runElseBranch) {
      timeout.clear();
      store.setOpen(false, createChangeEventDetails(triggerHover, event));
    }
  }, [delayRef, store, timeout]);
  const cleanupMouseMoveHandler = useStableCallback(() => {
    unbindMouseMoveRef.current();
    handlerRef.current = void 0;
  });
  const clearPointerEvents = useStableCallback(() => {
    if (performedPointerEventsMutationRef.current) {
      const body = getDocument(floatingElement).body;
      body.style.pointerEvents = "";
      body.removeAttribute(safePolygonIdentifier);
      performedPointerEventsMutationRef.current = false;
    }
  });
  const handleInteractInside = useStableCallback((event) => {
    const target = getTarget(event);
    if (!isInteractiveElement(target)) {
      interactedInsideRef.current = false;
      return;
    }
    interactedInsideRef.current = true;
  });
  React.useEffect(() => {
    if (!enabled) {
      return void 0;
    }
    function onReferenceMouseEnter(event) {
      timeout.clear();
      blockMouseMoveRef.current = false;
      if (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current) || getRestMs(restMsRef.current) > 0 && !getDelay(delayRef.current, "open")) {
        return;
      }
      const openDelay = getDelay(delayRef.current, "open", pointerTypeRef.current);
      const trigger2 = event.currentTarget ?? void 0;
      const domReference = store.select("domReferenceElement");
      const isOverInactiveTrigger = domReference && trigger2 && !contains(domReference, trigger2);
      if (openDelay) {
        timeout.start(openDelay, () => {
          if (!store.select("open")) {
            store.setOpen(true, createChangeEventDetails(triggerHover, event, trigger2));
          }
        });
      } else if (!open || isOverInactiveTrigger) {
        store.setOpen(true, createChangeEventDetails(triggerHover, event, trigger2));
      }
    }
    function onReferenceMouseLeave(event) {
      if (isClickLikeOpenEvent()) {
        clearPointerEvents();
        return;
      }
      unbindMouseMoveRef.current();
      const doc = getDocument(floatingElement);
      restTimeout.clear();
      restTimeoutPendingRef.current = false;
      const triggers = store.context.triggerElements;
      if (event.relatedTarget && triggers.hasElement(event.relatedTarget)) {
        return;
      }
      if (handleCloseRef.current && dataRef.current.floatingContext) {
        if (!open) {
          timeout.clear();
        }
        handlerRef.current = handleCloseRef.current({
          ...dataRef.current.floatingContext,
          tree,
          x: event.clientX,
          y: event.clientY,
          onClose() {
            clearPointerEvents();
            cleanupMouseMoveHandler();
            if (!isClickLikeOpenEvent()) {
              closeWithDelay(event, true);
            }
          }
        });
        const handler = handlerRef.current;
        doc.addEventListener("mousemove", handler);
        unbindMouseMoveRef.current = () => {
          doc.removeEventListener("mousemove", handler);
        };
        return;
      }
      const shouldClose = pointerTypeRef.current === "touch" ? !contains(floatingElement, event.relatedTarget) : true;
      if (shouldClose) {
        closeWithDelay(event);
      }
    }
    function onScrollMouseLeave(event) {
      if (isClickLikeOpenEvent() || !dataRef.current.floatingContext || !store.select("open")) {
        return;
      }
      const triggers = store.context.triggerElements;
      if (event.relatedTarget && triggers.hasElement(event.relatedTarget)) {
        return;
      }
      handleCloseRef.current?.({
        ...dataRef.current.floatingContext,
        tree,
        x: event.clientX,
        y: event.clientY,
        onClose() {
          clearPointerEvents();
          cleanupMouseMoveHandler();
          if (!isClickLikeOpenEvent()) {
            closeWithDelay(event);
          }
        }
      })(event);
    }
    function onFloatingMouseEnter() {
      timeout.clear();
      clearPointerEvents();
    }
    function onFloatingMouseLeave(event) {
      if (!isClickLikeOpenEvent()) {
        closeWithDelay(event, false);
      }
    }
    const trigger = triggerElement ?? domReferenceElement;
    if (isElement(trigger)) {
      const floating = floatingElement;
      if (open) {
        trigger.addEventListener("mouseleave", onScrollMouseLeave);
      }
      if (move) {
        trigger.addEventListener("mousemove", onReferenceMouseEnter, {
          once: true
        });
      }
      trigger.addEventListener("mouseenter", onReferenceMouseEnter);
      trigger.addEventListener("mouseleave", onReferenceMouseLeave);
      if (floating) {
        floating.addEventListener("mouseleave", onScrollMouseLeave);
        floating.addEventListener("mouseenter", onFloatingMouseEnter);
        floating.addEventListener("mouseleave", onFloatingMouseLeave);
        floating.addEventListener("pointerdown", handleInteractInside, true);
      }
      return () => {
        if (open) {
          trigger.removeEventListener("mouseleave", onScrollMouseLeave);
        }
        if (move) {
          trigger.removeEventListener("mousemove", onReferenceMouseEnter);
        }
        trigger.removeEventListener("mouseenter", onReferenceMouseEnter);
        trigger.removeEventListener("mouseleave", onReferenceMouseLeave);
        if (floating) {
          floating.removeEventListener("mouseleave", onScrollMouseLeave);
          floating.removeEventListener("mouseenter", onFloatingMouseEnter);
          floating.removeEventListener("mouseleave", onFloatingMouseLeave);
          floating.removeEventListener("pointerdown", handleInteractInside, true);
        }
      };
    }
    return void 0;
  }, [enabled, mouseOnly, move, domReferenceElement, floatingElement, triggerElement, store, closeWithDelay, cleanupMouseMoveHandler, clearPointerEvents, open, tree, delayRef, handleCloseRef, dataRef, isClickLikeOpenEvent, restMsRef, timeout, restTimeout, handleInteractInside]);
  useIsoLayoutEffect(() => {
    if (!enabled) {
      return void 0;
    }
    if (open && handleCloseRef.current?.__options?.blockPointerEvents && isHoverOpen()) {
      performedPointerEventsMutationRef.current = true;
      const floatingEl = floatingElement;
      if (isElement(domReferenceElement) && floatingEl) {
        const body = getDocument(floatingElement).body;
        body.setAttribute(safePolygonIdentifier, "");
        const ref = domReferenceElement;
        const parentFloating = tree?.nodesRef.current.find((node) => node.id === parentId)?.context?.elements.floating;
        if (parentFloating) {
          parentFloating.style.pointerEvents = "";
        }
        body.style.pointerEvents = "none";
        ref.style.pointerEvents = "auto";
        floatingEl.style.pointerEvents = "auto";
        return () => {
          body.style.pointerEvents = "";
          ref.style.pointerEvents = "";
          floatingEl.style.pointerEvents = "";
        };
      }
    }
    return void 0;
  }, [enabled, open, parentId, tree, handleCloseRef, isHoverOpen, domReferenceElement, floatingElement]);
  useIsoLayoutEffect(() => {
    if (!open) {
      pointerTypeRef.current = void 0;
      restTimeoutPendingRef.current = false;
      interactedInsideRef.current = false;
      cleanupMouseMoveHandler();
      clearPointerEvents();
    }
  }, [open, cleanupMouseMoveHandler, clearPointerEvents]);
  React.useEffect(() => {
    return () => {
      cleanupMouseMoveHandler();
      timeout.clear();
      restTimeout.clear();
      interactedInsideRef.current = false;
    };
  }, [enabled, domReferenceElement, cleanupMouseMoveHandler, timeout, restTimeout]);
  React.useEffect(() => {
    return clearPointerEvents;
  }, [clearPointerEvents]);
  const reference = React.useMemo(() => {
    function setPointerRef(event) {
      pointerTypeRef.current = event.pointerType;
    }
    return {
      onPointerDown: setPointerRef,
      onPointerEnter: setPointerRef,
      onMouseMove(event) {
        const {
          nativeEvent
        } = event;
        const trigger = event.currentTarget;
        const isOverInactiveTrigger = store.select("domReferenceElement") && !contains(store.select("domReferenceElement"), event.target);
        function handleMouseMove() {
          if (!blockMouseMoveRef.current && (!store.select("open") || isOverInactiveTrigger)) {
            store.setOpen(true, createChangeEventDetails(triggerHover, nativeEvent, trigger));
          }
        }
        if (mouseOnly && !isMouseLikePointerType(pointerTypeRef.current)) {
          return;
        }
        if (store.select("open") && !isOverInactiveTrigger || getRestMs(restMsRef.current) === 0) {
          return;
        }
        if (!isOverInactiveTrigger && restTimeoutPendingRef.current && event.movementX ** 2 + event.movementY ** 2 < 2) {
          return;
        }
        restTimeout.clear();
        if (pointerTypeRef.current === "touch") {
          handleMouseMove();
        } else if (isOverInactiveTrigger) {
          handleMouseMove();
        } else {
          restTimeoutPendingRef.current = true;
          restTimeout.start(getRestMs(restMsRef.current), handleMouseMove);
        }
      }
    };
  }, [mouseOnly, store, restMsRef, restTimeout]);
  return React.useMemo(() => enabled ? {
    reference
  } : {}, [enabled, reference]);
}
export {
  getDelay,
  useHover
};
//# sourceMappingURL=useHover.js.map
