import { createEffect, createMemo } from "solid-js";
import { useFieldMessagesContext } from "../field/field-messages-context.jsx";
import { useFieldRootContext } from "../internals/field-context.jsx";
import { fieldStateAttrs } from "../internals/field-state-attrs.jsx";
import { useAriaLabelledBy } from "../internals/labelable-provider/use-aria-labelled-by.jsx";
import { useLabelableId } from "../internals/labelable-provider/use-labelable-id.jsx";
import { makeSlotRefDirective } from "../internals/make-slot-ref-directive.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 { useFieldControlValidation } from "../internals/use-field-control-validation/use-field-control-validation.jsx";

/** State surface exposed to slot consumers. Mirrors the surrounding field. */

/**
 * Options for {@link createInput}. Every reactive input is `MaybeAccessor<T>`
 * — pass an accessor to make the value reactive (or a bare value; Solid's
 * JSX-proxy makes wrapper props reactive either way, and `readOption` is
 * identity on non-functions).
 *
 * **Solid-native shape.** Like the other field controls, `createInput` owns
 * no value/validation state of its own — that lives in the surrounding
 * `<Field.Root>`. The primitive reads the field/labelable/messages contexts
 * internally and projects a control prop-bag. Outside a `<Field.Root>` every
 * context resolves to its NOOP form and the control still renders a plain
 * `<input>`.
 */

/** Shape of attributes emitted by `c.control()`. */

/**
 * Public API returned by {@link createInput}. Flat accessors at the root; the
 * control prop-bag (`control`) and its directive form (`controlRef`) carry the
 * field wiring. No `Provider`/`useInput` — an `<input>` is a leaf with no
 * descendant subtree that would read the api back (same call as Toggle's
 * skipped provider).
 */

/**
 * Solid 2.0-native field-control primitive for a native `<input>` that
 * integrates with a surrounding `<Field.Root>`. Wires (when inside a field):
 *
 *   - id registration into the labelable provider so `<Field.Label>`'s
 *     `htmlFor` can target this input.
 *   - value registration into the field (and any `<Form>` above) via
 *     `useFieldControlValidation`.
 *   - `aria-labelledby` composed from registered labels.
 *   - `aria-describedby` composed from `<Field.Description>` / `<Field.Error>`
 *     ids via the field-messages registry.
 *   - `aria-invalid` — flipped to literal `"true"` once validation latches
 *     invalid (via `getInputValidationProps()`).
 *   - `onInput` / `onBlur` / `onFocus` / `onKeyDown` into the field's
 *     touched / dirty / filled / focused setters and the validation pipeline.
 *
 * Must be called in an owner scope (component body or `createRoot`) — it
 * reads contexts and installs effects / cleanups.
 */
export function createInput(options = {}) {
  const field = useFieldRootContext();
  const messages = useFieldMessagesContext();
  const {
    id
  } = useLabelableId({
    id: options.id
  });
  const disabled = createMemo(() => field.disabled() || readOption(options.disabled) === true, {
    name: "input.disabled"
  });
  const state = createMemo(() => ({
    ...field.state(),
    disabled: disabled()
  }), {
    name: "input.state"
  });
  const {
    getInputValidationProps,
    inputRef,
    commit
  } = useFieldControlValidation({
    value: () => readOption(options.value),
    id: () => id,
    controlRef: field.validation.inputRef,
    getValue: () => field.validation.inputRef.current?.value
  });

  // Mirror the live value into the field's `filled` state. A non-empty
  // external value (or a non-empty element value) marks filled; an explicit
  // empty string clears it.
  createEffect(() => readOption(options.value), externalValue => {
    const hasExternal = externalValue != null;
    const input = field.validation.inputRef.current;
    if (input?.value || hasExternal && externalValue !== "") {
      field.setFilled(true);
    } else if (hasExternal && externalValue === "") {
      field.setFilled(false);
    }
  });
  const ariaLabelledBy = useAriaLabelledBy();
  const ariaDescribedBy = createMemo(() => {
    const ids = messages.getMessageIds();
    if (ids.length === 0) return undefined;
    return ids.join(" ");
  }, {
    name: "input.ariaDescribedBy"
  });
  const controlInternal = {
    get id() {
      return id;
    },
    get disabled() {
      return disabled();
    },
    get name() {
      return field.name() ?? readOption(options.name);
    },
    get "aria-labelledby"() {
      return ariaLabelledBy() ?? "";
    },
    get "aria-describedby"() {
      return ariaDescribedBy() ?? "";
    },
    get "data-disabled"() {
      return disabled() ? "" : undefined;
    },
    onFocus: () => {
      field.setFocused(true);
    },
    onBlur: event => {
      field.setTouched(true);
      field.setFocused(false);
      if (field.validationMode() === "onBlur") {
        void commit(event.currentTarget.value);
      }
    },
    onInput: event => {
      const value = event.currentTarget.value;
      field.setDirty(value !== field.validityData().initialValue);
      field.setFilled(value !== "");
    },
    onKeyDown: event => {
      if (event.currentTarget.tagName === "INPUT" && event.key === "Enter") {
        field.setTouched(true);
        void commit(event.currentTarget.value);
      }
    }
  };

  // `getInputValidationProps()` is built from getters that re-read field
  // state, so materialize the bag once and merge it — the keys stay
  // reactive through `mergeRootProps`.
  const validationProps = getInputValidationProps();
  const control = userProps => {
    let merged = mergeRootProps(controlInternal, fieldStateAttrs(state));
    merged = mergeRootProps(merged, validationProps);
    merged = mergeRootProps(merged, userProps);
    return merged;
  };
  const bindControl = el => {
    inputRef.current = el;
  };
  const controlRef = () => {
    const baseDirective = makeSlotRefDirective(() => control());
    return el => {
      bindControl(el);
      baseDirective(el);
    };
  };
  const api = {
    disabled,
    state,
    id,
    control,
    bindControl,
    controlRef
  };
  api[SNAPSHOT_KEYS] = INPUT_SNAPSHOT_KEYS;
  return api;
}
const INPUT_SNAPSHOT_KEYS = snapshotKeys("disabled", "state");
//# sourceMappingURL=create-input.jsx.map
