import { createContext, createMemo, createSignal, untrack, useContext } from "solid-js";
import { REASONS } from "../internals/change-event-details.jsx";
import { readOption } from "../internals/maybe-accessor.jsx";
import { mergeRootProps } from "../internals/merge-root-props.jsx";
import { SNAPSHOT_KEYS, snapshotKeys } from "../internals/observability/snapshot.jsx";
import { createStableCallback } from "../internals/stable-callback.jsx";
import { CheckboxGroupContext } from "./checkbox-group-context.jsx";

/**
 * Why the value array changed. CheckboxGroup carries the `"none"` reason —
 * children dispatch via plain clicks; the group itself doesn't distinguish
 * navigation/key/imperative origins in its public surface (matches React's
 * `CheckboxGroup.ChangeEventDetails`).
 */

const EMPTY = Object.freeze([]);

/**
 * Options for {@link createCheckboxGroup}. Every reactive input is
 * `MaybeAccessor<T>`; pass an accessor to make the value reactive.
 *
 * Design notes:
 *
 * - `value` is `MaybeAccessor<readonly string[]>`; the consumer always owns
 *   the signal. Internal storage is the consumer's accessor — no defaultValue.
 * - `allValues` is the universe of child values for the parent-indicator
 *   helper (all/none/mixed math). Optional; the parent helper degrades to
 *   `false`/`false` when absent.
 * - `onValueChange(next, event, reason)` is **pre-change**. The consumer's
 *   signal write is the only writer. Post-change observation is via
 *   `createEffect(c.value, ...)` on the api.
 *
 * **Solid-native shape (2026-04-30).** No internal state. `value` is
 * required and the consumer always owns the signal. See `solidify.md` §6.
 */

/** Slot props emitted by `c.group()`. */

/** Parent-indicator bundle returned by {@link CheckboxGroupApi.parent}. */

/** Public api returned by {@link createCheckboxGroup}. */

const CheckboxGroupApiContext = createContext(null);

/**
 * Read the nearest {@link CheckboxGroupApi} provided by `c.Provider`. Throws
 * when used outside the Provider.
 */
export function useCheckboxGroup() {
  const ctx = useContext(CheckboxGroupApiContext);
  if (ctx == null) {
    throw new Error("Base UI: useCheckboxGroup() must be called inside a <c.Provider>.");
  }
  return ctx;
}

/**
 * Solid 2.0-native compound primitive for CheckboxGroup. Owns the selected
 * value array and the parent-indicator (all/none/mixed) bundle. Form wiring
 * (field registration, hidden inputs, name resolution) and rendering live in
 * the `<CheckboxGroup>` wrapper — those are integration concerns layered on
 * top of this primitive.
 *
 * Embeds cleanly with `<Checkbox.Root>`: the wrapper reads the legacy
 * {@link CheckboxGroupContext} (also published by `c.Provider`), routes
 * clicks through `groupContext.setValue(...)`, and derives each child's
 * `checked` from `groupContext.value().includes(childValue)`. The bypass
 * pattern in `checkbox-root.tsx`'s onClick keeps that interface unchanged.
 */
export function createCheckboxGroup(options) {
  // `value` reads through the consumer's accessor — no internal signal,
  // no controlled-or-internal branching.
  const value = createMemo(() => readOption(options.value), {
    name: "checkbox-group.value"
  });
  const allValues = createMemo(() => readOption(options.allValues), {
    name: "checkbox-group.allValues"
  });
  const disabled = createMemo(() => Boolean(readOption(options.disabled)), {
    name: "checkbox-group.disabled"
  });
  const readOnly = createMemo(() => Boolean(readOption(options.readOnly)), {
    name: "checkbox-group.readOnly"
  });
  const required = createMemo(() => Boolean(readOption(options.required)), {
    name: "checkbox-group.required"
  });
  const name = createMemo(() => readOption(options.name), {
    name: "checkbox-group.name"
  });

  // `setValue` is a pure event-emitter. The consumer is responsible for
  // updating their value-signal in response. No internal state to write.
  const setValue = (next, event, reason = REASONS.none) => {
    if (untrack(disabled) || untrack(readOnly)) return;
    const ev = event ?? new Event("valuechange", {
      cancelable: true
    });
    const nextArr = next.slice();
    options.onValueChange?.(nextArr, ev, reason);
  };

  // --- Parent-indicator helper -----------------------------------------
  // Mirrors `useCheckboxGroupParent` (the legacy hook) but lives on the
  // primitive so external callers get the same shape via `c.parent`. The
  // `disabledStates` map is a plain coordination side-channel populated by
  // child wrappers during setup; same convention React uses.
  const disabledStates = new Map();
  let uncontrolledStash = untrack(value);
  const [status, setStatus] = createSignal("mixed", {
    name: "checkbox-group.parentStatus",
    ownedWrite: true
  });
  const parentChecked = createMemo(() => {
    const v = value();
    const all = allValues() ?? EMPTY;
    return all.length > 0 && v.length === all.length;
  }, {
    name: "checkbox-group.parent.checked"
  });
  const parentIndeterminate = createMemo(() => {
    const v = value();
    const all = allValues() ?? EMPTY;
    return v.length > 0 && v.length !== all.length;
  }, {
    name: "checkbox-group.parent.indeterminate"
  });
  const handleParentFlip = createStableCallback(() => (_next, event, reason) => {
    const all = allValues() ?? EMPTY;
    const values = value();
    const stash = uncontrolledStash;

    // "none": disabled children that happen to be currently in the value
    // array stay on (mirrors React's `parentContext` math).
    const none = all.filter(v => disabledStates.get(v) && stash.includes(v));
    const allEnabled = all.filter(v => !disabledStates.get(v) || disabledStates.get(v) && stash.includes(v));
    const allOnOrOff = stash.length === allEnabled.length || stash.length === 0;
    if (allOnOrOff) {
      if (values.length === allEnabled.length) {
        setValue(none, event, reason);
      } else {
        setValue(allEnabled, event, reason);
      }
      return;
    }
    const phase = status();
    if (phase === "mixed") {
      setValue(allEnabled, event, reason);
      setStatus("on");
    } else if (phase === "on") {
      setValue(none, event, reason);
      setStatus("off");
    } else {
      setValue(stash.slice(), event, reason);
      setStatus("mixed");
    }
  });
  const parent = {
    checked: parentChecked,
    indeterminate: parentIndeterminate,
    disabledStates,
    getParentProps: () => ({
      checked: parentChecked(),
      indeterminate: parentIndeterminate(),
      onCheckedChange: handleParentFlip
    })
  };

  // --- Slot ------------------------------------------------------------
  const groupInternal = {
    role: "group",
    get "data-disabled"() {
      return disabled() ? "" : undefined;
    },
    get "data-readonly"() {
      return readOnly() ? "" : undefined;
    },
    get "data-required"() {
      return required() ? "" : undefined;
    }
  };
  const group = userProps => mergeRootProps(groupInternal, userProps);

  // --- Legacy-context bridge ------------------------------------------
  // `<Checkbox.Root>` reads `CheckboxGroupContext` and calls
  // `setValue(next, details)` from its onClick. Stash the most recent
  // pre-array on every child-driven write so the parent flip-all math
  // can restore "mixed" correctly; same trick the legacy hook uses.
  const bridgeSetValue = createStableCallback(() => (next, details) => {
    uncontrolledStash = next;
    // Reset the parent cycle to "mixed" on a child-driven change. Matches
    // the legacy `useCheckboxGroupParent.getChildProps` behaviour.
    setStatus("mixed");
    setValue(next, details.event, details.reason);
    // Cancel propagation: the legacy bridge also honours
    // `details.cancel()`. Consumers calling `details.cancel()` should see
    // no internal write — `setValue` already vetoes via `defaultPrevented`,
    // but the details API uses its own `isCanceled` flag, so honour both.
    if (details.isCanceled) {
      return;
    }
  });
  const legacyContext = {
    value,
    defaultValue: () => readOption(options.defaultValue),
    allValues,
    setValue: (next, details) => {
      if (details.isCanceled) return;
      bridgeSetValue(next, details);
    },
    parent: {
      id: "",
      checked: parentChecked,
      indeterminate: parentIndeterminate,
      disabledStates,
      getParentProps: () => ({
        id: "",
        checked: parentChecked(),
        indeterminate: parentIndeterminate(),
        "aria-controls": "",
        onCheckedChange: (_next, details) => {
          if (details.isCanceled) return;
          handleParentFlip(_next, details.event, details.reason);
        }
      }),
      getChildProps: childValue => ({
        checked: value().includes(childValue),
        onCheckedChange: (nextChecked, details) => {
          if (details.isCanceled) return;
          const current = value();
          const nextValue = current.slice();
          if (nextChecked) {
            if (!nextValue.includes(childValue)) nextValue.push(childValue);
          } else {
            const idx = nextValue.indexOf(childValue);
            if (idx >= 0) nextValue.splice(idx, 1);
          }
          uncontrolledStash = nextValue;
          setStatus("mixed");
          setValue(nextValue, details.event, details.reason);
        }
      })
    },
    disabled,
    present: true
  };
  const api = {
    value,
    allValues,
    disabled,
    readOnly,
    required,
    name,
    setValue,
    parent,
    group,
    Provider: props => <CheckboxGroupApiContext value={api}>
				<CheckboxGroupContext value={legacyContext}>
					{props.children}
				</CheckboxGroupContext>
			</CheckboxGroupApiContext>
  };
  api[SNAPSHOT_KEYS] = CHECKBOX_GROUP_SNAPSHOT_KEYS;
  return api;
}
const CHECKBOX_GROUP_SNAPSHOT_KEYS = snapshotKeys("value", "allValues", "disabled", "readOnly", "required", "name");
//# sourceMappingURL=create-checkbox-group.jsx.map
