/**
 * Stage 4.2 port of Base UI React's `useRole` hook.
 *
 * React source:
 *   `base-ui/packages/react/src/floating-ui-react/hooks/useRole.ts`
 *
 * Adds base screen-reader props to the reference / floating elements for a
 * given floating-element `role`. Mostly a pure getter — the only reactive
 * surface is the `open` / `floatingId` / `floatingElement` / `domReferenceElement`
 * reads off the root store, which Solid consumers pick up through
 * `store.useState(...)` when this hook is invoked inside a tracking scope.
 *
 * ### React → Solid deviations
 *
 * 1. React memoises each prop bag with `useMemo` keyed on `[ariaRole, open,
 *    floatingId, referenceId, role, ...]` so identity is stable across
 *    renders. Solid components don't re-run their bodies, so the getters
 *    are plain object literals — identity is stable for the lifetime of
 *    the call site already. Consumers that need reactivity on `open` /
 *    `floatingId` route through `mergePropsN`'s prop-getter pipeline,
 *    which re-reads these values at event-time.
 *
 * 2. Since the bag is built once during setup and re-used, the dynamic
 *    fields (`aria-expanded`, `aria-controls`, `aria-describedby`) are
 *    computed inline by reading `store.useState('open')`. Because every
 *    call to `useRole(...)` is invoked inside a consumer scope that reads
 *    these props through `useInteractions`'s `mergePropsN` at render time,
 *    Solid's fine-grained DOM updates will re-read the getter on every
 *    commit. To keep the ARIA contract 1:1 without over-subscribing, we
 *    expose these as plain lookups on the store — Solid's JSX spread
 *    observes the lookup during render and re-runs it when the store
 *    signal fires.
 *
 * 3. `useId` in React → `useBaseUiId` in Solid.
 */
import { untrack } from "solid-js";
import { useBaseUiId } from "../../base-ui-id.jsx";
import { useFloatingParentNodeId } from "../components/floating-tree.jsx";
import { getFloatingFocusElement } from "../utils/element.jsx";
const componentRoleToAriaRoleMap = new Map([["select", "listbox"], ["combobox", "listbox"], ["label", false]]);
const EMPTY_OBJECT = Object.freeze({});

/**
 * Adds base screen reader props to the reference and floating elements for a
 * given floating element `role`.
 * @see https://floating-ui.com/docs/useRole
 */
export function useRole(context, props = {}) {
  const store = "rootStore" in context ? context.rootStore : context;

  // Untrack the body-time `role` read so a parent passing a
  // signal-derived value via JSX does not fire `[STRICT_READ_UNTRACKED]`
  // in the strict-read scope set up by `devComponent(...)`. `role` is
  // not declared as `MaybeAccessor` — it's a snapshot value matching
  // the React fork's semantics.
  const {
    role = "dialog"
  } = untrack(() => ({
    role: props.role
  }));
  const defaultReferenceId = useBaseUiId();
  const ariaRole = componentRoleToAriaRoleMap.get(role) ?? role;
  const parentId = useFloatingParentNodeId();
  const isNested = parentId != null;

  // Reactive lookups. Each call reads from the store mirror; when
  // `mergePropsN` (via `useInteractions`) spreads these into JSX, Solid's
  // render-effect subscribes to the store signal and re-evaluates on
  // each change.
  const getOpen = () => store.state.open;
  const getFloatingId = () => {
    const floatingElement = store.state.floatingElement;
    const defaultFloatingId = store.state.floatingId;
    return getFloatingFocusElement(floatingElement)?.id ?? defaultFloatingId;
  };
  const getReferenceId = () => {
    const domReference = store.state.domReferenceElement;
    return domReference?.id || defaultReferenceId;
  };
  const triggerProps = (() => {
    if (ariaRole === "tooltip" || role === "label") {
      return EMPTY_OBJECT;
    }
    return {
      "aria-haspopup": ariaRole === "alertdialog" ? "dialog" : ariaRole,
      "aria-expanded": "false",
      ...(ariaRole === "listbox" && {
        role: "combobox"
      }),
      ...(ariaRole === "menu" && isNested && {
        role: "menuitem"
      }),
      ...(role === "select" && {
        "aria-autocomplete": "none"
      }),
      ...(role === "combobox" && {
        "aria-autocomplete": "list"
      })
    };
  })();
  const reference = (() => {
    if (ariaRole === "tooltip" || role === "label") {
      // Describedby / labelledby swap — seed with "" when closed (Solid 2's
      // JSX spread cannot subscribe to keys that start undefined, so we
      // emit an empty string).
      return {
        get [`aria-${role === "label" ? "labelledby" : "describedby"}`]() {
          return getOpen() ? getFloatingId() : undefined;
        }
      };
    }
    const refResult = {
      ...triggerProps,
      get "aria-expanded"() {
        return getOpen() ? "true" : "false";
      },
      get "aria-controls"() {
        return getOpen() ? getFloatingId() : undefined;
      }
    };
    // `{...(condition && {get id() {...}})}` LOSES the getter (object
    // spread invokes getters and copies values), which both breaks
    // reactivity AND fires `[STRICT_READ_UNTRACKED]` at this hook's
    // body-time strict-read scope. Define the getter directly so the
    // signal read happens lazily inside whatever JSX tracking scope
    // later consumes `reference.id`.
    if (ariaRole === "menu") {
      Object.defineProperty(refResult, "id", {
        enumerable: true,
        configurable: true,
        get: () => getReferenceId()
      });
    }
    return refResult;
  })();
  const floating = (() => {
    // Build with `Object.defineProperty` so the `id` getter is copied
    // as a descriptor, not invoked + recopied as a value (the latter
    // is what `{ ...base }` would do, breaking reactivity AND firing
    // `[STRICT_READ_UNTRACKED]` at this hook's body-time scope).
    const base = {};
    Object.defineProperty(base, "id", {
      enumerable: true,
      configurable: true,
      get: () => getFloatingId()
    });
    if (ariaRole) {
      base.role = ariaRole;
    }
    if (ariaRole === "tooltip" || role === "label") {
      return base;
    }
    if (ariaRole === "menu") {
      Object.defineProperty(base, "aria-labelledby", {
        enumerable: true,
        configurable: true,
        get: () => getReferenceId()
      });
      return base;
    }
    return base;
  })();
  const item = ({
    active,
    selected
  }) => {
    const commonProps = {
      role: "option"
    };
    // Use a getter so `getFloatingId()` is read lazily inside the JSX
    // tracking scope rather than at this object-build time (which is the
    // `<MenuRootComponent>` body for menu items via `useInteractions`).
    if (active) {
      Object.defineProperty(commonProps, "id", {
        enumerable: true,
        configurable: true,
        get: () => `${getFloatingId()}-fui-option`
      });
    }

    // For `menu`, we are unable to tell if the item is a `menuitemradio`
    // or `menuitemcheckbox`. For backwards-compatibility reasons, also
    // avoid defaulting to `menuitem` as it may overwrite custom role
    // props.
    switch (role) {
      case "select":
      case "combobox":
        return {
          ...commonProps,
          "aria-selected": selected
        };
      default:
    }
    return {};
  };
  return {
    reference,
    floating,
    item,
    trigger: triggerProps
  };
}
//# sourceMappingURL=use-role.jsx.map
