/**
 * Stage 5.3 port of Base UI React's `FloatingDelayGroup`
 * (`packages/react/src/floating-ui-react/components/FloatingDelayGroup.tsx`).
 *
 * Coordinates hover-open/close delays across sibling floating surfaces so
 * that rapidly moving between grouped tooltips skips the normal open delay:
 * the next member opens immediately while any group member's close window
 * (or the explicit `timeoutMs`) is still open.
 *
 * ### React → Solid deviations
 *
 * - `React.useRef` → plain `MutableRef<T>` objects. The ref plumbing mirrors
 *   the React source exactly: `delayRef`, `initialDelayRef`, `currentIdRef`,
 *   and `currentContextRef` are shared by identity with consumer components
 *   via context so that sibling `useDelayGroup` calls all observe the same
 *   mutable state.
 * - `React.useState(false)` for the instant-phase flag is a `createSignal`
 *   pair (`isInstantPhase` / `setIsInstantPhase`) so the returned bag can
 *   track reactively.
 * - `React.useMemo` over the context value is dropped — Solid component
 *   bodies run once, and the contained refs already have stable identity,
 *   so the object literal is created once per `FloatingDelayGroup` mount.
 * - `useIsoLayoutEffect(cb, [...deps])` pairs become the Solid split
 *   compute/apply form. The dependency set that "opens a new member" is
 *   replicated by reading `store.useState('floatingId')` +
 *   `options.open` in the compute phase; the apply phase mirrors the
 *   React effect body line-for-line.
 * - Timeout management uses `useTimeout()` for automatic `onCleanup` —
 *   matches the React source's `useTimeout()` helper 1:1.
 *
 * ### Reason strings
 *
 * Pre-empted closes (when switching members) emit `REASONS.none`, matching
 * the React source literal (`createChangeEventDetails(REASONS.none)`).
 */

import { createContext, createSignal, onCleanup, untrack, useContext } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../change-event-details.jsx";
import { createStableCallback } from "../../stable-callback.jsx";
import { useIsoLayoutEffect } from "../../use-iso-layout-effect.jsx";
import { Timeout, useTimeout } from "../../use-timeout.jsx";
import { getDelay } from "../hooks/use-hover-shared.jsx";

// ---------------------------------------------------------------------------
// Context
// ---------------------------------------------------------------------------

const FloatingDelayGroupContext = createContext({
  hasProvider: false,
  timeoutMs: 0,
  delayRef: {
    current: 0
  },
  initialDelayRef: {
    current: 0
  },
  timeout: new Timeout(),
  currentIdRef: {
    current: null
  },
  currentContextRef: {
    current: null
  }
});

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

/**
 * Provides context for a group of floating elements that should share a
 * `delay`. Sibling members consume the group via `useDelayGroup(context)`
 * and will skip their open delay if another group member has opened
 * recently (within the close delay or `timeoutMs` window).
 *
 * @see https://floating-ui.com/docs/FloatingDelayGroup
 * @internal
 */
export function FloatingDelayGroup(props) {
  const timeoutMs = () => props.timeoutMs ?? 0;

  // Body-time snapshots — `delayRef.current` is mutated reactively by the
  // `delay` setter on the returned bag, and `initialDelayRef` is the
  // stable initial value (matches React's `useRef(props.delay)` semantics).
  const delayRef = {
    current: untrack(() => props.delay)
  };
  const initialDelayRef = {
    current: untrack(() => props.delay)
  };
  const currentIdRef = {
    current: null
  };
  const currentContextRef = {
    current: null
  };
  const timeout = useTimeout();
  const value = {
    hasProvider: true,
    delayRef,
    initialDelayRef,
    currentIdRef,
    currentContextRef,
    timeout,
    get timeoutMs() {
      return timeoutMs();
    }
  };
  return <FloatingDelayGroupContext value={value}>
			{props.children}
		</FloatingDelayGroupContext>;
}

// ---------------------------------------------------------------------------
// useDelayGroup — sibling registration hook
// ---------------------------------------------------------------------------

/**
 * Registers the caller into the nearest `<FloatingDelayGroup>`. Returns
 * the shared `delayRef`, a flag marking the instant (inter-member)
 * phase, and whether a provider is present. Call inside a component that
 * participates in hover-delay coordination (e.g. Tooltip.Root).
 *
 * Accepts either a `FloatingRootContext` (the store itself) or a
 * `FloatingContext` (with `.rootStore`). The React source uses the same
 * dual-input shape so consumers pass whichever they have on hand.
 *
 * @see https://floating-ui.com/docs/FloatingDelayGroup
 * @internal
 */
export function useDelayGroup(context, options = {
  open: false
}) {
  const store = "rootStore" in context ? context.rootStore : context;
  const groupContext = useContext(FloatingDelayGroupContext);
  const {
    currentIdRef,
    delayRef,
    initialDelayRef,
    currentContextRef,
    hasProvider,
    timeout
  } = groupContext;
  const [isInstantPhase, setIsInstantPhaseSignal] = createSignal(false, {
    ownedWrite: true
  });

  // Stabilize so previous member can synchronously flip our phase when
  // control passes to a new member mid-effect (React relies on a stable
  // state setter; Solid's setter is already stable but we wrap for the
  // identity-parity the shared `currentContextRef` requires).
  const setIsInstantPhase = createStableCallback(() => value => setIsInstantPhaseSignal(value));

  // Effect #1: when this member is currently the group owner and its `open`
  // flag flips to false, schedule (or immediately perform) the unset — if
  // `timeoutMs` is configured, the group stays "warm" for the extra window.
  useIsoLayoutEffect(() => ({
    floatingId: store.useState("floatingId"),
    open: options.open,
    timeoutMs: groupContext.timeoutMs
  }), snapshot => {
    const {
      floatingId,
      open,
      timeoutMs
    } = snapshot;
    function unset() {
      setIsInstantPhaseSignal(false);
      currentContextRef.current?.setIsInstantPhase(false);
      currentIdRef.current = null;
      currentContextRef.current = null;
      delayRef.current = initialDelayRef.current;
    }
    if (!currentIdRef.current) {
      return undefined;
    }
    if (!open && currentIdRef.current === floatingId) {
      setIsInstantPhaseSignal(false);
      if (timeoutMs) {
        const closingId = floatingId;
        timeout.start(timeoutMs, () => {
          // If another member has taken over the group, skip resetting.
          if (store.state.open || currentIdRef.current && currentIdRef.current !== closingId) {
            return;
          }
          unset();
        });
        // Matches React's `return () => timeout.clear()` — if the
        // effect re-runs before the timeout fires (or the scope is
        // disposed), cancel the pending reset.
        return () => {
          timeout.clear();
        };
      }
      unset();
    }
    return undefined;
  });

  // Effect #2: when this member opens, claim the group. If a different
  // member was the previous owner, flip both into instant phase and force
  // the old owner to close synchronously with `REASONS.none`.
  useIsoLayoutEffect(() => ({
    floatingId: store.useState("floatingId"),
    open: options.open
  }), snapshot => {
    const {
      floatingId,
      open
    } = snapshot;
    if (!open) {
      return;
    }
    const prevContext = currentContextRef.current;
    const prevId = currentIdRef.current;

    // A new tooltip is opening, so cancel any pending timeout that
    // would reset the group's delay back to the initial value.
    timeout.clear();
    currentContextRef.current = {
      onOpenChange: store.setOpen,
      setIsInstantPhase
    };
    currentIdRef.current = floatingId ?? null;
    delayRef.current = {
      open: 0,
      close: getDelay(initialDelayRef.current, "close")
    };
    if (prevId !== null && prevId !== floatingId) {
      setIsInstantPhaseSignal(true);
      prevContext?.setIsInstantPhase(true);
      prevContext?.onOpenChange(false, createChangeEventDetails(REASONS.none));
    } else {
      setIsInstantPhaseSignal(false);
      prevContext?.setIsInstantPhase(false);
    }
  });

  // Effect #3: on unmount, clear our slot in `currentContextRef` so the
  // previous-owner branch above doesn't reach into a disposed member.
  // Matches React's `useIsoLayoutEffect(() => () => { ... }, [])`.
  onCleanup(() => {
    currentContextRef.current = null;
  });
  return {
    get hasProvider() {
      return hasProvider;
    },
    get delayRef() {
      return delayRef;
    },
    get isInstantPhase() {
      return isInstantPhase();
    }
  };
}
//# sourceMappingURL=floating-delay-group.jsx.map
