/* COPYRIGHT Esri - https://js.arcgis.com/5.1/LICENSE.txt */
import { makeGenericController } from "@arcgis/lumina/controllers";
import { q as queryElementRoots, v as getShadowRootNode, j as isPrimaryPointerButton, x as isKeyboardTriggeredClick } from "./dom.js";
import { i as isActivationKey } from "./key.js";
import { t as toAriaBoolean } from "./aria.js";
const useReferenceElement = (options) => {
  const { manager } = options;
  return makeGenericController((component, controller) => {
    let animationFrameId = void 0;
    let lastRegisteredReferenceEl = void 0;
    const canManageReferenceElement = (referenceEl) => {
      return Boolean(referenceEl && component.referenceElementType);
    };
    const registerReferenceElement = (referenceEl) => {
      if (!canManageReferenceElement(referenceEl)) {
        return;
      }
      manager.registerElement(component, referenceEl);
      lastRegisteredReferenceEl = referenceEl;
    };
    const unregisterReferenceElement = (referenceEl) => {
      if (!canManageReferenceElement(referenceEl)) {
        return;
      }
      manager.unregisterElement(component, referenceEl);
      if (lastRegisteredReferenceEl === referenceEl) {
        lastRegisteredReferenceEl = void 0;
      }
    };
    const getReferenceElement = (component2) => {
      const { referenceElement, el } = component2;
      return (typeof referenceElement === "string" ? queryElementRoots(el, { id: referenceElement }) : referenceElement) || void 0;
    };
    const setUpReferenceElement = (warn = true) => {
      if (!component.referenceElementType) {
        return;
      }
      component.referenceEl = getReferenceElement(component);
      const { el, referenceElement, referenceEl } = component;
      if (warn && referenceElement && !referenceEl) {
        console.warn(`${el.tagName}: reference-element id "${referenceElement}" was not found.`, {
          el
        });
      }
    };
    controller.onConnected(() => {
      animationFrameId = requestAnimationFrame(() => {
        if (!component.el.isConnected) {
          return;
        }
        setUpReferenceElement(component.manager.loadedCalled);
        registerReferenceElement(component.referenceEl);
      });
    });
    controller.onLoaded(() => {
      if (component.referenceElement && !component.referenceEl) {
        setUpReferenceElement();
      }
    });
    controller.onUpdate((changes) => {
      if (!component.hasUpdated) {
        return;
      }
      if (changes.has("referenceElement")) {
        setUpReferenceElement();
      }
      if (changes.has("referenceEl")) {
        unregisterReferenceElement(changes.get("referenceEl"));
        registerReferenceElement(component.referenceEl);
      } else if (changes.has("open")) {
        manager.updateElement(component, component.referenceEl);
      }
    });
    controller.onDisconnected(() => {
      if (animationFrameId != null) {
        cancelAnimationFrame(animationFrameId);
        animationFrameId = void 0;
      }
      unregisterReferenceElement(lastRegisteredReferenceEl);
    });
  });
};
function haveSameComponents(components, activeComponents) {
  if (components === activeComponents) {
    return true;
  }
  if (components.length !== activeComponents.length) {
    return false;
  }
  const s1 = new Set(components);
  const s2 = new Set(activeComponents);
  if (s1.size !== s2.size) {
    return false;
  }
  for (const item of s1) {
    if (!s2.has(item)) {
      return false;
    }
  }
  return true;
}
const clickTolerance = 5;
const HOVER_OPEN_DELAY_MS = 300;
const HOVER_QUICK_OPEN_DELAY_MS = HOVER_OPEN_DELAY_MS / 3;
const HOVER_CLOSE_DELAY_MS = HOVER_OPEN_DELAY_MS * 1.5;
function isDrag({
  startX,
  startY,
  endX,
  endY
}) {
  const distance = Math.hypot(endX - startX, endY - startY);
  return distance > clickTolerance;
}
const referenceElementManager = (options) => {
  const registeredElements = /* @__PURE__ */ new Map();
  const registeredShadowRootCounts = /* @__PURE__ */ new WeakMap();
  let activeComponents = null;
  let clickedComponents = null;
  let hoverCloseTimeout = null;
  let hoverOpenTimeout = null;
  let hoveredComponents = null;
  let pointerDownPosition = null;
  let registeredComponentCount = 0;
  const queryComponents = (composedPath, type) => {
    const registeredElement = composedPath.find((pathEl) => registeredElements.has(pathEl));
    if (!registeredElement) {
      return void 0;
    }
    const components = registeredElements.get(registeredElement);
    return type ? components?.filter((component) => component.referenceElementType === type) : components;
  };
  const toggleComponents = (event, type) => {
    const composedPath = event.composedPath();
    const components = queryComponents(composedPath, type);
    components?.forEach((component) => {
      if (component && !component.triggerDisabled) {
        component.open = !component.open;
      }
    });
    Array.from(registeredElements.values()).flat().filter(
      (component) => !components?.includes(component) && component.autoClose && component.open && !composedPath.includes(component.el)
    ).forEach((component) => component.open = false);
  };
  const clickHandler = (event) => {
    if (isKeyboardTriggeredClick(event) || event.defaultPrevented || pointerDownPosition && isDrag({
      endY: event.clientY,
      endX: event.clientX,
      startY: pointerDownPosition.y,
      startX: pointerDownPosition.x
    })) {
      return;
    }
    pointerDownPosition = null;
    toggleComponents(event, "click");
  };
  const clearHoverOpenTimeout = () => {
    if (hoverOpenTimeout != null) {
      window.clearTimeout(hoverOpenTimeout);
    }
    hoverOpenTimeout = null;
  };
  const clearHoverCloseTimeout = () => {
    if (hoverCloseTimeout != null) {
      window.clearTimeout(hoverCloseTimeout);
    }
    hoverCloseTimeout = null;
  };
  const clearHoverTimeout = () => {
    clearHoverOpenTimeout();
    clearHoverCloseTimeout();
  };
  const pathHasOpenHoverComponent = (components, composedPath) => {
    return activeComponents?.some((component) => component?.open && composedPath.includes(component.el)) || components?.some((component) => component?.open && composedPath.includes(component.el));
  };
  const toggleHoverComponents = (components, open) => {
    components?.forEach((component) => component.open = open);
    activeComponents = open ? components : null;
  };
  const closeActiveHoverComponents = () => {
    toggleHoverComponents(activeComponents, false);
  };
  const hoverKeyDownHandler = (event) => {
    if (event.key === "Escape" && !event.defaultPrevented) {
      const openActiveHoverComponents = activeComponents?.filter((component) => component?.open);
      if (openActiveHoverComponents?.length) {
        clearHoverTimeout();
        closeActiveHoverComponents();
        const composedPath = event.composedPath();
        if (openActiveHoverComponents.some(
          (component) => component.referenceEl instanceof Element && composedPath.includes(component.referenceEl) || composedPath.includes(component.el)
        )) {
          event.preventDefault();
        }
      }
    }
  };
  const hoverClickHandler = (event) => {
    if (event.defaultPrevented) {
      return;
    }
    clickedComponents = null;
    const composedPath = event.composedPath();
    const components = queryComponents(composedPath, "hover");
    if (pathHasOpenHoverComponent(components, composedPath)) {
      clearHoverTimeout();
      return;
    }
    closeActiveHoverComponents();
    if (!components?.length) {
      return;
    }
    clearHoverTimeout();
    const closeOnClickHoverComponents = components.filter((component) => component.closeOnClick);
    const nonCloseOnClickHoverComponents = components.filter((component) => !component.closeOnClick);
    if (closeOnClickHoverComponents?.length) {
      clickedComponents = closeOnClickHoverComponents;
      toggleHoverComponents(closeOnClickHoverComponents, false);
    }
    toggleHoverComponents(nonCloseOnClickHoverComponents, true);
  };
  const pointerDownHandler = (event) => {
    if (event.defaultPrevented || !isPrimaryPointerButton(event)) {
      return;
    }
    const { clientX, clientY } = event;
    pointerDownPosition = { x: clientX, y: clientY };
  };
  const keyDownHandler = (event) => {
    if (event.defaultPrevented) {
      return;
    }
    if (event.key === "Escape") {
      closeAllComponents();
    } else if (isActivationKey(event.key)) {
      toggleComponents(event, "click");
    }
  };
  const onReferenceElementKeyDown = (event) => {
    const components = queryComponents(event.composedPath());
    components?.forEach((component) => component.onReferenceElementKeyDown?.(event));
  };
  const closeAllComponents = () => {
    Array.from(registeredElements.values()).flat().forEach((component) => component.open = false);
  };
  const closeComponentsIfNotActive = (components) => {
    if (!haveSameComponents(components ?? [], activeComponents ?? [])) {
      closeActiveHoverComponents();
    }
  };
  const openHoveredComponents = (components) => {
    hoverOpenTimeout = window.setTimeout(
      () => {
        if (hoverOpenTimeout === null || !haveSameComponents(components ?? [], hoveredComponents ?? [])) {
          return;
        }
        clearHoverCloseTimeout();
        closeComponentsIfNotActive(components);
        toggleHoverComponents(components, true);
      },
      activeComponents?.some((component) => component.open) ? HOVER_QUICK_OPEN_DELAY_MS : HOVER_OPEN_DELAY_MS
    );
  };
  const closeHoveredComponents = () => {
    hoverCloseTimeout = window.setTimeout(() => {
      if (hoverCloseTimeout === null) {
        return;
      }
      closeActiveHoverComponents();
    }, HOVER_CLOSE_DELAY_MS);
  };
  const pointerMoveHandler = (event) => {
    if (event.defaultPrevented) {
      closeActiveHoverComponents();
      return;
    }
    const composedPath = event.composedPath();
    const components = queryComponents(composedPath, "hover");
    if (pathHasOpenHoverComponent(components, composedPath)) {
      clearHoverTimeout();
      return;
    }
    if (components?.some((component) => clickedComponents?.includes(component))) {
      return;
    }
    if (!components?.some((component) => hoveredComponents?.includes(component))) {
      clearHoverOpenTimeout();
    }
    hoveredComponents = components;
    if (components?.length) {
      openHoveredComponents(components);
    } else if (activeComponents?.some((component) => component?.open)) {
      closeHoveredComponents();
    }
    clickedComponents = null;
  };
  const blurHandler = () => {
    closeActiveHoverComponents();
  };
  const pointerLeaveHandler = (event) => {
    if (event.defaultPrevented) {
      return;
    }
    clearHoverTimeout();
    closeHoveredComponents();
  };
  const clickListener = (event) => {
    if (options.click) {
      clickHandler(event);
    }
    if (options.hover) {
      hoverClickHandler(event);
    }
  };
  const keyDownListener = (event) => {
    onReferenceElementKeyDown(event);
    if (options.click) {
      keyDownHandler(event);
    }
    if (options.hover) {
      hoverKeyDownHandler(event);
    }
  };
  const addListeners = () => {
    if (options.click || options.hover) {
      window.addEventListener("click", clickListener);
      window.addEventListener("keydown", keyDownListener);
    }
    if (options.click) {
      window.addEventListener("pointerdown", pointerDownHandler);
    }
    if (options.hover) {
      window.addEventListener("pointermove", pointerMoveHandler);
      window.addEventListener("focusin", focusInHandler);
      window.addEventListener("blur", blurHandler);
      document.addEventListener("pointerleave", pointerLeaveHandler);
    }
  };
  const removeListeners = () => {
    if (options.click || options.hover) {
      window.removeEventListener("click", clickListener);
      window.removeEventListener("keydown", keyDownListener);
    }
    if (options.click) {
      window.removeEventListener("pointerdown", pointerDownHandler);
    }
    if (options.hover) {
      window.removeEventListener("pointermove", pointerMoveHandler);
      window.removeEventListener("focusin", focusInHandler);
      window.removeEventListener("blur", blurHandler);
      document.removeEventListener("pointerleave", pointerLeaveHandler);
    }
  };
  const toggleFocusedComponents = (components, open) => {
    {
      clearHoverTimeout();
    }
    toggleHoverComponents(components, open);
  };
  const getReferenceElShadowRootNode = (referenceEl) => {
    return referenceEl instanceof Element ? getShadowRootNode(referenceEl) : null;
  };
  const focusInHandler = (event) => {
    if (event.defaultPrevented) {
      return;
    }
    const composedPath = event.composedPath();
    const components = queryComponents(composedPath, "hover");
    if (pathHasOpenHoverComponent(components, composedPath)) {
      clearHoverTimeout();
      return;
    }
    if (components?.some((component) => clickedComponents?.includes(component))) {
      return;
    }
    clickedComponents = null;
    closeComponentsIfNotActive(components);
    if (!components?.length) {
      return;
    }
    toggleFocusedComponents(components, true);
  };
  const updateElement = (component, referenceEl) => {
    if (!referenceEl || !component.referenceElementType) {
      return;
    }
    if (options.click && "ariaExpanded" in referenceEl) {
      const existingComponents = registeredElements.get(referenceEl) ?? [];
      const existingComponentOpen = existingComponents?.some((component2) => component2.open) ?? false;
      referenceEl.ariaExpanded = toAriaBoolean(component.open || existingComponentOpen);
    }
  };
  const addShadowListeners = (shadowRoot) => {
    shadowRoot.addEventListener("focusin", focusInHandler);
  };
  const removeShadowListeners = (shadowRoot) => {
    shadowRoot.removeEventListener("focusin", focusInHandler);
  };
  const registerShadowRoot = (shadowRoot) => {
    const count = registeredShadowRootCounts.get(shadowRoot);
    const newCount = (typeof count === "number" ? count : 0) + 1;
    if (newCount === 1) {
      addShadowListeners(shadowRoot);
    }
    registeredShadowRootCounts.set(shadowRoot, newCount);
  };
  const unregisterShadowRoot = (shadowRoot) => {
    const count = registeredShadowRootCounts.get(shadowRoot);
    const currentCount = typeof count === "number" ? count : 0;
    const newCount = Math.max(0, currentCount - 1);
    if (currentCount > 0 && newCount === 0) {
      removeShadowListeners(shadowRoot);
      registeredShadowRootCounts.delete(shadowRoot);
      return;
    }
    if (newCount > 0) {
      registeredShadowRootCounts.set(shadowRoot, newCount);
    }
  };
  const registerElement = (component, referenceEl) => {
    if (!referenceEl || !component.referenceElementType) {
      return;
    }
    const existingComponents = registeredElements.get(referenceEl) ?? [];
    if (existingComponents.includes(component)) {
      return;
    }
    if (options.click && "ariaControlsElements" in referenceEl) {
      const currentElements = referenceEl.ariaControlsElements ?? [];
      if (!currentElements.includes(component.el)) {
        const updatedElements = [...currentElements, component.el];
        referenceEl.ariaControlsElements = updatedElements;
      }
    }
    if (options.hover && "ariaDescribedByElements" in referenceEl) {
      const currentElements = referenceEl.ariaDescribedByElements ?? [];
      if (!currentElements.includes(component.el)) {
        const updatedElements = [...currentElements, component.el];
        referenceEl.ariaDescribedByElements = updatedElements;
      }
    }
    registeredComponentCount++;
    registeredElements.set(referenceEl, [...existingComponents, component]);
    const shadowRoot = options.hover ? getReferenceElShadowRootNode(referenceEl) : null;
    if (shadowRoot) {
      registerShadowRoot(shadowRoot);
    }
    if (registeredComponentCount === 1) {
      addListeners();
    }
    updateElement(component, referenceEl);
  };
  const decrementRegisteredElementCount = (shadowRoot) => {
    registeredComponentCount--;
    if (shadowRoot) {
      unregisterShadowRoot(shadowRoot);
    }
  };
  const unregisterElement = (component, referenceEl) => {
    if (!referenceEl || !component.referenceElementType) {
      return;
    }
    const shadowRoot = options.hover ? getReferenceElShadowRootNode(referenceEl) : null;
    const existingComponents = registeredElements.get(referenceEl) ?? [];
    const updatedComponents = existingComponents.filter((p) => p !== component);
    if (updatedComponents.length > 0) {
      registeredElements.set(referenceEl, updatedComponents);
      if (updatedComponents.length !== existingComponents.length) {
        decrementRegisteredElementCount(shadowRoot);
      }
    } else if (registeredElements.delete(referenceEl)) {
      decrementRegisteredElementCount(shadowRoot);
    }
    if (registeredComponentCount === 0) {
      removeListeners();
      clearHoverTimeout();
    }
    if (options.click && "ariaControlsElements" in referenceEl) {
      const newElements = referenceEl.ariaControlsElements?.filter((element) => element !== component.el);
      referenceEl.ariaControlsElements = newElements?.length > 0 ? newElements : null;
    }
    if (options.click && "ariaExpanded" in referenceEl) {
      const hasRegisteredComponents = (updatedComponents?.length ?? 0) > 0;
      if (hasRegisteredComponents) {
        const existingComponentOpen = updatedComponents?.some((component2) => component2.open) ?? false;
        referenceEl.ariaExpanded = toAriaBoolean(existingComponentOpen);
      } else {
        referenceEl.ariaExpanded = null;
      }
    }
    if (options.hover && "ariaDescribedByElements" in referenceEl) {
      const newElements = referenceEl.ariaDescribedByElements?.filter((element) => element !== component.el);
      referenceEl.ariaDescribedByElements = newElements?.length > 0 ? newElements : null;
    }
  };
  return {
    registerElement,
    unregisterElement,
    updateElement
  };
};
export {
  referenceElementManager as r,
  useReferenceElement as u
};
