/**
 * Shared helpers for the composite primitives. These mirror the small
 * list-navigation utilities in `packages/react/src/floating-ui-react/utils/
 * composite.ts` + the input / scroll helpers in
 * `packages/react/src/internals/composite/composite.ts`.
 *
 * The 1D list primitives are all the non-floating consumers (toolbar, tabs,
 * radio-group, toggle-group, slider) need. Grid helpers are intentionally not
 * here — the grid implementation lives in `floating-ui/utils/composite.ts`
 * (`getGridNavigatedIndex`), used by the floating path for menu / select /
 * combobox. This stays 1D unless a non-floating grid consumer surfaces.
 */

/**
 * `disabledIndices` input. Consumers may pass a frozen list or a predicate.
 */

/**
 * Read-only element list. We accept `ReadonlyArray` so call sites that hand us
 * a mutable backing array (the `elementsRef` plain array) still type-check.
 */

export function isIndexOutOfListBounds(list, index) {
  return index < 0 || index >= list.length;
}

/**
 * Best-effort visibility check. Uses `checkVisibility()` where available
 * (Chromium 105+, Firefox 125+) and falls back to a computed-style probe.
 */
export function isElementVisible(element) {
  if (typeof element.checkVisibility === "function") {
    return element.checkVisibility();
  }
  const owner = element.ownerDocument?.defaultView;
  if (!owner) return true;
  return owner.getComputedStyle(element).display !== "none";
}
export function isListIndexDisabled(list, index, disabledIndices) {
  const isExplicitlyDisabled = typeof disabledIndices === "function" ? disabledIndices(index) : disabledIndices?.includes(index) ?? false;
  if (isExplicitlyDisabled) return true;
  const element = list[index];
  if (!element) return false;
  if (!isElementVisible(element)) return true;
  return !disabledIndices && (element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true");
}

/**
 * Walk the list looking for the next non-disabled index. `startingIndex` is
 * exclusive (we advance before the first check) — matching React Base UI's
 * implementation so consumers can port semantics unchanged.
 */
export function findNonDisabledListIndex(list, options = {}) {
  const {
    startingIndex = -1,
    decrement = false,
    disabledIndices,
    amount = 1
  } = options;
  let index = startingIndex;
  do {
    index += decrement ? -amount : amount;
  } while (index >= 0 && index <= list.length - 1 && isListIndexDisabled(list, index, disabledIndices));
  return index;
}
export function getMinListIndex(list, disabledIndices) {
  return findNonDisabledListIndex(list, {
    disabledIndices
  });
}
export function getMaxListIndex(list, disabledIndices) {
  return findNonDisabledListIndex(list, {
    decrement: true,
    startingIndex: list.length,
    disabledIndices
  });
}

/**
 * Sort comparator for `compareDocumentPosition`. Uses the "following"/
 * "preceding" bits so nodes render in DOM order regardless of registration
 * sequence.
 */
export function sortByDocumentPosition(a, b) {
  const position = a.compareDocumentPosition(b);
  const follows = (position & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 || (position & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0;
  if (follows) return -1;
  const precedes = (position & Node.DOCUMENT_POSITION_PRECEDING) !== 0 || (position & Node.DOCUMENT_POSITION_CONTAINS) !== 0;
  if (precedes) return 1;
  return 0;
}

/**
 * Is the target a native text input? Used to defer arrow-key handling to the
 * browser when the cursor is inside a textbox.
 */
export function isNativeInput(element) {
  if (element == null) return false;
  const el = element;
  if (el.tagName === "INPUT") {
    const input = el;
    return input.selectionStart != null;
  }
  if (el.tagName === "TEXTAREA") return true;
  return false;
}

/**
 * Read-only view of `HTMLElement.disabled` / `aria-disabled` for targets that
 * may not have the `disabled` DOM property (e.g. links).
 */
export function isElementDisabled(element) {
  if (element == null || !(element instanceof Element)) return false;
  const el = element;
  if (el.disabled === true) return true;
  return el.getAttribute("aria-disabled") === "true";
}

/**
 * Scroll `element` into view inside `scrollContainer` when it's outside the
 * visible rectangle, respecting `direction` and `orientation`. The logic is a
 * direct port of Base UI React's `scrollIntoViewIfNeeded`.
 */
export function scrollIntoViewIfNeeded(scrollContainer, element, direction, orientation) {
  if (!scrollContainer || !element?.scrollTo) {
    return;
  }
  let targetX = scrollContainer.scrollLeft;
  let targetY = scrollContainer.scrollTop;
  const isOverflowingX = scrollContainer.clientWidth < scrollContainer.scrollWidth;
  const isOverflowingY = scrollContainer.clientHeight < scrollContainer.scrollHeight;
  if (isOverflowingX && orientation !== "vertical") {
    const elementOffsetLeft = getOffset(scrollContainer, element, "left");
    const containerStyles = getStyles(scrollContainer);
    const elementStyles = getStyles(element);
    if (direction === "ltr") {
      if (elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight > scrollContainer.scrollLeft + scrollContainer.clientWidth - containerStyles.scrollPaddingRight) {
        targetX = elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight - scrollContainer.clientWidth + containerStyles.scrollPaddingRight;
      } else if (elementOffsetLeft - elementStyles.scrollMarginLeft < scrollContainer.scrollLeft + containerStyles.scrollPaddingLeft) {
        targetX = elementOffsetLeft - elementStyles.scrollMarginLeft - containerStyles.scrollPaddingLeft;
      }
    }
    if (direction === "rtl") {
      if (elementOffsetLeft - elementStyles.scrollMarginRight < scrollContainer.scrollLeft + containerStyles.scrollPaddingLeft) {
        targetX = elementOffsetLeft - elementStyles.scrollMarginLeft - containerStyles.scrollPaddingLeft;
      } else if (elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight > scrollContainer.scrollLeft + scrollContainer.clientWidth - containerStyles.scrollPaddingRight) {
        targetX = elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight - scrollContainer.clientWidth + containerStyles.scrollPaddingRight;
      }
    }
  }
  if (isOverflowingY && orientation !== "horizontal") {
    const elementOffsetTop = getOffset(scrollContainer, element, "top");
    const containerStyles = getStyles(scrollContainer);
    const elementStyles = getStyles(element);
    if (elementOffsetTop - elementStyles.scrollMarginTop < scrollContainer.scrollTop + containerStyles.scrollPaddingTop) {
      targetY = elementOffsetTop - elementStyles.scrollMarginTop - containerStyles.scrollPaddingTop;
    } else if (elementOffsetTop + element.offsetHeight + elementStyles.scrollMarginBottom > scrollContainer.scrollTop + scrollContainer.clientHeight - containerStyles.scrollPaddingBottom) {
      targetY = elementOffsetTop + element.offsetHeight + elementStyles.scrollMarginBottom - scrollContainer.clientHeight + containerStyles.scrollPaddingBottom;
    }
  }
  scrollContainer.scrollTo({
    left: targetX,
    top: targetY,
    behavior: "auto"
  });
}
function getOffset(ancestor, element, side) {
  const propName = side === "left" ? "offsetLeft" : "offsetTop";
  let current = element;
  let result = 0;
  while (current.offsetParent) {
    result += current[propName];
    if (current.offsetParent === ancestor) break;
    current = current.offsetParent;
  }
  return result;
}
function getStyles(element) {
  const owner = element.ownerDocument?.defaultView;
  if (!owner) {
    return {
      scrollMarginTop: 0,
      scrollMarginRight: 0,
      scrollMarginBottom: 0,
      scrollMarginLeft: 0,
      scrollPaddingTop: 0,
      scrollPaddingRight: 0,
      scrollPaddingBottom: 0,
      scrollPaddingLeft: 0
    };
  }
  const styles = owner.getComputedStyle(element);
  return {
    scrollMarginTop: Number.parseFloat(styles.scrollMarginTop) || 0,
    scrollMarginRight: Number.parseFloat(styles.scrollMarginRight) || 0,
    scrollMarginBottom: Number.parseFloat(styles.scrollMarginBottom) || 0,
    scrollMarginLeft: Number.parseFloat(styles.scrollMarginLeft) || 0,
    scrollPaddingTop: Number.parseFloat(styles.scrollPaddingTop) || 0,
    scrollPaddingRight: Number.parseFloat(styles.scrollPaddingRight) || 0,
    scrollPaddingBottom: Number.parseFloat(styles.scrollPaddingBottom) || 0,
    scrollPaddingLeft: Number.parseFloat(styles.scrollPaddingLeft) || 0
  };
}
//# sourceMappingURL=composite-utils.jsx.map
