/**
 * `<Select.Root>` — Solid 2 port of Base UI React's `SelectRoot`
 * (`packages/react/src/select/root/SelectRoot.tsx`).
 *
 * Owns the select's open/value state (controlled + uncontrolled), wires
 * `useInteractions` with click + dismiss + list-navigation + typeahead,
 * manages highlighting (`activeIndex`) + selection (`selectedIndex`)
 * independently, and writes a hidden `<input>` (or multiple hidden inputs
 * when `multiple`) for native form participation.
 *
 * ### React → Solid deviations
 *
 * - The React source's store + selectors pair is replaced by plain
 *   signals — Solid's fine-grained reactivity + context gives the same
 *   subscribe-where-you-care ergonomics. The signals live under the root
 *   context.
 * - `useOpenInteractionType` is not yet wired; `openMethod` is omitted
 *   from the port (no touch/keyboard differentiation inside the popup).
 *   Deferred along with the align-item-with-trigger math.
 * - `alignItemWithTrigger` popup trigger-aligned placement is deferred —
 *   the complex scroll-arrow + max-height geometry isn't exercisable in
 *   happy-dom.
 * - `useOnFirstRender` stamping of `popupProps` / `triggerProps` maps to
 *   the root context exposing interaction getters directly — parts read
 *   them inside their own reactive scopes.
 */

import { createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js";
import { useBaseUiId } from "../../internals/base-ui-id.jsx";
import { createChangeEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { createControlledState } from "../../internals/create-controlled-state.jsx";
import { FloatingTree, useFloatingParentNodeId } from "../../internals/floating-ui/components/floating-tree.jsx";
import { useClick } from "../../internals/floating-ui/hooks/use-click.jsx";
import { useDismiss } from "../../internals/floating-ui/hooks/use-dismiss.jsx";
import { useFloatingRootContext } from "../../internals/floating-ui/hooks/use-floating-root-context.jsx";
import { useInteractions } from "../../internals/floating-ui/hooks/use-interactions.jsx";
import { useListNavigation } from "../../internals/floating-ui/hooks/use-list-navigation.jsx";
import { useTypeahead } from "../../internals/floating-ui/hooks/use-typeahead.jsx";
import { readOption } from "../../internals/maybe-accessor.jsx";
import { useOpenChangeComplete } from "../../internals/popups/use-open-change-complete.jsx";
import { useOpenInteractionType } from "../../internals/popups/use-open-interaction-type.jsx";
import { useTransitionStatus } from "../../internals/use-transition-status/use-transition-status.jsx";
import { VISUALLY_HIDDEN_STYLE } from "../../internals/visually-hidden-style.jsx";
import { createSelect } from "../create-select.jsx";
import { SelectRootContext, useSelectRootContext } from "../select-root-context.jsx";
import { defaultItemEquality, findItemIndex } from "../utils/item-equality.jsx";
import { stringifyAsLabel, stringifyAsValue } from "../utils/resolve-value-label.jsx";

/**
 * Imperative actions written into `actionsRef` on `<Select.Root>`. Mirrors
 * React's `SelectRoot.Actions` — only `unmount()` is exposed (close is
 * reserved via `onOpenChange`).
 */

function SelectRootComponent(props) {
  const multiple = () => props.multiple ?? false;
  const modal = () => props.modal ?? true;
  const disabled = () => readOption(props.disabled) ?? false;
  const readOnly = () => readOption(props.readOnly) ?? false;
  const required = () => readOption(props.required) ?? false;
  const highlightItemOnHover = () => props.highlightItemOnHover ?? true;
  const itemToStringLabel = () => props.itemToStringLabel;
  const itemToStringValue = () => props.itemToStringValue;
  const isItemEqualToValue = () => props.isItemEqualToValue ?? defaultItemEquality;
  const items = () => readOption(props.items);
  const name = () => props.name;
  const generatedId = useBaseUiId(props.id);

  // Controlled / uncontrolled state (Base UI `useControlled` contract,
  // applied to BOTH axes like React's `SelectRoot`):
  // - `value` is controlled while its resolved value is not `undefined` —
  //   `value={null}` IS controlled (Base UI semantics). The uncontrolled
  //   fallback is `null`; the createSelect primitive normalizes `null` to
  //   `[]` in multiple mode (matching React's
  //   `multiple ? defaultValue ?? EMPTY_ARRAY : defaultValue`).
  // - `open` is controlled while its resolved value is not `undefined`,
  //   otherwise backed by an internal signal seeded from `defaultOpen`.
  const [resolvedValue, setUncontrolledValue] = createControlledState({
    controlled: () => readOption(props.value),
    default: () => readOption(props.defaultValue),
    fallback: null,
    name: "<Select.Root>",
    state: "value"
  });
  const [resolvedOpen, setUncontrolledOpen] = createControlledState({
    controlled: () => {
      const controlled = readOption(props.open);
      return controlled === undefined ? undefined : Boolean(controlled);
    },
    default: () => readOption(props.defaultOpen),
    fallback: false,
    name: "<Select.Root>",
    state: "open"
  });

  // Logical state delegated to the createSelect primitive.
  // The primitive owns: value/open normalisation + isItemSelected + valueLabel.
  // The wrapper adds: floating-ui, keyboard nav, hidden form inputs, scroll arrows.
  const _primitive = createSelect({
    value: resolvedValue,
    open: resolvedOpen,
    multiple: props.multiple,
    disabled: () => Boolean(readOption(props.disabled)),
    readOnly: () => Boolean(readOption(props.readOnly)),
    required: () => Boolean(readOption(props.required)),
    items: () => readOption(props.items),
    itemToStringLabel: () => props.itemToStringLabel,
    itemToStringValue: () => props.itemToStringValue,
    isItemEqualToValue: () => props.isItemEqualToValue
  });

  // Value — reads from the primitive (handles multi-select empty-array default).
  const value = _primitive.value;

  // Open — reads from the primitive.
  const open = _primitive.open;

  // Highlighted + selected index. They're independent — the highlight
  // drives keyboard navigation whereas the selection reflects the stored
  // value.
  const [activeIndex, setActiveIndex] = createSignal(null, {
    ownedWrite: true
  });
  const [selectedIndex, setSelectedIndex] = createSignal(null, {
    ownedWrite: true
  });

  // Label id — wired up via useRegisteredLabelId when Select.Label lands.
  const [labelId, setLabelId] = createSignal(undefined, {
    ownedWrite: true
  });

  // Element slots.
  const [triggerElement, setTriggerElement] = createSignal(null, {
    ownedWrite: true
  });
  const [listElement, setListElement] = createSignal(null, {
    ownedWrite: true
  });
  const popupRef = {
    current: null
  };
  const valueRef = {
    current: null
  };
  const selectedItemTextRef = {
    current: null
  };
  const listRef = {
    current: []
  };
  const labelsRef = {
    current: []
  };
  // biome-ignore lint/suspicious/noExplicitAny: values registry indexes by position; typed as `any` to match React's surface.
  const valuesRef = {
    current: []
  };
  const typingRef = {
    current: false
  };
  // biome-ignore lint/suspicious/noExplicitAny: initial value holder mirrors React's ref.
  const initialValueRef = {
    current: untrack(value)
  };

  // Mount / transition machinery.
  const {
    mounted,
    transitionStatus,
    setMounted
  } = useTransitionStatus(open, () => true, () => false);

  // Scroll-arrow visibility signals — written by the popup's trigger-aligned
  // geometry effect.
  const [scrollUpArrowVisible, setScrollUpArrowVisible] = createSignal(false, {
    ownedWrite: true
  });
  const [scrollDownArrowVisible, setScrollDownArrowVisible] = createSignal(false, {
    ownedWrite: true
  });
  const scrollArrowsMountedCountRef = {
    current: 0
  };

  // Filled by the popup's geometry effect on initial placement; called from
  // items / positioner to re-probe scroll edges after list mutations. React
  // stores this on `handleScrollArrowVisibility` via `useStableCallback`; we
  // use a plain ref so updates don't require re-running consumers.
  const scrollHandlerRef = {
    current: () => {}
  };
  const handleScrollArrowVisibilityRef = {
    current: () => {}
  };
  const handleScrollArrowVisibility = () => {
    handleScrollArrowVisibilityRef.current();
  };

  // Most-recent-interaction flags (keyboard vs. mouse). Written from the
  // popup's onKeyDown / onMouseMove + trigger's onKeyDown / onPointerMove.
  const keyboardActiveRef = {
    current: false
  };

  // Selection-window guard. Flips to `true` on a delay after open — mirrors
  // React's `SelectTrigger` behavior.
  const selectionRef = {
    current: {
      allowSelectedMouseUp: false,
      allowUnselectedMouseUp: false
    }
  };

  // Capture the interaction type that opened the popup. `openMethod` is
  // `null` while closed, `'mouse' | 'keyboard' | 'touch' | 'pen' | ''` after
  // the opening interaction.
  const {
    openMethod
  } = useOpenInteractionType(open);

  // Deferred unmount — waits for any CSS exit animation on the popup.
  useOpenChangeComplete({
    open,
    ref: popupRef,
    onComplete: () => {
      if (!open()) {
        setMounted(false);
        setActiveIndex(null);
        props.onOpenChangeComplete?.(false);
      } else {
        props.onOpenChangeComplete?.(true);
      }
    }
  });

  // Reset trigger-aligned state when the popup unmounts. Mirrors React's
  // `<Select.Positioner>` layout-effect which clears scroll-arrow flags
  // when `mounted` flips to false, and the trigger-side reset for
  // selectionRef when `open` flips to false.
  createEffect(() => mounted(), isMounted => {
    if (!isMounted) {
      setScrollUpArrowVisible(false);
      setScrollDownArrowVisible(false);
    }
  });
  createEffect(() => open(), isOpen => {
    if (!isOpen) {
      selectionRef.current = {
        allowSelectedMouseUp: false,
        allowUnselectedMouseUp: false
      };
    }
  });

  // Sync selectedIndex when open=false or the value changes — mirrors
  // React's `syncSelectedIndex` layout effect.
  createEffect(() => ({
    isOpen: open(),
    currentValue: value(),
    equals: isItemEqualToValue()
  }), ({
    isOpen,
    currentValue,
    equals
  }) => {
    if (isOpen) return;
    const registry = valuesRef.current;
    if (multiple()) {
      const cv = Array.isArray(currentValue) ? currentValue : [];
      if (cv.length === 0) {
        setSelectedIndex(null);
        return;
      }
      const last = cv[cv.length - 1];
      const idx = findItemIndex(registry, last, equals);
      setSelectedIndex(idx === -1 ? null : idx);
      return;
    }
    const idx = findItemIndex(registry, currentValue, equals);
    setSelectedIndex(idx === -1 ? null : idx);
  });

  // Setters — mirror Base UI's `SelectRoot` `setOpen` / `setValue`: fire
  // the `onXChange` callback, honor `details.cancel()`, then write the
  // uncontrolled state (a no-op for reads while the matching prop is
  // controlled — the consumer's signal write is the source of truth).
  const setOpen = (next, details) => {
    props.onOpenChange?.(next, details);
    if (details.isCanceled) return;
    setUncontrolledOpen(next);
  };
  const setValue = (next, details) => {
    props.onValueChange?.(next, details);
    if (details.isCanceled) return;
    setUncontrolledValue(next);
  };

  // Floating-UI integration.
  const floatingRootContext = useFloatingRootContext({
    open: untrack(open),
    onOpenChange: (nextOpen, details) => {
      setOpen(nextOpen, details);
    }
  });
  createEffect(() => ({
    openValue: open(),
    trigger: triggerElement(),
    status: transitionStatus()
  }), ({
    openValue,
    trigger,
    status
  }) => {
    floatingRootContext.update({
      open: openValue,
      referenceElement: trigger,
      domReferenceElement: trigger,
      transitionStatus: status
    });
  });

  // Interaction hooks. Each hook reads its accessor inputs once at body
  // time to seed local refs / signals — wrap in `untrack` so those
  // snapshot reads don't trip Solid 2's `[STRICT_READ_UNTRACKED]` shell
  // labelled with this component's name. Reactivity for subsequent
  // updates flows through the accessor closures the hooks retain.
  const click = untrack(() => useClick(floatingRootContext, {
    enabled: () => !readOnly() && !disabled(),
    event: "mousedown"
  }));
  const dismiss = untrack(() => useDismiss(floatingRootContext, {
    enabled: () => !readOnly() && !disabled(),
    bubbles: false
  }));
  const listNavigation = untrack(() => useListNavigation(floatingRootContext, {
    enabled: () => !readOnly() && !disabled(),
    listRef,
    activeIndex,
    selectedIndex,
    focusItemOnHover: highlightItemOnHover,
    onNavigate(nextActiveIndex) {
      // Retain the highlight while transitioning out — React's note.
      if (nextActiveIndex === null && !untrack(open)) return;
      setActiveIndex(nextActiveIndex);
    }
  }));
  const typeahead = untrack(() => useTypeahead(floatingRootContext, {
    enabled: () => !readOnly() && !disabled() && (open() || !multiple()),
    listRef: labelsRef,
    activeIndex,
    selectedIndex,
    onMatch(index) {
      if (untrack(open)) {
        setActiveIndex(index);
      } else {
        const nextValue = valuesRef.current[index];
        setValue(nextValue, createChangeEventDetails(REASONS.none));
      }
    },
    onTypingChange(typing) {
      typingRef.current = typing;
    }
  }));
  const interactions = untrack(() => useInteractions([click, dismiss, listNavigation, typeahead]));

  // actionsRef wiring.
  const handleUnmount = () => {
    setMounted(false);
    setActiveIndex(null);
  };
  const actions = {
    unmount: handleUnmount
  };
  const ref = props.actionsRef;
  if (typeof ref === "function") {
    ref(actions);
    onCleanup(() => ref(null));
  } else if (ref && typeof ref === "object") {
    ref.current = actions;
    onCleanup(() => {
      ref.current = null;
    });
  }

  // Hidden form input — single-select path.
  const serializedValue = createMemo(() => {
    const v = value();
    if (multiple() && Array.isArray(v) && v.length === 0) return "";
    return stringifyAsValue(v, itemToStringValue());
  });

  // For multi-select we render one hidden `<input>` per value (matching
  // React). This accessor returns the list of serialized strings.
  const multipleSerialized = createMemo(() => {
    const v = value();
    if (!multiple() || !Array.isArray(v)) return [];
    return v.map(x => stringifyAsValue(x, itemToStringValue()));
  });
  const hasMultipleSelection = createMemo(() => {
    const v = value();
    return multiple() && Array.isArray(v) && v.length > 0;
  });
  const hiddenInputName = () => multiple() ? undefined : name();

  // Browser autofill handling — mirrors React. When autocomplete writes a
  // string into the hidden input, try to map it back into a registered
  // item value (by stringified value OR by label).
  const handleAutofill = event => {
    const target = event.currentTarget;
    if (!target) return;
    const nextValue = target.value;
    if (multiple()) return; // Browser autofill only writes a single scalar.
    const registry = valuesRef.current;
    const matching = registry.find(candidate => {
      const candidateValue = stringifyAsValue(candidate, itemToStringValue());
      if (candidateValue.toLowerCase() === nextValue.toLowerCase()) return true;
      const candidateLabel = stringifyAsLabel(candidate, itemToStringLabel());
      if (candidateLabel.toLowerCase() === nextValue.toLowerCase()) return true;
      return false;
    });
    if (matching != null) {
      setValue(matching, createChangeEventDetails(REASONS.none, event));
    }
  };
  const setInputRef = el => {
    const r = props.inputRef;
    if (typeof r === "function") r(el);else if (r && typeof r === "object") r.current = el;
  };
  const contextValue = {
    open,
    mounted,
    transitionStatus,
    modal,
    multiple,
    disabled,
    readOnly,
    required,
    name,
    id: () => generatedId,
    labelId,
    setLabelId,
    value,
    activeIndex,
    setActiveIndex,
    selectedIndex,
    setSelectedIndex,
    highlightItemOnHover,
    isItemEqualToValue: isItemEqualToValue,
    itemToStringLabel: itemToStringLabel,
    itemToStringValue: itemToStringValue,
    items,
    setOpen,
    setValue,
    floatingRootContext,
    interactions,
    triggerElement,
    setTriggerElement,
    popupRef,
    listElement,
    setListElement,
    valueRef,
    selectedItemTextRef,
    listRef,
    labelsRef,
    valuesRef,
    typingRef,
    onOpenChangeComplete: props.onOpenChangeComplete,
    initialValueRef,
    openMethod,
    scrollUpArrowVisible,
    setScrollUpArrowVisible,
    scrollDownArrowVisible,
    setScrollDownArrowVisible,
    scrollArrowsMountedCountRef,
    handleScrollArrowVisibility,
    scrollHandlerRef,
    keyboardActiveRef,
    selectionRef,
    handleScrollArrowVisibilityRef
  };
  return <SelectRootContext value={contextValue}>
			{props.children}
			<input type="hidden" ref={setInputRef} id={generatedId && hiddenInputName() == null ? `${generatedId}-hidden-input` : undefined} form={props.form} name={hiddenInputName()} autocomplete={props.autoComplete} value={multiple() ? "" : serializedValue()} disabled={disabled()} required={required() && !hasMultipleSelection()} readonly={readOnly()} style={{
      ...VISUALLY_HIDDEN_STYLE
    }} tabindex={-1} aria-hidden="true" onFocus={() => {
      // Move focus to the trigger when the hidden input is focused.
      const trigger = triggerElement();
      trigger?.focus();
    }} onChange={handleAutofill} />
			{multiple() && name() ? <MultipleHiddenInputs name={name()} form={props.form} values={multipleSerialized} /> : null}
		</SelectRootContext>;
}
function MultipleHiddenInputs(props) {
  return <>
			{props.values().map(v => <input type="hidden" form={props.form} name={props.name} value={v} />)}
		</>;
}

/**
 * Groups all parts of the select. Doesn't render its own HTML element —
 * the root is a pure context provider.
 */
export function SelectRoot(props) {
  if (useSelectRootContext(true) != null || useFloatingParentNodeId() != null) {
    return <SelectRootComponent {...props} />;
  }
  return <FloatingTree>
			<SelectRootComponent {...props} />
		</FloatingTree>;
}
//# sourceMappingURL=select-root.jsx.map
