import { dynamic } from "@solidjs/web";
import { createMemo, omit } from "solid-js";
import { createChangeEventDetails, createGenericEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { composeRefs, userRef } from "../../internals/compose-refs.jsx";
import { useCompositeListItem } from "../../internals/composite/composite-list.jsx";
import { fieldStateAttrs } from "../../internals/field-state-attrs.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { ariaInvalid as seedAriaInvalid, ariaLabelledBy as seedAriaLabelledBy } from "../../internals/seed-aria-attr.jsx";
import { getOTPFieldInputState, useOTPFieldRootContext } from "../root/otp-field-root-context.jsx";
import { normalizeOTPValue, removeOTPCharacter, replaceOTPValue, stripOTPWhitespace } from "../utils/otp.jsx";
/**
 * A single OTP slot. Renders an `<input>` (or `as` override).
 *
 * §1 wave 2: replaces `renderElement` with `dynamic(() => props.as ?? "input")`.
 * Field-validity attrs flow through `fieldStateAttrs(inputState)`; OTP-specific
 * presence attrs (`data-complete`, `data-disabled`, `data-readonly`,
 * `data-required`) are emitted as inline reactive getters on the internal slot
 * bag.
 */
export function OTPFieldInput(props) {
  const root = useOTPFieldRootContext();
  const {
    ref: listItemRef,
    index
  } = useCompositeListItem();
  const slotValue = createMemo(() => root.value()[index()] ?? "");
  const inputState = createMemo(() => getOTPFieldInputState(root.state(), slotValue(), index()));
  const externalAriaLabel = () => typeof props["aria-label"] === "string" ? props["aria-label"] : undefined;
  const externalAriaLabelledBy = () => typeof props["aria-labelledby"] === "string" ? props["aria-labelledby"] : undefined;
  const inheritedLabel = () => externalAriaLabelledBy() ?? root.inputAriaLabelledBy();
  const ariaLabel = () => index() === 0 ? undefined : externalAriaLabel();
  const userStyling = resolveClassStyle(props, () => inputState());
  const userProps = omit(props, "class", "style", "attrs", "as", "aria-label", "aria-labelledby");
  const internalSlot = {
    get id() {
      return root.getInputId(index());
    },
    get value() {
      return slotValue();
    },
    get type() {
      return root.mask() ? "password" : "text";
    },
    get inputmode() {
      return root.inputMode();
    },
    get autocomplete() {
      return index() === 0 ? root.autoComplete() : "off";
    },
    autocorrect: "off",
    spellcheck: false,
    get enterkeyhint() {
      return index() === root.length() - 1 ? "done" : "next";
    },
    get maxlength() {
      return index() === 0 ? root.length() : 1;
    },
    get tabindex() {
      return root.activeIndex() === index() ? 0 : -1;
    },
    get disabled() {
      return root.disabled() || undefined;
    },
    get form() {
      return root.form();
    },
    get pattern() {
      return root.pattern();
    },
    get readonly() {
      return root.readOnly() || undefined;
    },
    get required() {
      return root.required() || undefined;
    },
    get "aria-label"() {
      return ariaLabel();
    },
    get "data-complete"() {
      return root.state().complete ? "" : undefined;
    },
    get "data-disabled"() {
      return root.disabled() ? "" : undefined;
    },
    get "data-readonly"() {
      return root.readOnly() ? "" : undefined;
    },
    get "data-required"() {
      return root.required() ? "" : undefined;
    },
    // `data-active="true"` on the focused slot at the cursor position (and
    // `"false"` otherwise), mirroring the `input-otp` library's per-slot
    // active marker. Consumers key the active-cell ring/border off
    // `data-[active=true]:…`; the field-wide `data-focused` covers every
    // slot, so it can't drive a single-cell highlight. Gated on `focused()`
    // so no cell lights up while the field is blurred.
    get "data-active"() {
      return root.state().focused && root.activeIndex() === index() ? "true" : "false";
    },
    onMouseDown(event) {
      if (event.defaultPrevented || root.disabled()) return;
      event.preventDefault();
      root.focusInput(index());
    },
    onFocus(event) {
      if (event.defaultPrevented || root.disabled()) return;
      root.handleInputFocus(index(), event);
    },
    onBlur(event) {
      if (event.defaultPrevented) return;
      root.handleInputBlur(event);
    },
    onInput(event) {
      if (event.defaultPrevented || root.disabled() || root.readOnly()) return;
      const rawValue = event.currentTarget.value;
      const nextDigits = normalizeOTPValue(rawValue, root.length(), root.validationType(), root.sanitizeValue());
      const didSanitize = stripOTPWhitespace(rawValue).length > nextDigits.length;
      if (didSanitize) {
        root.reportValueInvalid(rawValue, createGenericEventDetails(REASONS.inputChange, event));
      }
      const currentSlotValue = slotValue();
      const currentValue = root.value();
      const slotIndex = index();
      if (nextDigits === "") {
        if (rawValue === "") {
          root.setValue(removeOTPCharacter(currentValue, slotIndex), createChangeEventDetails(REASONS.inputClear, event));
        } else {
          // The typed text sanitized away entirely (e.g. a letter in a
          // numeric field). Restore the slot's committed digit so the
          // rejected character doesn't linger in the DOM input — slot 0's
          // `maxlength` would otherwise let invalid chars pile up. When the
          // slot is empty this clears the input back to "".
          event.currentTarget.value = currentSlotValue;
          if (currentSlotValue !== "") event.currentTarget.select();
        }
        return;
      }
      const nextValue = replaceOTPValue(currentValue, slotIndex, nextDigits, root.length(), root.validationType(), root.sanitizeValue());
      const committedValue = root.setValue(nextValue, createChangeEventDetails(REASONS.inputChange, event));
      if (committedValue == null) {
        // The change was canceled, so the signal still holds the old
        // value. Restore the slot's committed digit ourselves rather
        // than leaving the rejected character for the `value` binding
        // to overwrite on its next run.
        event.currentTarget.value = currentSlotValue;
        return;
      }
      const nextInput = Math.min(slotIndex + nextDigits.length, root.length() - 1);
      root.queueFocusInput(nextInput, committedValue);
    },
    onKeyDown(event) {
      if (event.defaultPrevented || root.disabled()) return;
      const slotIndex = index();
      const currentValue = root.value();
      const currentSlotValue = slotValue();
      if (event.key === "ArrowLeft") {
        event.preventDefault();
        event.stopPropagation();
        root.focusInput(Math.max(0, slotIndex - 1));
        return;
      }
      if (event.key === "ArrowRight") {
        event.preventDefault();
        event.stopPropagation();
        root.focusInput(Math.min(root.length() - 1, slotIndex + 1));
        return;
      }
      if (event.key === "Home") {
        event.preventDefault();
        event.stopPropagation();
        root.focusInput(0);
        return;
      }
      if (event.key === "End") {
        event.preventDefault();
        event.stopPropagation();
        root.focusInput(Math.max(currentValue.length - 1, 0));
        return;
      }
      if (root.readOnly()) return;
      if (event.key === "Delete") {
        event.preventDefault();
        event.stopPropagation();
        const committed = root.setValue(removeOTPCharacter(currentValue, slotIndex), createChangeEventDetails(REASONS.keyboard, event));
        if (committed != null) {
          root.queueFocusInput(slotIndex, committed);
        }
        return;
      }
      const inputValue = event.currentTarget.value;
      const fullSelection = event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === inputValue.length;
      if (event.key.length === 1 && fullSelection && currentSlotValue === event.key) {
        event.preventDefault();
        event.stopPropagation();
        root.focusInput(Math.min(root.length() - 1, slotIndex + 1));
        return;
      }
      if (event.key === "Backspace") {
        event.preventDefault();
        event.stopPropagation();
        const deleteIndex = currentSlotValue === "" ? Math.max(0, slotIndex - 1) : slotIndex;
        const targetIndex = Math.max(0, slotIndex - 1);
        const committed = root.setValue(removeOTPCharacter(currentValue, deleteIndex), createChangeEventDetails(REASONS.keyboard, event));
        if (committed != null) {
          root.queueFocusInput(targetIndex, committed);
        }
      }
    },
    onPaste(event) {
      if (event.defaultPrevented || root.disabled() || root.readOnly()) return;
      let rawValue = "";
      try {
        rawValue = event.clipboardData?.getData("text/plain") ?? "";
      } catch {
        return;
      }
      event.preventDefault();
      const nextDigits = normalizeOTPValue(rawValue, root.length(), root.validationType(), root.sanitizeValue());
      const didSanitize = stripOTPWhitespace(rawValue).length > nextDigits.length;
      if (didSanitize) {
        root.reportValueInvalid(rawValue, createGenericEventDetails(REASONS.inputPaste, event));
      }
      if (nextDigits === "") return;
      const slotIndex = index();
      const committedValue = root.setValue(replaceOTPValue(root.value(), slotIndex, nextDigits, root.length(), root.validationType(), root.sanitizeValue()), createChangeEventDetails(REASONS.inputPaste, event));
      if (committedValue != null) {
        const nextInput = Math.min(slotIndex + nextDigits.length, root.length() - 1);
        root.queueFocusInput(nextInput, committedValue);
      }
    }
  };
  const styledUserProps = mergeRootProps(userStyling, userProps);
  let merged = mergeRootProps(internalSlot, seedAriaLabelledBy(() => ariaLabel() != null ? "" : inheritedLabel()) ?? {});
  merged = mergeRootProps(merged, seedAriaInvalid(() => root.invalid()) ?? {});
  merged = mergeRootProps(merged, fieldStateAttrs(inputState));
  merged = mergeRootProps(merged, styledUserProps);
  const Tag = dynamic(() => props.as ?? "input");
  return <Tag {...merged} ref={composeRefs(listItemRef, userRef(props))} />;
}
//# sourceMappingURL=otp-field-input.jsx.map
