import { createContext, createMemo, runWithOwner, useContext } from "solid-js";
import { createCollatorItemFilter, getFilter } from "../combobox/internals/filter.jsx";
import { isGroupedItems, stringifyAsLabel } from "../combobox/internals/resolve-value-label.jsx";
import { readOption } from "../internals/maybe-accessor.jsx";
import { SNAPSHOT_KEYS, snapshotKeys } from "../internals/observability/snapshot.jsx";

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

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

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

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

/**
 * Solid 2-native headless primitive for autocomplete selection + filtering
 * logic. Autocomplete is the "input value is the source of truth" variant:
 * there is no separate selected-value state — the input text IS the value.
 * Owns: value / open state, items / filteredItems / query derivation, and
 * item label helpers. DOM concerns (floating-ui, input element, keyboard nav,
 * portal) live in the structural `<Autocomplete.Root>` wrapper.
 */
export function createAutocomplete(options) {
  const mode = createMemo(() => readOption(options.mode) ?? "list", {
    name: "autocomplete.mode"
  });
  const disabled = createMemo(() => Boolean(readOption(options.disabled)), {
    name: "autocomplete.disabled"
  });
  const readOnly = createMemo(() => Boolean(readOption(options.readOnly)), {
    name: "autocomplete.readOnly"
  });
  const itemToStringLabel = createMemo(() => readOption(options.itemToStringLabel), {
    name: "autocomplete.itemToStringLabel"
  });

  // Value — reads through the consumer's accessor (defaults to "").
  const value = createMemo(() => readOption(options.value) ?? "", {
    name: "autocomplete.value"
  });

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

  // Items
  const items = createMemo(() => readOption(options.items), {
    name: "autocomplete.items"
  });
  const isGrouped = createMemo(() => isGroupedItems(items()), {
    name: "autocomplete.isGrouped"
  });

  // In "both" mode we filter against the typed value (not any inline-autocomplete
  // overlay). The query is the trimmed text.
  const query = createMemo(() => value().trim(), {
    name: "autocomplete.query"
  });

  // Filtering — "inline" and "none" modes show all items unfiltered.
  const isStaticItems = createMemo(() => {
    const m = mode();
    return m === "inline" || m === "none";
  });
  const collatorFilter = createMemo(() => getFilter({
    locale: readOption(options.locale)
  }), {
    name: "autocomplete.collatorFilter"
  });
  const resolvedFilter = createMemo(() => {
    if (isStaticItems()) return () => true;
    const userFilter = readOption(options.filter);
    if (userFilter === null) return () => true;
    if (userFilter !== undefined) return userFilter;
    return createCollatorItemFilter(collatorFilter(), itemToStringLabel());
  }, {
    name: "autocomplete.resolvedFilter"
  });
  const filteredItems = createMemo(() => {
    const list = items();
    if (!list) return [];
    const filt = resolvedFilter();
    const q = query();
    const label = itemToStringLabel();
    if (isGrouped()) {
      const groups = list;
      const out = [];
      for (const g of groups) {
        const candidates = q === "" ? g.items : g.items.filter(item => filt(item, q, label));
        if (candidates.length > 0) {
          out.push({
            ...g,
            items: candidates
          });
        }
      }
      return out;
    }
    if (q === "") return list;
    return list.filter(item => filt(item, q, label));
  }, {
    name: "autocomplete.filteredItems"
  });
  const flatFilteredItems = createMemo(() => {
    const fi = filteredItems();
    if (isGrouped()) {
      return fi.flatMap(g => g.items);
    }
    return fi;
  }, {
    name: "autocomplete.flatFilteredItems"
  });
  const itemLabel = item => stringifyAsLabel(item, itemToStringLabel());

  // 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,
    mode,
    disabled,
    readOnly,
    items,
    filteredItems,
    flatFilteredItems,
    query,
    isGrouped,
    itemLabel,
    setValue,
    setOpen,
    Provider: props => <AutocompleteCtx value={api}>
				{props.children}
			</AutocompleteCtx>
  };
  api[SNAPSHOT_KEYS] = AUTOCOMPLETE_SNAPSHOT_KEYS;
  return api;
}
const AUTOCOMPLETE_SNAPSHOT_KEYS = snapshotKeys("value", "open", "mode", "disabled", "readOnly", "query");
//# sourceMappingURL=create-autocomplete.jsx.map
