/**
 * `<Menu.Positioner>` — anchors the popup to the trigger via floating-ui.
 * Mirrors `packages/react/src/menu/positioner/MenuPositioner.tsx`.
 *
 * Renders no visible UI — wraps the popup in a floating-ui positioned
 * `<div>`. Installs an `InternalBackdrop` when modal. Installs a
 * `CompositeList` so items + submenu-triggers register into the root's
 * `listRef` / `labelsRef` for keyboard navigation + typeahead.
 *
 * ### Port deviations
 *
 *  - `useAnchorPositioning`'s full collision-avoidance / sticky / viewport
 *    integration is not wired — uses low-level `useFloating` + flip/shift/
 *    size/arrow like `<Popover.Positioner>`. Full math needs browser
 *    coverage.
 */

import { flip, offset, shift, size } from "@floating-ui/dom";
import { dynamic } from "@solidjs/web";
import { createEffect, createMemo, createSignal, omit, onCleanup, Show, untrack } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { composeRefs, userRef } from "../../internals/compose-refs.jsx";
import { CompositeList } from "../../internals/composite/composite-list.jsx";
import { FloatingNode, useFloatingNodeId } from "../../internals/floating-ui/components/floating-tree.jsx";
import { useFloating } from "../../internals/floating-ui/hooks/use-floating.jsx";
import { arrow } from "../../internals/floating-ui/middleware/arrow.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { InternalBackdrop } from "../../internals/popups/internal-backdrop.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { Timeout } from "../../internals/use-timeout.jsx";
import { useMenubarContext } from "../../menubar/menubar-context.jsx";
import { useMenuPortalContext } from "../portal/menu-portal-context.jsx";
import { useMenuRootContext } from "../root/menu-root-context.jsx";
import { MenuPositionerContext } from "./menu-positioner-context.jsx";
function composePlacement(side, align) {
  const physicalSide = side === "inline-start" ? "left" : side === "inline-end" ? "right" : side;
  if (align === "center") return physicalSide;
  return `${physicalSide}-${align}`;
}
function parsePlacement(placement) {
  const [rawSide, rawAlign] = placement.split("-");
  return {
    side: rawSide,
    align: rawAlign ?? "center"
  };
}
export function MenuPositioner(props) {
  const root = useMenuRootContext();
  const menubar = useMenubarContext(true);
  const nodeId = useFloatingNodeId();
  const keepMounted = useMenuPortalContext();
  const nested = () => root.nested();
  const side = () => props.side ?? (nested() ? "inline-end" : "bottom");
  const align = () => props.align ?? (nested() ? "start" : "start");
  const sideOffset = () => props.sideOffset ?? 0;
  const alignOffset = () => props.alignOffset ?? 0;
  const arrowPadding = () => props.arrowPadding ?? 5;
  const collisionPadding = () => props.collisionPadding ?? 5;
  const strategy = () => props.positionMethod ?? "absolute";
  const arrowRef = {
    current: null
  };
  const floating = useFloating({
    rootContext: root.floatingRootContext,
    nodeId,
    get placement() {
      return composePlacement(side(), align());
    },
    get strategy() {
      return strategy();
    },
    get middleware() {
      return [offset({
        mainAxis: sideOffset(),
        alignmentAxis: alignOffset()
      }), flip({
        padding: collisionPadding()
      }), shift({
        padding: collisionPadding()
      }), size({
        padding: collisionPadding(),
        apply({
          rects,
          availableWidth,
          availableHeight,
          elements: els
        }) {
          const s = els.floating.style;
          s.setProperty("--anchor-width", `${rects.reference.width}px`);
          s.setProperty("--anchor-height", `${rects.reference.height}px`);
          s.setProperty("--available-width", `${availableWidth}px`);
          s.setProperty("--available-height", `${availableHeight}px`);
        }
      }), arrow({
        element: arrowRef.current,
        padding: arrowPadding(),
        offsetParent: "real"
      })];
    }
  });
  let positionerNode = null;
  const setPositionerElement = el => {
    positionerNode = el;
    // opacity:0 before first paint (ref runs during DOM construction); the
    // reveal effect can run after the first paint in Solid 2. See
    // popover-positioner.
    if (el) el.style.opacity = "0";
    floating.refs.setFloating(el);
    root.positionerRef.current = el;
  };

  // Solid 2 DOM spread misses the first `floating.x`/`y` frame —
  // imperatively mirror the transform so the popup doesn't flash at (0, 0).
  // Transform + reveal in ONE imperative write so the popup is never visible
  // at the stale (0,0) transform (the teleport). `opacity:0` until
  // `isPositioned`, matching Base UI's `useAnchorPositioning`. See
  // popover-positioner for the full rationale.
  createEffect(() => {
    if (!root.mounted()) return null;
    return {
      transform: `translate(${Math.round(floating.x)}px, ${Math.round(floating.y)}px)`,
      positioned: floating.isPositioned || !root.open()
    };
  }, data => {
    if (!data || !positionerNode) return;
    positionerNode.style.transform = data.transform;
    positionerNode.style.opacity = data.positioned ? "" : "0";
  });
  const computedSideAlign = createMemo(() => parsePlacement(floating.placement));
  const computedSide = () => computedSideAlign().side;
  const computedAlign = () => computedSideAlign().align;
  const [anchorHidden] = createSignal(false);
  const arrowStyles = createMemo(() => {
    const data = floating.middlewareData.arrow;
    if (!data) return {
      position: "absolute"
    };
    const styles = {
      position: "absolute"
    };
    if (data.x != null) styles.left = `${data.x}px`;
    if (data.y != null) styles.top = `${data.y}px`;
    return styles;
  });
  const arrowUncentered = createMemo(() => {
    const data = floating.middlewareData.arrow;
    return (data?.centerOffset ?? 0) !== 0;
  });
  const positionerContext = {
    side: computedSide,
    align: computedAlign,
    anchorHidden,
    arrowRef,
    arrowStyles,
    arrowUncentered,
    nodeId: () => nodeId
  };
  const state = createMemo(() => ({
    open: root.open(),
    side: computedSide(),
    align: computedAlign(),
    anchorHidden: anchorHidden(),
    nested: nested(),
    instant: root.instantType()
  }));
  createEffect(() => root.mounted(), () => {
    floating.update();
  });

  // Close this (sub)menu when a different item in its parent menu is
  // hovered, so hovering a sibling closes other branches. Re-hovering this
  // menu's own trigger cancels a pending delayed close. Mirrors React's
  // `itemhover` listener in `MenuPositioner`.
  const closeTimeout = Timeout.create();
  const tree = root.floatingTreeRoot();
  if (tree != null) {
    const onItemHover = event => {
      const parentNodeId = untrack(root.floatingParentNodeId);
      if (!untrack(root.open) || event.nodeId !== parentNodeId) return;
      const trigger = untrack(root.activeTriggerElement);
      if (event.target == null || trigger == null || trigger === event.target) {
        closeTimeout.clear();
        return;
      }
      const close = () => root.setOpen(false, createChangeEventDetails(REASONS.siblingOpen));
      const delay = root.closeDelayRef.current;
      if (delay <= 0) close();else if (!closeTimeout.isStarted()) closeTimeout.start(delay, close);
    };
    tree.events.on("itemhover", onItemHover);
    onCleanup(() => tree.events.off("itemhover", onItemHover));
  }
  onCleanup(() => closeTimeout.clear());
  createEffect(() => root.open(), isOpen => {
    if (!isOpen) closeTimeout.clear();
  });

  // `transform` is written imperatively in the `createEffect` above
  // because Solid 2's DOM spread subscribes `get style()` late and misses
  // the first `floating.x` / `floating.y` frame.
  const positionerStyle = createMemo(() => {
    const openNow = root.open();
    const mountedNow = root.mounted();
    return {
      position: strategy(),
      top: "0",
      left: "0",
      visibility: mountedNow ? undefined : "hidden",
      "pointer-events": openNow ? undefined : "none"
    };
  });

  // Inside a menubar the menubar's `modal` decides, and the backdrop cuts
  // out the whole menubar so its other triggers stay clickable.
  const shouldRenderBackdrop = createMemo(() => root.mounted() && !nested() && (menubar != null ? menubar.modal() : root.modal()));
  const backdropCutout = () => (menubar != null ? menubar.contentElement() : root.activeTriggerElement()) ?? undefined;
  const internal = {
    get style() {
      return positionerStyle();
    },
    get hidden() {
      return !root.open();
    },
    get "data-open"() {
      return state().open ? "" : undefined;
    },
    get "data-closed"() {
      return state().open ? undefined : "";
    },
    get "data-side"() {
      return state().side;
    },
    get "data-align"() {
      return state().align;
    },
    get "data-anchor-hidden"() {
      return state().anchorHidden ? "" : undefined;
    },
    get "data-nested"() {
      return state().nested ? "" : undefined;
    },
    get "data-instant"() {
      return state().instant ? "" : undefined;
    }
  };
  const userClassStyle = resolveClassStyle(props, () => state());
  const userProps = omit(props, "as", "class", "style", "attrs", "side", "align", "sideOffset", "alignOffset", "arrowPadding", "collisionPadding", "positionMethod");
  let merged = mergeRootProps(internal, userClassStyle);
  merged = mergeRootProps(merged, userProps);
  const Tag = dynamic(() => props.as ?? "div");
  return <FloatingNode id={nodeId}>
			<MenuPositionerContext value={positionerContext}>
				<Show when={root.mounted() || keepMounted()}>
					<CompositeList elementsRef={root.listRef.current} labelsRef={root.labelsRef.current}>
						<Show when={shouldRenderBackdrop()}>
							<InternalBackdrop inert={!root.open()} cutout={backdropCutout()} />
						</Show>
						<Tag {...merged} ref={composeRefs(setPositionerElement, userRef(props))} />
					</CompositeList>
				</Show>
			</MenuPositionerContext>
		</FloatingNode>;
}
//# sourceMappingURL=menu-positioner.jsx.map
