// packages/components/src/form-token-field/index.tsx
import clsx from "clsx";
import { useEffect, useRef, useState } from "@wordpress/element";
import { __, _n, sprintf } from "@wordpress/i18n";
import { useDebounce, useInstanceId, usePrevious } from "@wordpress/compose";
import { speak } from "@wordpress/a11y";
import { isShallowEqual } from "@wordpress/is-shallow-equal";
import Token from "./token.js";
import TokenInput from "./token-input.js";
import { TokensAndInputWrapperFlex } from "./styles.js";
import SuggestionsList from "./suggestions-list.js";
import { FlexItem } from "../flex/index.js";
import { StyledHelp, StyledLabel } from "../base-control/styles/base-control-styles.js";
import { useDeprecated36pxDefaultSizeProp } from "../utils/use-deprecated-props.js";
import { withIgnoreIMEEvents } from "../utils/with-ignore-ime-events.js";
import { maybeWarnDeprecated36pxSize } from "../utils/deprecated-36px-size.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
var identity = (value) => value;
function FormTokenField(props) {
  const {
    autoCapitalize,
    autoComplete,
    maxLength,
    placeholder,
    label = __("Add item"),
    className,
    suggestions = [],
    maxSuggestions = 100,
    value = [],
    displayTransform = identity,
    saveTransform = (token) => token.trim(),
    onChange = () => {
    },
    onInputChange = () => {
    },
    onFocus = void 0,
    isBorderless = false,
    disabled = false,
    tokenizeOnSpace = false,
    messages = {
      added: __("Item added."),
      removed: __("Item removed."),
      remove: __("Remove item"),
      __experimentalInvalid: __("Invalid item")
    },
    __experimentalRenderItem,
    __experimentalExpandOnFocus = false,
    __experimentalValidateInput = () => true,
    __experimentalShowHowTo = true,
    __next40pxDefaultSize = false,
    __experimentalAutoSelectFirstMatch = false,
    tokenizeOnBlur = false
  } = useDeprecated36pxDefaultSizeProp(props);
  maybeWarnDeprecated36pxSize({
    componentName: "FormTokenField",
    size: void 0,
    __next40pxDefaultSize
  });
  const instanceId = useInstanceId(FormTokenField);
  const [incompleteTokenValue, setIncompleteTokenValue] = useState("");
  const [inputOffsetFromEnd, setInputOffsetFromEnd] = useState(0);
  const [isActive, setIsActive] = useState(false);
  const [isExpanded, setIsExpanded] = useState(false);
  const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
  const [selectedSuggestionScroll, setSelectedSuggestionScroll] = useState(false);
  const prevSuggestions = usePrevious(suggestions);
  const prevValue = usePrevious(value);
  const input = useRef(null);
  const tokensAndInput = useRef(null);
  const debouncedSpeak = useDebounce(speak, 500);
  useEffect(() => {
    if (isActive && !hasFocus()) {
      focus();
    }
  }, [isActive]);
  useEffect(() => {
    const suggestionsDidUpdate = !isShallowEqual(suggestions, prevSuggestions || []);
    if (suggestionsDidUpdate || value !== prevValue) {
      updateSuggestions(suggestionsDidUpdate);
    }
  }, [suggestions, prevSuggestions, value, prevValue]);
  useEffect(() => {
    updateSuggestions();
  }, [incompleteTokenValue]);
  useEffect(() => {
    updateSuggestions();
  }, [__experimentalAutoSelectFirstMatch]);
  if (disabled && isActive) {
    setIsActive(false);
    setIncompleteTokenValue("");
  }
  function focus() {
    input.current?.focus();
  }
  function hasFocus() {
    return input.current === input.current?.ownerDocument.activeElement;
  }
  function onFocusHandler(event) {
    if (hasFocus() || event.target === tokensAndInput.current) {
      setIsActive(true);
      setIsExpanded(__experimentalExpandOnFocus || isExpanded);
    } else {
      setIsActive(false);
    }
    if ("function" === typeof onFocus) {
      onFocus(event);
    }
  }
  function onBlur(event) {
    if (inputHasValidValue() && __experimentalValidateInput(incompleteTokenValue)) {
      setIsActive(false);
      if (tokenizeOnBlur && inputHasValidValue()) {
        addNewToken(incompleteTokenValue);
      }
    } else {
      setIncompleteTokenValue("");
      setInputOffsetFromEnd(0);
      setIsActive(false);
      if (__experimentalExpandOnFocus) {
        const hasFocusWithin = event.relatedTarget === tokensAndInput.current;
        setIsExpanded(hasFocusWithin);
      } else {
        setIsExpanded(false);
      }
      setSelectedSuggestionIndex(-1);
      setSelectedSuggestionScroll(false);
    }
  }
  function onKeyDown(event) {
    let preventDefault = false;
    if (event.defaultPrevented) {
      return;
    }
    switch (event.key) {
      case "Backspace":
        preventDefault = handleDeleteKey(deleteTokenBeforeInput);
        break;
      case "Enter":
        preventDefault = addCurrentToken();
        break;
      case "ArrowLeft":
        preventDefault = handleLeftArrowKey();
        break;
      case "ArrowUp":
        preventDefault = handleUpArrowKey();
        break;
      case "ArrowRight":
        preventDefault = handleRightArrowKey();
        break;
      case "ArrowDown":
        preventDefault = handleDownArrowKey();
        break;
      case "Delete":
        preventDefault = handleDeleteKey(deleteTokenAfterInput);
        break;
      case "Space":
        if (tokenizeOnSpace) {
          preventDefault = addCurrentToken();
        }
        break;
      case "Escape":
        preventDefault = handleEscapeKey(event);
        break;
      case "Tab":
        preventDefault = handleTabKey(event);
        break;
      default:
        break;
    }
    if (preventDefault) {
      event.preventDefault();
    }
  }
  function onKeyPress(event) {
    let preventDefault = false;
    switch (event.key) {
      case ",":
        preventDefault = handleCommaKey();
        break;
      default:
        break;
    }
    if (preventDefault) {
      event.preventDefault();
    }
  }
  function onContainerTouched(event) {
    if (event.target === tokensAndInput.current && isActive) {
      event.preventDefault();
    }
  }
  function onTokenClickRemove(event) {
    deleteToken(event.value);
    focus();
  }
  function onSuggestionHovered(suggestion) {
    const index = getMatchingSuggestions().indexOf(suggestion);
    if (index >= 0) {
      setSelectedSuggestionIndex(index);
      setSelectedSuggestionScroll(false);
    }
  }
  function onSuggestionSelected(suggestion) {
    addNewToken(suggestion);
  }
  function onInputChangeHandler(event) {
    const text = event.value;
    const separator = tokenizeOnSpace ? /[ ,\t]+/ : /[,\t]+/;
    const items = text.split(separator);
    const tokenValue = items[items.length - 1] || "";
    if (items.length > 1) {
      addNewTokens(items.slice(0, -1));
    }
    setIncompleteTokenValue(tokenValue);
    onInputChange(tokenValue);
  }
  function handleDeleteKey(_deleteToken) {
    let preventDefault = false;
    if (hasFocus() && isInputEmpty()) {
      _deleteToken();
      preventDefault = true;
    }
    return preventDefault;
  }
  function handleLeftArrowKey() {
    let preventDefault = false;
    if (isInputEmpty()) {
      moveInputBeforePreviousToken();
      preventDefault = true;
    }
    return preventDefault;
  }
  function handleRightArrowKey() {
    let preventDefault = false;
    if (isInputEmpty()) {
      moveInputAfterNextToken();
      preventDefault = true;
    }
    return preventDefault;
  }
  function handleUpArrowKey() {
    setSelectedSuggestionIndex((index) => {
      return (index === 0 ? getMatchingSuggestions(incompleteTokenValue, suggestions, value, maxSuggestions, saveTransform).length : index) - 1;
    });
    setSelectedSuggestionScroll(true);
    return true;
  }
  function handleDownArrowKey() {
    setSelectedSuggestionIndex((index) => {
      return (index + 1) % getMatchingSuggestions(incompleteTokenValue, suggestions, value, maxSuggestions, saveTransform).length;
    });
    setSelectedSuggestionScroll(true);
    return true;
  }
  function collapseSuggestionsList(event) {
    if (event.target instanceof HTMLInputElement) {
      setIncompleteTokenValue(event.target.value);
      setIsExpanded(false);
      setSelectedSuggestionIndex(-1);
      setSelectedSuggestionScroll(false);
    }
  }
  function handleEscapeKey(event) {
    collapseSuggestionsList(event);
    return true;
  }
  function handleTabKey(event) {
    collapseSuggestionsList(event);
    return false;
  }
  function handleCommaKey() {
    if (inputHasValidValue()) {
      addNewToken(incompleteTokenValue);
    }
    return true;
  }
  function moveInputToIndex(index) {
    setInputOffsetFromEnd(value.length - Math.max(index, -1) - 1);
  }
  function moveInputBeforePreviousToken() {
    setInputOffsetFromEnd((prevInputOffsetFromEnd) => {
      return Math.min(prevInputOffsetFromEnd + 1, value.length);
    });
  }
  function moveInputAfterNextToken() {
    setInputOffsetFromEnd((prevInputOffsetFromEnd) => {
      return Math.max(prevInputOffsetFromEnd - 1, 0);
    });
  }
  function deleteTokenBeforeInput() {
    const index = getIndexOfInput() - 1;
    if (index > -1) {
      deleteToken(value[index]);
    }
  }
  function deleteTokenAfterInput() {
    const index = getIndexOfInput();
    if (index < value.length) {
      deleteToken(value[index]);
      moveInputToIndex(index);
    }
  }
  function addCurrentToken() {
    let preventDefault = false;
    const selectedSuggestion = getSelectedSuggestion();
    if (selectedSuggestion) {
      addNewToken(selectedSuggestion);
      preventDefault = true;
    } else if (inputHasValidValue()) {
      addNewToken(incompleteTokenValue);
      preventDefault = true;
    }
    return preventDefault;
  }
  function addNewTokens(tokens) {
    const tokensToAdd = [...new Set(tokens.map(saveTransform).filter(Boolean).filter((token) => !valueContainsToken(token)))];
    if (tokensToAdd.length > 0) {
      const newValue = [...value];
      newValue.splice(getIndexOfInput(), 0, ...tokensToAdd);
      onChange(newValue);
    }
  }
  function addNewToken(token) {
    if (!__experimentalValidateInput(token)) {
      speak(messages.__experimentalInvalid, "assertive");
      return;
    }
    addNewTokens([token]);
    speak(messages.added, "assertive");
    setIncompleteTokenValue("");
    setSelectedSuggestionIndex(-1);
    setSelectedSuggestionScroll(false);
    setIsExpanded(!__experimentalExpandOnFocus);
    if (isActive && !tokenizeOnBlur) {
      focus();
    }
  }
  function deleteToken(token) {
    const newTokens = value.filter((item) => {
      return getTokenValue(item) !== getTokenValue(token);
    });
    onChange(newTokens);
    speak(messages.removed, "assertive");
  }
  function getTokenValue(token) {
    if ("object" === typeof token) {
      return token.value;
    }
    return token;
  }
  function getMatchingSuggestions(searchValue = incompleteTokenValue, _suggestions = suggestions, _value = value, _maxSuggestions = maxSuggestions, _saveTransform = saveTransform) {
    let match = _saveTransform(searchValue);
    const startsWithMatch = [];
    const containsMatch = [];
    const normalizedValue = _value.map((item) => {
      if (typeof item === "string") {
        return item;
      }
      return item.value;
    });
    if (match.length === 0) {
      _suggestions = _suggestions.filter((suggestion) => !normalizedValue.includes(suggestion));
    } else {
      match = match.normalize("NFKC").toLocaleLowerCase();
      _suggestions.forEach((suggestion) => {
        const index = suggestion.normalize("NFKC").toLocaleLowerCase().indexOf(match);
        if (normalizedValue.indexOf(suggestion) === -1) {
          if (index === 0) {
            startsWithMatch.push(suggestion);
          } else if (index > 0) {
            containsMatch.push(suggestion);
          }
        }
      });
      _suggestions = startsWithMatch.concat(containsMatch);
    }
    return _suggestions.slice(0, _maxSuggestions);
  }
  function getSelectedSuggestion() {
    if (selectedSuggestionIndex !== -1) {
      return getMatchingSuggestions()[selectedSuggestionIndex];
    }
    return void 0;
  }
  function valueContainsToken(token) {
    return value.some((item) => {
      return getTokenValue(token) === getTokenValue(item);
    });
  }
  function getIndexOfInput() {
    return value.length - inputOffsetFromEnd;
  }
  function isInputEmpty() {
    return incompleteTokenValue.length === 0;
  }
  function inputHasValidValue() {
    return saveTransform(incompleteTokenValue).length > 0;
  }
  function updateSuggestions(resetSelectedSuggestion = true) {
    const inputHasMinimumChars = incompleteTokenValue.trim().length > 1;
    const matchingSuggestions2 = getMatchingSuggestions(incompleteTokenValue);
    const hasMatchingSuggestions = matchingSuggestions2.length > 0;
    const shouldExpandIfFocuses = hasFocus() && __experimentalExpandOnFocus;
    setIsExpanded(shouldExpandIfFocuses || inputHasMinimumChars && hasMatchingSuggestions);
    if (resetSelectedSuggestion) {
      if (__experimentalAutoSelectFirstMatch && inputHasMinimumChars && hasMatchingSuggestions) {
        setSelectedSuggestionIndex(0);
        setSelectedSuggestionScroll(true);
      } else {
        setSelectedSuggestionIndex(-1);
        setSelectedSuggestionScroll(false);
      }
    }
    if (inputHasMinimumChars) {
      const message = hasMatchingSuggestions ? sprintf(
        /* translators: %d: number of results. */
        _n("%d result found, use up and down arrow keys to navigate.", "%d results found, use up and down arrow keys to navigate.", matchingSuggestions2.length),
        matchingSuggestions2.length
      ) : __("No results.");
      debouncedSpeak(message, "assertive");
    }
  }
  function renderTokensAndInput() {
    const components = value.map(renderToken);
    components.splice(getIndexOfInput(), 0, renderInput());
    return components;
  }
  function renderToken(token, index, tokens) {
    const _value = getTokenValue(token);
    const status = typeof token !== "string" ? token.status : void 0;
    const termPosition = index + 1;
    const termsCount = tokens.length;
    return /* @__PURE__ */ _jsx(FlexItem, {
      children: /* @__PURE__ */ _jsx(Token, {
        value: _value,
        status,
        title: typeof token !== "string" ? token.title : void 0,
        displayTransform,
        onClickRemove: onTokenClickRemove,
        isBorderless: typeof token !== "string" && token.isBorderless || isBorderless,
        onMouseEnter: typeof token !== "string" ? token.onMouseEnter : void 0,
        onMouseLeave: typeof token !== "string" ? token.onMouseLeave : void 0,
        disabled: "error" !== status && disabled,
        messages,
        termsCount,
        termPosition
      })
    }, "token-" + _value);
  }
  function renderInput() {
    const inputProps = {
      instanceId,
      autoCapitalize,
      autoComplete,
      placeholder: value.length === 0 ? placeholder : "",
      disabled,
      value: incompleteTokenValue,
      onBlur,
      isExpanded,
      selectedSuggestionIndex
    };
    return /* @__PURE__ */ _jsx(TokenInput, {
      ...inputProps,
      onChange: !(maxLength && value.length >= maxLength) ? onInputChangeHandler : void 0,
      ref: input
    }, "input");
  }
  const classes = clsx(className, "components-form-token-field__input-container", {
    "is-active": isActive,
    "is-disabled": disabled
  });
  let tokenFieldProps = {
    className: "components-form-token-field",
    tabIndex: -1
  };
  const matchingSuggestions = getMatchingSuggestions();
  if (!disabled) {
    tokenFieldProps = Object.assign({}, tokenFieldProps, {
      onKeyDown: withIgnoreIMEEvents(onKeyDown),
      onKeyPress,
      onFocus: onFocusHandler
    });
  }
  return /* @__PURE__ */ _jsxs("div", {
    ...tokenFieldProps,
    children: [label && /* @__PURE__ */ _jsx(StyledLabel, {
      htmlFor: `components-form-token-input-${instanceId}`,
      className: "components-form-token-field__label",
      children: label
    }), /* @__PURE__ */ _jsxs("div", {
      ref: tokensAndInput,
      className: classes,
      tabIndex: -1,
      onMouseDown: onContainerTouched,
      onTouchStart: onContainerTouched,
      children: [/* @__PURE__ */ _jsx(TokensAndInputWrapperFlex, {
        justify: "flex-start",
        align: "center",
        gap: 1,
        wrap: true,
        __next40pxDefaultSize,
        hasTokens: !!value.length,
        children: renderTokensAndInput()
      }), isExpanded && /* @__PURE__ */ _jsx(SuggestionsList, {
        instanceId,
        match: saveTransform(incompleteTokenValue),
        displayTransform,
        suggestions: matchingSuggestions,
        selectedIndex: selectedSuggestionIndex,
        scrollIntoView: selectedSuggestionScroll,
        onHover: onSuggestionHovered,
        onSelect: onSuggestionSelected,
        __experimentalRenderItem
      })]
    }), __experimentalShowHowTo && /* @__PURE__ */ _jsx(StyledHelp, {
      id: `components-form-token-suggestions-howto-${instanceId}`,
      className: "components-form-token-field__help",
      children: tokenizeOnSpace ? __("Separate with commas, spaces, or the Enter key.") : __("Separate with commas or the Enter key.")
    })]
  });
}
var form_token_field_default = FormTokenField;
export {
  FormTokenField,
  form_token_field_default as default
};
//# sourceMappingURL=index.js.map
