import { dynamic } from "@solidjs/web";
import { omit, untrack } from "solid-js";
import { createChangeEventDetails, createGenericEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { composeRefs, userRef } from "../../internals/compose-refs.jsx";
import { useFieldRootContext } from "../../internals/field-context.jsx";
import { useFormContext } from "../../internals/form-context.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { useFieldControlValidation } from "../../internals/use-field-control-validation/use-field-control-validation.jsx";
import { useValueChanged } from "../../internals/use-value-changed.jsx";
import { useNumberFieldRootContext } from "../root/number-field-root-context.jsx";
import { DEFAULT_STEP } from "../utils/constants.jsx";
import { formatNumber, formatNumberMaxPrecision } from "../utils/format-number.jsx";
import { ANY_MINUS_DETECT_RE, ANY_MINUS_RE, ANY_PLUS_DETECT_RE, ANY_PLUS_RE, ARABIC_DETECT_RE, FULLWIDTH_DETECT_RE, getNumberLocaleDetails, HAN_DETECT_RE, PERSIAN_DETECT_RE, parseNumber } from "../utils/parse.jsx";
import { numberFieldDataAttrs } from "../utils/state-attributes-mapping.jsx";
const NAVIGATE_KEYS = new Set(["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Escape"]);
/**
 * The native input control of the number field. Renders an `<input>`.
 *
 * Solid 2 adaptations (mirrors
 * `packages/react/src/number-field/input/NumberFieldInput.tsx`):
 *
 * - `onInput` maps to React's `onChange` (Solid's `onChange` fires on
 *   commit, not every keystroke). IME composition handling is kept as-is
 *   via the `event.which === 229` early return in `onKeyDown`.
 * - Field integration goes through `useFieldControlValidation` which wires
 *   the visible input into `<Field.Root>`'s registration + validation
 *   pipeline. The blur handler calls `commitValidation` in `onBlur` mode,
 *   matching the post-review slider-thumb pattern (see
 *   `src/slider/thumb/slider-thumb.tsx:491-511`).
 * - `aria-invalid` is seeded with `"false"` via
 *   `getInputValidationProps()` so the key stays in Solid 2's DOM-spread
 *   `ownKeys` from render 1 (see
 *   `test/internals/merge-props-undefined-attr.test.tsx`).
 */
export function NumberFieldInput(props) {
  const context = useNumberFieldRootContext();
  const field = useFieldRootContext();
  const {
    clearErrors
  } = useFormContext();
  const hasTouchedInputRef = {
    current: false
  };
  const blockRevalidationRef = {
    current: false
  };

  // Hook reads its accessor inputs once at body to seed registration —
  // `untrack` keeps the snapshot reads out of the component-body
  // strict-read shell. Reactivity flows through the retained closures.
  const {
    getInputValidationProps
  } = untrack(() => useFieldControlValidation({
    value: () => context.value(),
    id: () => context.id,
    controlRef: context.inputRef,
    getValue: () => context.value()
  }));
  useValueChanged(() => context.value(), (_next, previousValue) => {
    const validateOnChange = field.shouldValidateOnChange();
    clearErrors(context.name());
    if (validateOnChange) {
      void field.validation.commit(context.value());
    }
    if (previousValue === context.value() || validateOnChange) {
      return;
    }
    if (blockRevalidationRef.current) {
      blockRevalidationRef.current = false;
      return;
    }
    void field.validation.commit(context.value(), true);
  });
  function onFocus(event) {
    if (event.defaultPrevented || context.readOnly() || context.disabled()) {
      return;
    }
    field.setFocused(true);
    if (hasTouchedInputRef.current) return;
    hasTouchedInputRef.current = true;
    const target = event.currentTarget;
    const length = target.value.length;
    target.setSelectionRange(length, length);
  }
  function onBlur(event) {
    if (event.defaultPrevented || context.readOnly() || context.disabled()) {
      return;
    }
    field.setTouched(true);
    field.setFocused(false);
    const hadManualInput = !context.allowInputSyncRef.current;
    const hadPendingProgrammaticChange = context.hasPendingCommitRef.current;
    context.allowInputSyncRef.current = true;
    const inputText = context.inputValue();
    if (inputText.trim() === "") {
      context.setValue(null, createChangeEventDetails(REASONS.inputClear, event));
      if (field.validationMode() === "onBlur") {
        void field.validation.commit(null);
      }
      context.onValueCommitted(null, createGenericEventDetails(REASONS.inputClear, event));
      return;
    }
    const formatOptions = context.formatOptionsRef.current;
    const parsedValue = parseNumber(inputText, context.locale(), formatOptions);
    if (parsedValue === null) {
      return;
    }
    const hasExplicitPrecision = formatOptions?.maximumFractionDigits != null || formatOptions?.minimumFractionDigits != null;
    const maxFrac = formatOptions?.maximumFractionDigits;
    const committed = hasExplicitPrecision && typeof maxFrac === "number" ? Number(parsedValue.toFixed(maxFrac)) : parsedValue;
    const nextEventDetails = createGenericEventDetails(REASONS.inputBlur, event);
    const currentValue = context.value();
    const shouldUpdateValue = currentValue !== committed;
    const shouldCommit = hadManualInput || shouldUpdateValue || hadPendingProgrammaticChange;
    if (field.validationMode() === "onBlur") {
      void field.validation.commit(committed);
    }
    if (shouldUpdateValue) {
      blockRevalidationRef.current = true;
      context.setValue(committed, createChangeEventDetails(REASONS.inputBlur, event));
    }
    if (shouldCommit) {
      context.onValueCommitted(committed, nextEventDetails);
    }
    const canonicalText = formatNumber(committed, context.locale(), formatOptions);
    const shouldPreserveFullPrecision = !hasExplicitPrecision && parsedValue === currentValue && inputText === formatNumberMaxPrecision(parsedValue, context.locale(), formatOptions);
    if (!shouldPreserveFullPrecision && inputText !== canonicalText) {
      context.setInputValue(canonicalText);
    }
  }
  function onInput(event) {
    if (event.defaultPrevented) return;
    context.allowInputSyncRef.current = false;
    const targetValue = event.currentTarget.value;
    if (targetValue.trim() === "") {
      context.setInputValue(targetValue);
      context.setValue(null, createChangeEventDetails(REASONS.inputClear, event));
      return;
    }
    const allowedNonNumericKeys = context.getAllowedNonNumericKeys();
    const isValidCharacterString = Array.from(targetValue).every(ch => {
      const isAsciiDigit = ch >= "0" && ch <= "9";
      const isArabicNumeral = ARABIC_DETECT_RE.test(ch);
      const isHanNumeral = HAN_DETECT_RE.test(ch);
      const isPersianNumeral = PERSIAN_DETECT_RE.test(ch);
      const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(ch);
      const isMinus = ANY_MINUS_DETECT_RE.test(ch);
      return isAsciiDigit || isArabicNumeral || isHanNumeral || isPersianNumeral || isFullwidthNumeral || isMinus || allowedNonNumericKeys.has(ch);
    });
    if (!isValidCharacterString) return;
    const parsedValue = parseNumber(targetValue, context.locale(), context.formatOptionsRef.current);
    context.setInputValue(targetValue);
    if (parsedValue !== null) {
      context.setValue(parsedValue, createChangeEventDetails(REASONS.inputChange, event));
    }
  }
  function onKeyDown(event) {
    if (event.defaultPrevented || context.readOnly() || context.disabled()) {
      return;
    }
    context.allowInputSyncRef.current = true;
    const allowedNonNumericKeys = context.getAllowedNonNumericKeys();
    let isAllowedNonNumericKey = allowedNonNumericKeys.has(event.key);
    const {
      decimal,
      currency,
      percentSign
    } = getNumberLocaleDetails(context.locale(), context.formatOptionsRef.current);
    const selectionStart = event.currentTarget.selectionStart;
    const selectionEnd = event.currentTarget.selectionEnd;
    const inputText = context.inputValue();
    const isAllSelected = selectionStart === 0 && selectionEnd === inputText.length;
    const selectionContainsIndex = index => selectionStart != null && selectionEnd != null && index >= selectionStart && index < selectionEnd;
    if (ANY_MINUS_DETECT_RE.test(event.key) && Array.from(allowedNonNumericKeys).some(k => ANY_MINUS_DETECT_RE.test(k || ""))) {
      const existingIndex = inputText.search(ANY_MINUS_RE);
      const isReplacingExisting = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);
      isAllowedNonNumericKey = !(ANY_MINUS_DETECT_RE.test(inputText) || ANY_PLUS_DETECT_RE.test(inputText)) || isAllSelected || isReplacingExisting;
    }
    if (ANY_PLUS_DETECT_RE.test(event.key) && Array.from(allowedNonNumericKeys).some(k => ANY_PLUS_DETECT_RE.test(k || ""))) {
      const existingIndex = inputText.search(ANY_PLUS_RE);
      const isReplacingExisting = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);
      isAllowedNonNumericKey = !(ANY_MINUS_DETECT_RE.test(inputText) || ANY_PLUS_DETECT_RE.test(inputText)) || isAllSelected || isReplacingExisting;
    }
    for (const symbol of [decimal, currency, percentSign]) {
      if (event.key === symbol && symbol) {
        const symbolIndex = inputText.indexOf(symbol);
        const isSymbolHighlighted = selectionContainsIndex(symbolIndex);
        isAllowedNonNumericKey = !inputText.includes(symbol) || isAllSelected || isSymbolHighlighted;
      }
    }
    const isAsciiDigit = event.key >= "0" && event.key <= "9";
    const isArabicNumeral = ARABIC_DETECT_RE.test(event.key);
    const isHanNumeral = HAN_DETECT_RE.test(event.key);
    const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(event.key);
    const isNavigateKey = NAVIGATE_KEYS.has(event.key);

    // IME composition (`event.which === 229`) takes the early-return path
    // so we don't eat the pinyin/Japanese/etc. stroke that the IME will
    // later commit through a regular `input` event.
    if (event.which === 229 || event.altKey || event.ctrlKey || event.metaKey || isAllowedNonNumericKey || isAsciiDigit || isArabicNumeral || isFullwidthNumeral || isHanNumeral || isNavigateKey) {
      return;
    }
    const parsedValue = parseNumber(inputText, context.locale(), context.formatOptionsRef.current);
    const amount = context.getStepAmount(event) ?? DEFAULT_STEP;

    // Prevent insertion of text or caret from moving.
    event.preventDefault();
    event.stopPropagation();
    const commitDetails = createGenericEventDetails(REASONS.keyboard, event);
    if (event.key === "ArrowUp") {
      context.incrementValue(amount, {
        direction: 1,
        currentValue: parsedValue,
        event,
        reason: REASONS.keyboard
      });
      context.onValueCommitted(context.lastChangedValueRef.current ?? context.valueRef.current, commitDetails);
    } else if (event.key === "ArrowDown") {
      context.incrementValue(amount, {
        direction: -1,
        currentValue: parsedValue,
        event,
        reason: REASONS.keyboard
      });
      context.onValueCommitted(context.lastChangedValueRef.current ?? context.valueRef.current, commitDetails);
    } else if (event.key === "Home" && context.min() != null) {
      context.setValue(context.min(), createChangeEventDetails(REASONS.keyboard, event));
      context.onValueCommitted(context.lastChangedValueRef.current ?? context.valueRef.current, commitDetails);
    } else if (event.key === "End" && context.max() != null) {
      context.setValue(context.max(), createChangeEventDetails(REASONS.keyboard, event));
      context.onValueCommitted(context.lastChangedValueRef.current ?? context.valueRef.current, commitDetails);
    }
  }
  function onPaste(event) {
    if (event.defaultPrevented || context.readOnly() || context.disabled()) {
      return;
    }
    event.preventDefault();
    const clipboardData = event.clipboardData;
    if (!clipboardData) return;
    const pastedData = clipboardData.getData("text/plain");
    const parsedValue = parseNumber(pastedData, context.locale(), context.formatOptionsRef.current);
    if (parsedValue !== null) {
      context.allowInputSyncRef.current = false;
      context.setValue(parsedValue, createChangeEventDetails(REASONS.inputPaste, event));
      context.setInputValue(pastedData);
    }
  }
  const userStyling = resolveClassStyle(props, () => context.state());
  const userProps = omit(props, "class", "style", "attrs", "as");
  const localRef = el => {
    context.inputRef.current = el;
  };
  const internalSlot = {
    id: context.id,
    get required() {
      return context.required();
    },
    get disabled() {
      return context.disabled();
    },
    get readOnly() {
      return context.readOnly();
    },
    get inputMode() {
      return context.inputMode();
    },
    get value() {
      return context.inputValue();
    },
    type: "text",
    autocomplete: "off",
    autocorrect: "off",
    spellcheck: false,
    "aria-roledescription": props["aria-roledescription"] ?? "Number field",
    onFocus,
    onBlur,
    onInput,
    onKeyDown,
    onPaste
  };
  const styledUserProps = mergeRootProps(userStyling, userProps);
  let merged = mergeRootProps(internalSlot, numberFieldDataAttrs(context.state));
  merged = mergeRootProps(merged, getInputValidationProps());
  merged = mergeRootProps(merged, styledUserProps);
  const Tag = dynamic(() => props.as ?? "input");
  return <Tag {...merged} ref={composeRefs(localRef, userRef(props))} />;
}
//# sourceMappingURL=number-field-input.jsx.map
