import { dynamic } from "@solidjs/web";
import { createMemo, createRenderEffect, createSignal, omit } from "solid-js";
import { createChangeEventDetails, createGenericEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { composeRefs, userRef } from "../../internals/compose-refs.jsx";
import { CompositeList } from "../../internals/composite/composite-list.jsx";
import { createControlledState } from "../../internals/create-controlled-state.jsx";
import { contains } from "../../internals/dom/contains.jsx";
import { ownerDocument } from "../../internals/dom/owner-document.jsx";
import { useFieldRootContext } from "../../internals/field-context.jsx";
import { fieldStateAttrs } from "../../internals/field-state-attrs.jsx";
import { useFormContext } from "../../internals/form-context.jsx";
import { useAriaLabelledBy } from "../../internals/labelable-provider/use-aria-labelled-by.jsx";
import { useLabelableId } from "../../internals/labelable-provider/use-labelable-id.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { ariaDescribedBy as seedAriaDescribedBy, ariaLabelledBy as seedAriaLabelledBy } from "../../internals/seed-aria-attr.jsx";
import { createStableCallback } from "../../internals/stable-callback.jsx";
import { useFieldControlValidation } from "../../internals/use-field-control-validation/use-field-control-validation.jsx";
import { useValueChanged } from "../../internals/use-value-changed.jsx";
import { VISUALLY_HIDDEN_STYLE } from "../../internals/visually-hidden-style.jsx";
import { getOTPValidationConfig, normalizeOTPValue, stripOTPWhitespace } from "../utils/otp.jsx";
import { OTPFieldRootContext } from "./otp-field-root-context.jsx";
const VISUALLY_HIDDEN_INPUT_STYLE = {
  ...VISUALLY_HIDDEN_STYLE,
  padding: "1px"
};
/**
 * Groups all `<OTPField.Input>` slots and manages the shared OTP value, focus
 * coordination, clipboard distribution, validation and form integration.
 * Renders a `<div role="group">` (or `as` override).
 *
 * §1 wave 2: replaces `renderElement` with `dynamic(() => props.as ?? "div")`.
 * Field-validity attrs flow through `fieldStateAttrs(state)`; OTP-specific
 * presence attrs (`data-complete`, `data-disabled`, `data-readonly`,
 * `data-required`) are emitted via inline reactive getters on the internal
 * slot bag. The `seedAriaLabelledBy`/`seedAriaDescribedBy` helpers and the
 * field-validation a11y bag compose via `mergeRootProps`.
 */
export function OTPFieldRoot(props) {
  const field = useFieldRootContext();
  const form = useFormContext();
  const disabled = () => field.disabled() || props.disabled === true;
  const readOnly = () => props.readOnly === true;
  const required = () => props.required === true;
  const mask = () => props.mask === true;
  const autoSubmit = () => props.autoSubmit === true;
  const length = () => props.length;
  const validationType = () => props.validationType ?? "numeric";
  const autoComplete = () => props.autoComplete ?? "one-time-code";
  const sanitizeValue = () => props.sanitizeValue;
  const name = () => field.name() ?? props.name;
  const formId = () => props.form;
  const {
    id: rootId
  } = useLabelableId({
    id: typeof props.id === "string" ? props.id : undefined
  });

  // Controlled / uncontrolled value (Base UI `value` / `defaultValue`
  // contract): the `value` prop wins while not `undefined` (an explicit
  // `""` IS a controlled empty value); otherwise an internal signal
  // (seeded once from `defaultValue`, default `""`) backs the state and
  // `setValue` below writes it.
  const [valueUnwrapped, setUncontrolledValue] = createControlledState({
    controlled: () => props.value,
    default: () => props.defaultValue,
    fallback: "",
    name: "<OTPField.Root>",
    state: "value"
  });
  const value = createMemo(() => normalizeOTPValue(valueUnwrapped(), length(), validationType(), sanitizeValue()));
  const rootRef = {
    current: null
  };
  const elementsRef = [];
  let pendingFocus = null;
  let pendingCompleteValue = null;
  const firstInputRef = {
    get current() {
      return elementsRef[0] ?? null;
    },
    set current(_value) {
      // Read-only shim — CompositeList owns `elementsRef` mutations.
    }
  };
  const ariaLabelledBy = useAriaLabelledBy(() => typeof props["aria-labelledby"] === "string" ? props["aria-labelledby"] : undefined);
  const inputAriaLabelledBy = createMemo(() => {
    if (typeof props["aria-labelledby"] === "string") return undefined;
    return ariaLabelledBy();
  });
  const validationConfig = createMemo(() => getOTPValidationConfig(validationType()));
  const pattern = () => validationConfig()?.slotPattern ?? undefined;
  const hiddenInputPattern = () => {
    const cfg = validationConfig();
    return cfg ? cfg.getRootPattern(length()) : undefined;
  };
  const inputMode = () => props.inputMode ?? validationConfig()?.inputMode;
  const hasValidLength = () => {
    const l = length();
    return Number.isInteger(l) && l > 0;
  };
  const filled = () => value() !== "";
  const [focusedIndex, setFocusedIndex] = createSignal(0);
  const [focused, setFocusedState] = createSignal(false);
  const [inputCount, setInputCount] = createSignal(0, {
    ownedWrite: true
  });
  if (process.env.NODE_ENV !== "production") {
    createRenderEffect(() => ({
      count: inputCount(),
      len: length(),
      sanitize: sanitizeValue(),
      vtype: validationType()
    }), ({
      count,
      len,
      sanitize,
      vtype
    }) => {
      if (!Number.isInteger(len) || len <= 0) {
        console.warn(`Base UI: <OTPField.Root> \`length\` must be a positive integer. Received \`length={${String(len)}}\`.`);
      } else if (count !== 0 && count !== len) {
        console.warn(`Base UI: <OTPField.Root> \`length\` must match the number of rendered <OTPField.Input /> parts. Received \`length={${len}}\` but rendered ${count} input${count === 1 ? "" : "s"}.`);
      }
      if (sanitize != null && vtype !== "none") {
        console.warn('Base UI: <OTPField.Root> `sanitizeValue` is only used when `validationType="none"`.');
      }
    });
  }
  const activeIndex = createMemo(() => {
    const l = length();
    if (focused()) {
      return Math.min(focusedIndex(), Math.max(l - 1, 0));
    }
    return Math.min(value().length, Math.max(l - 1, 0));
  });
  const controlRef = {
    get current() {
      return firstInputRef.current;
    },
    set current(_v) {
      // No-op — the first slot input is the registered control.
    }
  };
  const validation = useFieldControlValidation({
    value: () => value(),
    id: () => rootId,
    controlRef,
    getValue: () => value()
  });
  useValueChanged(filled, () => {
    field.setFilled(filled());
  });
  useValueChanged(value, () => {
    const current = value();
    field.setDirty(current !== field.validityData().initialValue);
    if (field.shouldValidateOnChange()) {
      void validation.commit(current);
    } else {
      void validation.commit(current, true);
    }
    const pendingComplete = pendingCompleteValue;
    if (pendingComplete != null) {
      pendingCompleteValue = null;
      if (pendingComplete.value === current) {
        props.onValueComplete?.(current, pendingComplete.eventDetails);
        if (autoSubmit()) requestSubmit();
      }
    }
    const pendingFocusSnapshot = pendingFocus;
    if (pendingFocusSnapshot != null) {
      pendingFocus = null;
      if (pendingFocusSnapshot.value === current) {
        focusInput(pendingFocusSnapshot.index);
      }
    }
  });
  const focusInput = createStableCallback(() => index => {
    const max = Math.max(elementsRef.length - 1, 0);
    const targetIndex = Math.min(Math.max(index, 0), max);
    const target = elementsRef[targetIndex];
    target?.focus();
    target?.select?.();
  });
  const queueFocusInput = createStableCallback(() => (index, nextValue) => {
    pendingFocus = {
      index,
      value: nextValue
    };
  });
  function requestSubmit() {
    let formElement = validation.inputRef.current?.form ?? elementsRef[0]?.form ?? null;
    const formIdValue = formId();
    if (formIdValue) {
      const doc = ownerDocument(rootRef.current);
      const associatedElement = doc.getElementById(formIdValue);
      if (associatedElement?.tagName === "FORM") {
        formElement = associatedElement;
      }
    }
    if (formElement && typeof formElement.requestSubmit === "function") {
      formElement.requestSubmit();
    }
  }
  useValueChanged(value, () => {
    const currentName = name();
    if (currentName != null && form.clearErrors) {
      form.clearErrors(currentName);
    }
  });
  const setValue = createStableCallback(() => (nextValue, details) => {
    const normalizedValue = normalizeOTPValue(nextValue, length(), validationType(), sanitizeValue());
    const previous = value();
    if (normalizedValue === previous) return null;
    props.onValueChange?.(normalizedValue, details);
    // Cancel gate matches React Base UI `OTPFieldRoot.setValue`:
    // `details.cancel()` keeps the uncontrolled state unchanged, queues
    // no pending complete, and returns `null` so callers skip the
    // focus-advance bookkeeping.
    if (details.isCanceled) return null;
    setUncontrolledValue(normalizedValue);
    const l = length();
    if (normalizedValue.length === l && previous.length !== l) {
      pendingCompleteValue = {
        value: normalizedValue,
        eventDetails: createGenericEventDetails(details.reason, details.event)
      };
    } else if (normalizedValue.length !== l) {
      pendingCompleteValue = null;
    }
    return normalizedValue;
  });
  const reportValueInvalid = createStableCallback(() => (invalidValue, details) => {
    props.onValueInvalid?.(invalidValue, details);
  });
  const handleInputFocus = createStableCallback(() => (index, event) => {
    const currentValue = value();
    if (index > currentValue.length) {
      focusInput(Math.min(currentValue.length, length() - 1));
      return;
    }
    setFocusedIndex(index);
    setFocusedState(true);
    field.setFocused(true);
    event.currentTarget.select();
  });
  const handleInputBlur = createStableCallback(() => event => {
    const relatedTarget = event.relatedTarget;
    if (contains(rootRef.current, relatedTarget)) return;
    field.setTouched(true);
    setFocusedState(false);
    field.setFocused(false);
    if (field.validationMode() === "onBlur") {
      void validation.commit(value());
    }
  });
  const getInputId = index => {
    if (rootId == null) return undefined;
    return index === 0 ? rootId : `${rootId}-${index + 1}`;
  };
  const state = createMemo(() => ({
    ...field.state(),
    complete: value().length === length(),
    disabled: disabled(),
    filled: filled(),
    focused: focused(),
    length: length(),
    readOnly: readOnly(),
    required: required(),
    value: value()
  }));
  const contextValue = {
    activeIndex,
    autoComplete,
    disabled,
    form: formId,
    focusInput,
    queueFocusInput,
    getInputId,
    handleInputBlur,
    handleInputFocus,
    inputMode,
    inputAriaLabelledBy,
    invalid: () => field.invalid(),
    length,
    mask,
    pattern,
    reportValueInvalid,
    readOnly,
    required,
    sanitizeValue,
    setValue,
    state,
    validationType: () => validationType(),
    value,
    present: true
  };
  const userStyling = resolveClassStyle(props, () => state());
  const userProps = omit(props, "class", "style", "attrs", "as", "autoComplete", "autoSubmit", "children", "defaultValue", "disabled", "form", "id", "inputMode", "length", "mask", "name", "onValueChange", "onValueComplete", "onValueInvalid", "readOnly", "required", "sanitizeValue", "validationType", "value", "aria-labelledby", "aria-describedby");
  const ariaDescribedBy = () => typeof props["aria-describedby"] === "string" ? props["aria-describedby"] : undefined;
  const internalSlot = {
    id: rootId,
    role: "group",
    get "data-complete"() {
      return value().length === length() ? "" : undefined;
    },
    get "data-disabled"() {
      return disabled() ? "" : undefined;
    },
    get "data-readonly"() {
      return readOnly() ? "" : undefined;
    },
    get "data-required"() {
      return required() ? "" : undefined;
    }
  };
  const styledUserProps = mergeRootProps(userStyling, userProps);
  let merged = mergeRootProps(internalSlot, seedAriaLabelledBy(ariaLabelledBy) ?? {});
  merged = mergeRootProps(merged, seedAriaDescribedBy(ariaDescribedBy) ?? {});
  merged = mergeRootProps(merged, validation.getValidationProps() ?? {});
  merged = mergeRootProps(merged, fieldStateAttrs(state));
  merged = mergeRootProps(merged, styledUserProps);
  const Tag = dynamic(() => props.as ?? "div");
  return <Tag {...merged} ref={composeRefs(el => {
    rootRef.current = el;
  }, userRef(props))}>
			<OTPFieldRootContext value={contextValue}>
				<CompositeList elementsRef={elementsRef} onMapChange={map => {
        setInputCount(map.size);
      }}>
					{props.children}
				</CompositeList>
				<HiddenValidationInput enabled={hasValidLength} autoComplete={autoComplete} disabled={disabled} form={formId} getRootId={() => rootId} hasName={() => name() != null} inputMode={inputMode} length={length} name={name} pattern={hiddenInputPattern} readOnly={readOnly} required={required} reportValueInvalid={reportValueInvalid} sanitizeValue={sanitizeValue} setValue={setValue} validation={validation} validationType={validationType} value={value} focusFirstInput={() => focusInput(0)} queueFocusInput={queueFocusInput} />
			</OTPFieldRootContext>
		</Tag>;
}
function HiddenValidationInput(props) {
  if (!props.enabled()) return null;
  const id = () => {
    const rid = props.getRootId();
    return rid && !props.hasName() ? `${rid}-hidden-input` : undefined;
  };
  const hiddenStyle = () => props.hasName() ? VISUALLY_HIDDEN_INPUT_STYLE : {
    ...VISUALLY_HIDDEN_STYLE
  };
  const inputValidationProps = props.validation.getInputValidationProps({
    onFocus: () => {
      props.focusFirstInput();
    },
    onChange: event => {
      if (event.defaultPrevented) return;
      const rawValue = event.currentTarget.value;
      const normalizedValue = normalizeOTPValue(rawValue, props.length(), props.validationType(), props.sanitizeValue());
      if (stripOTPWhitespace(rawValue).length > normalizedValue.length) {
        props.reportValueInvalid(rawValue, createGenericEventDetails(REASONS.inputChange, event));
      }
      const committed = props.setValue(normalizedValue, createChangeEventDetails(REASONS.inputChange, event));
      if (committed != null && committed !== "") {
        props.queueFocusInput(committed.length - 1, committed);
      }
    }
  });
  const ref = el => {
    props.validation.inputRef.current = el;
  };
  return (
    // biome-ignore lint/a11y/noAriaHiddenOnFocusable: mirrors React Base UI's hidden validation input.
    <input {...inputValidationProps} ref={ref} type="text" id={id()} form={props.form()} name={props.name()} value={props.value()} autocomplete={props.autoComplete() ?? "one-time-code"} inputmode={props.inputMode()} minlength={props.length()} maxlength={props.length()} pattern={props.pattern()} disabled={props.disabled() || undefined} readonly={props.readOnly() || undefined} required={props.required() || undefined} aria-hidden="true" tabindex={-1} style={hiddenStyle()} />
  );
}
//# sourceMappingURL=otp-field-root.jsx.map
