import { createMemo, createSignal } from "solid-js";
import { useBaseUiId } from "../internals/base-ui-id.jsx";
import { createStableCallback } from "../internals/stable-callback.jsx";

/**
 * Props the parent-checkbox consumer splats on `<Checkbox.Root parent>`. The
 * Solid port accepts them as a plain props bag (no accessors) because they're
 * consumed inside the checkbox-root's reactive scope — reads re-track on every
 * call.
 */

/**
 * Props the per-child consumer splats on each non-parent `<Checkbox.Root>`.
 */

const EMPTY = Object.freeze([]);

/**
 * "Parent checkbox" helper. Matches Base UI React's `useCheckboxGroupParent`.
 *
 * The parent checkbox reflects the aggregate of its children:
 *
 * - all children checked → parent is checked
 * - no children checked  → parent is unchecked
 * - mixed               → parent is indeterminate
 *
 * Clicking the parent cycles through all-on / all-off (with an uncontrolled
 * stash so the user's mixed state can be "remembered" when cycling back).
 * Clicking a child flips just that child and resets the parent cycle back to
 * `"mixed"`.
 *
 * Solid 2 adaptations:
 *
 * - `id` is generated once via `useBaseUiId`. Mirrors React's single-render
 *   id and stays stable for the component's lifetime.
 * - `indeterminate` / `checked` are derived via `createMemo` over the group
 *   value + `allValues` — no manual recomputation, no stale closures.
 * - `onValueChange` is wrapped in `createStableCallback` so the child/parent
 *   prop factories can be captured into closures without re-wiring.
 * - `disabledStates` is a plain `Map` (not a store / signal). Children push
 *   into it during setup — it's a coordination side-channel, not reactive
 *   state; the React port does the same via `disabledStatesRef`.
 * - `status` tracks the cycle phase (`"on" | "off" | "mixed"`). Declared
 *   with `ownedWrite: true` because the parent's `onCheckedChange` runs in
 *   owned scope (it's invoked from the checkbox-root's event handler chain
 *   which is itself called from inside a reactive merge proxy read — Solid
 *   doesn't mark event callbacks as unowned by default).
 * - `uncontrolledState` is a plain `let` binding (not a signal). It's a
 *   sticky last-seen "mixed" snapshot used for the cycle-back-to-mixed
 *   branch — no reactivity needed.
 */
export function useCheckboxGroupParent(params) {
  const id = useBaseUiId();
  const disabledStates = new Map();

  // Snapshot of the value at the time of the last child-driven change.
  // The parent's cycle uses this when returning to the "mixed" phase so the
  // user's custom mix is restored instead of losing it to the on/off cycle.
  let uncontrolledState = params.value() ?? EMPTY;
  const [status, setStatus] = createSignal("mixed", {
    ownedWrite: true
  });
  const onValueChange = createStableCallback(() => params.onValueChange);
  const checked = createMemo(() => {
    const values = params.value() ?? EMPTY;
    const all = params.allValues() ?? EMPTY;
    return values.length === all.length && all.length > 0;
  });
  const indeterminate = createMemo(() => {
    const values = params.value() ?? EMPTY;
    const all = params.allValues() ?? EMPTY;
    return values.length > 0 && values.length !== all.length;
  });
  function getParentProps() {
    const values = params.value() ?? EMPTY;
    const all = params.allValues() ?? EMPTY;
    return {
      id,
      indeterminate: indeterminate(),
      checked: checked(),
      // TODO: custom `id` on child checkboxes breaks this — matches the
      // React port's known limitation (base-ui#2691).
      "aria-controls": all.map(v => `${id}-${v}`).join(" "),
      onCheckedChange(_next, details) {
        const currentUncontrolled = uncontrolledState;

        // None: disabled children that happen to be checked stay on.
        const none = all.filter(v => disabledStates.get(v) && currentUncontrolled.includes(v));
        // All: enabled children + disabled-but-checked children.
        const allEnabled = all.filter(v => !disabledStates.get(v) || disabledStates.get(v) && currentUncontrolled.includes(v));
        const allOnOrOff = currentUncontrolled.length === allEnabled.length || currentUncontrolled.length === 0;
        if (allOnOrOff) {
          if (values.length === allEnabled.length) {
            onValueChange(none, details);
          } else {
            onValueChange(allEnabled, details);
          }
          return;
        }
        const phase = status();
        if (phase === "mixed") {
          onValueChange(allEnabled, details);
          setStatus("on");
        } else if (phase === "on") {
          onValueChange(none, details);
          setStatus("off");
        } else {
          // "off" → restore the remembered mix.
          onValueChange([...currentUncontrolled], details);
          setStatus("mixed");
        }
      }
    };
  }
  function getChildProps(childValue) {
    const values = params.value() ?? EMPTY;
    return {
      checked: values.includes(childValue),
      onCheckedChange(nextChecked, details) {
        const nextValue = values.slice();
        if (nextChecked) {
          nextValue.push(childValue);
        } else {
          const idx = nextValue.indexOf(childValue);
          if (idx >= 0) nextValue.splice(idx, 1);
        }
        uncontrolledState = nextValue;
        onValueChange(nextValue, details);
        setStatus("mixed");
      }
    };
  }
  return {
    id,
    indeterminate,
    checked,
    disabledStates,
    getParentProps,
    getChildProps
  };
}
//# sourceMappingURL=use-checkbox-group-parent.jsx.map
