import { flip, offset, shift, size } from "@floating-ui/dom";
import { dynamic } from "@solidjs/web";
import { createEffect, createMemo, createSignal, omit, Show } from "solid-js";
import { composeRefs, userRef } from "../../internals/compose-refs.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 { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { useComboboxRootContext } from "../root/combobox-root-context.jsx";
import { ComboboxPositionerContext } from "./positioner-context.jsx";
function composePlacement(side, align) {
  if (align === "center") return side;
  return `${side}-${align}`;
}
function parsePlacement(placement) {
  const [rawSide, rawAlign] = placement.split("-");
  return {
    side: rawSide,
    align: rawAlign ?? "center"
  };
}

/**
 * Positions the popup against the input. Renders a `<div>` element.
 *
 * happy-dom returns 0 for all layout rects, so pixel-offset positioning
 * math is not unit-testable. Real browser coverage required.
 */
export function ComboboxPositioner(props) {
  const root = useComboboxRootContext();
  const nodeId = useFloatingNodeId();
  const side = () => props.side ?? "bottom";
  const align = () => props.align ?? "start";
  const sideOffsetValue = () => props.sideOffset ?? 0;
  const alignOffsetValue = () => 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: sideOffsetValue(),
        alignmentAxis: alignOffsetValue()
      }), 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.setPositionerElement(el);
  };

  // Solid 2's DOM spread subscribes `get style()` late, so the first
  // `floating.x` / `floating.y` write after mount can be missed and the popup
  // renders at (0, 0). Imperatively mirror the transform here — the
  // createEffect is guaranteed to re-run on every `x` / `y` change.
  // 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;

  // Keep root.popupSide in sync for sibling parts.
  createEffect(() => computedSide(), s => {
    root.setPopupSide(s);
  });
  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
  };
  const state = createMemo(() => ({
    open: root.open(),
    side: computedSide(),
    align: computedAlign(),
    anchorHidden: anchorHidden(),
    empty: root.flatFilteredItems().length === 0
  }));
  createEffect(() => root.mounted(), () => {
    floating.update();
  });

  // `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"
    };
  });
  const internal = {
    get hidden() {
      return !root.open();
    },
    // popupStateMapping projection.
    get "data-open"() {
      return state().open ? "" : undefined;
    },
    get "data-closed"() {
      return state().open ? undefined : "";
    },
    get "data-anchor-hidden"() {
      return state().anchorHidden ? "" : undefined;
    },
    get "data-side"() {
      return state().side;
    },
    get "data-align"() {
      return state().align;
    }
  };
  const internalStyle = {
    get style() {
      return positionerStyle();
    }
  };
  const userClassStyle = resolveClassStyle(props, () => state());
  const userProps = omit(props, "as", "class", "style", "attrs", "side", "align", "sideOffset", "alignOffset", "arrowPadding", "collisionPadding", "positionMethod");
  let merged = mergeRootProps(internalStyle, internal);
  merged = mergeRootProps(merged, userClassStyle);
  merged = mergeRootProps(merged, userProps);
  const Tag = dynamic(() => props.as ?? "div");
  return <FloatingNode id={nodeId}>
			<ComboboxPositionerContext value={positionerContext}>
				<Show when={root.mounted()}>
					<Tag {...merged} ref={composeRefs(setPositionerElement, userRef(props))} />
				</Show>
			</ComboboxPositionerContext>
		</FloatingNode>;
}
//# sourceMappingURL=combobox-positioner.jsx.map
