import { createContext, createMemo, runWithOwner, untrack, useContext } from "solid-js";
import { readOption } from "../internals/maybe-accessor.jsx";
import { SNAPSHOT_KEYS, snapshotKeys } from "../internals/observability/snapshot.jsx";
import { defaultItemEquality } from "./utils/item-equality.jsx";
import { stringifyAsLabel } from "./utils/resolve-value-label.jsx";

/** Detect grouped items (has at least one entry with an `items` key). */
function isGroupedItems(items) {
  if (!Array.isArray(items) || items.length === 0) return false;
  const first = items[0];
  return typeof first === "object" && first !== null && "items" in first && !("value" in first);
}

// ---------------------------------------------------------------------------
// Public types
// ---------------------------------------------------------------------------

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

const SelectCtx = createContext(null);
export function useSelect() {
  const ctx = useContext(SelectCtx);
  if (ctx == null) {
    throw new Error("Base UI: useSelect() must be called inside a <c.Provider>.");
  }
  return ctx;
}

// ---------------------------------------------------------------------------
// Primitive
// ---------------------------------------------------------------------------

/**
 * Solid 2-native headless primitive for the select selection logic. Owns:
 * value / open state, isItemSelected derivation, and valueLabel. No filter
 * input (Select has no text input). DOM concerns (floating-ui positioning,
 * keyboard nav, portal, hidden form input) live in the structural
 * `<Select.Root>` wrapper that composes this primitive.
 */
export function createSelect(options) {
  // Freeze multi-select at init — the scalar vs. array value shape cannot
  // flip after mount.
  const isMultiple = untrack(() => options.multiple === true);
  const disabled = createMemo(() => Boolean(readOption(options.disabled)), {
    name: "select.disabled"
  });
  const readOnly = createMemo(() => Boolean(readOption(options.readOnly)), {
    name: "select.readOnly"
  });
  const required = createMemo(() => Boolean(readOption(options.required)), {
    name: "select.required"
  });
  const itemToStringLabel = createMemo(() => readOption(options.itemToStringLabel), {
    name: "select.itemToStringLabel"
  });
  const isItemEqualToValue = createMemo(() => readOption(options.isItemEqualToValue) ?? defaultItemEquality, {
    name: "select.isItemEqualToValue"
  });

  // Value — reads through the consumer's accessor.
  const value = createMemo(() => {
    const raw = readOption(options.value);
    if (isMultiple) {
      return Array.isArray(raw) ? raw : raw == null ? [] : raw;
    }
    return raw ?? null;
  }, {
    name: "select.value"
  });

  // Open — reads through the consumer's accessor.
  const open = createMemo(() => Boolean(readOption(options.open)), {
    name: "select.open"
  });

  // Items registry
  const items = createMemo(() => readOption(options.items), {
    name: "select.items"
  });
  const isGrouped = createMemo(() => {
    const its = items();
    if (!its) return false;
    return isGroupedItems(its);
  }, {
    name: "select.isGrouped"
  });

  // isItemSelected — pure derivation.
  const isItemSelected = item => {
    const sv = value();
    if (sv == null) return false;
    const comparer = isItemEqualToValue();
    if (isMultiple && Array.isArray(sv)) {
      return sv.some(v => comparer(item, v));
    }
    return comparer(item, sv);
  };

  // Display label for the trigger — derived from value + itemToStringLabel.
  const valueLabel = createMemo(() => {
    const sv = value();
    if (sv == null) return "";
    if (isMultiple && Array.isArray(sv)) {
      return sv.map(v => stringifyAsLabel(v, itemToStringLabel())).join(", ");
    }
    return stringifyAsLabel(sv, itemToStringLabel());
  }, {
    name: "select.valueLabel"
  });

  // Pure event-emitters — runWithOwner(null, ...) escapes the reactive owner
  // so signal writes inside the callback don't trigger SIGNAL_WRITE_IN_OWNED_SCOPE.
  const setValue = (next, reason = "imperative") => {
    runWithOwner(null, () => options.onValueChange?.(next, reason));
  };
  const setOpen = (next, reason = "imperative") => {
    runWithOwner(null, () => options.onOpenChange?.(next, reason));
  };
  const api = {
    value,
    open,
    multiple: isMultiple,
    disabled,
    readOnly,
    required,
    items,
    isGrouped,
    isItemSelected,
    valueLabel,
    setValue,
    setOpen,
    Provider: props => <SelectCtx value={api}>
				{props.children}
			</SelectCtx>
  };
  api[SNAPSHOT_KEYS] = SELECT_SNAPSHOT_KEYS;
  return api;
}
const SELECT_SNAPSHOT_KEYS = snapshotKeys("value", "open", "disabled", "readOnly", "required", "valueLabel");
//# sourceMappingURL=create-select.jsx.map
