"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// packages/components/src/autocomplete/index.tsx
var autocomplete_exports = {};
__export(autocomplete_exports, {
  default: () => Autocomplete,
  useAutocomplete: () => useAutocomplete,
  useAutocompleteProps: () => useAutocompleteProps,
  useLastDifferentValue: () => useLastDifferentValue
});
module.exports = __toCommonJS(autocomplete_exports);
var import_element = require("@wordpress/element");
var import_compose = require("@wordpress/compose");
var import_rich_text = require("@wordpress/rich-text");
var import_a11y = require("@wordpress/a11y");
var import_keycodes = require("@wordpress/keycodes");
var import_autocompleter_ui = require("./autocompleter-ui.cjs");
var import_get_autocomplete_match = require("./get-autocomplete-match.cjs");
var import_with_ignore_ime_events = require("../utils/with-ignore-ime-events.cjs");
var import_get_node_text = __toESM(require("../utils/get-node-text.cjs"));
var import_lock_unlock = require("../lock-unlock.cjs");
var import_jsx_runtime = require("react/jsx-runtime");
var {
  subscribeDelegatedListener
} = (0, import_lock_unlock.unlock)(import_compose.privateApis);
var EMPTY_FILTERED_OPTIONS = [];
var AUTOCOMPLETE_HOOK_REFERENCE = {};
function getCompletionObject(completion) {
  if (completion !== null && typeof completion === "object" && "action" in completion && completion.action !== void 0 && "value" in completion && completion.value !== void 0) {
    return completion;
  }
  return {
    action: "insert-at-caret",
    value: completion
  };
}
var initialState = {
  selectedIndex: 0,
  filteredOptions: EMPTY_FILTERED_OPTIONS,
  filterValue: "",
  autocompleter: null
};
function autocompleteReducer(state, action) {
  switch (action.type) {
    case "RESET":
      return initialState;
    case "SELECT":
      return {
        ...state,
        selectedIndex: action.index
      };
    case "OPTIONS":
      return {
        ...state,
        filteredOptions: action.options,
        selectedIndex: action.options.length === state.filteredOptions.length ? state.selectedIndex : 0
      };
    case "MATCH":
      return {
        ...state,
        autocompleter: action.completer,
        filterValue: action.query
      };
  }
}
function useAutocomplete({
  record,
  onChange,
  onReplace,
  completers,
  contentRef
}) {
  const instanceId = (0, import_compose.useInstanceId)(AUTOCOMPLETE_HOOK_REFERENCE);
  const [state, dispatch] = (0, import_element.useReducer)(autocompleteReducer, initialState);
  const {
    selectedIndex,
    filteredOptions,
    filterValue,
    autocompleter
  } = state;
  const backspacingRef = (0, import_element.useRef)(false);
  const prevRecordTextRef = (0, import_element.useRef)("");
  const lastCompletionRef = (0, import_element.useRef)(null);
  function insertCompletion(replacement) {
    if (autocompleter === null) {
      return "";
    }
    const end = record.start;
    const start = end - autocompleter.triggerPrefix.length - filterValue.length;
    const toInsert = (0, import_rich_text.create)({
      html: (0, import_element.renderToString)(replacement)
    });
    onChange((0, import_rich_text.insert)(record, toInsert, start, end));
    return (0, import_rich_text.getTextContent)(toInsert);
  }
  function select(option) {
    if (option.isDisabled || !autocompleter) {
      return;
    }
    const {
      getOptionCompletion
    } = autocompleter;
    if (!getOptionCompletion) {
      dispatch({
        type: "RESET"
      });
      contentRef.current?.focus();
      return;
    }
    const completionObject = getCompletionObject(getOptionCompletion(option.value, filterValue));
    if ("replace" === completionObject.action) {
      onReplace([completionObject.value]);
      return;
    }
    if ("insert-at-caret" === completionObject.action) {
      const completionText = insertCompletion(completionObject.value);
      if (completionText.startsWith(autocompleter.triggerPrefix)) {
        const afterPrefix = completionText.slice(autocompleter.triggerPrefix.length);
        if (afterPrefix) {
          lastCompletionRef.current = {
            name: autocompleter.name,
            value: afterPrefix
          };
        }
      }
    }
    dispatch({
      type: "RESET"
    });
    contentRef.current?.focus();
  }
  function onChangeOptions(options) {
    dispatch({
      type: "OPTIONS",
      options
    });
  }
  function handleKeyDown(event) {
    backspacingRef.current = event.key === "Backspace";
    if (!autocompleter) {
      return;
    }
    if (filteredOptions.length === 0) {
      return;
    }
    if (event.defaultPrevented) {
      return;
    }
    switch (event.key) {
      case "ArrowUp":
      case "ArrowDown": {
        const offset = event.key === "ArrowUp" ? -1 : 1;
        const newIndex = (selectedIndex + offset + filteredOptions.length) % filteredOptions.length;
        dispatch({
          type: "SELECT",
          index: newIndex
        });
        if ((0, import_keycodes.isAppleOS)()) {
          (0, import_a11y.speak)((0, import_get_node_text.default)(filteredOptions[newIndex].label), "assertive");
        }
        break;
      }
      case "Escape":
        dispatch({
          type: "RESET"
        });
        event.preventDefault();
        break;
      case "Enter":
        select(filteredOptions[selectedIndex]);
        break;
      case "ArrowLeft":
      case "ArrowRight":
        dispatch({
          type: "RESET"
        });
        return;
      default:
        return;
    }
    event.preventDefault();
  }
  const textContent = (0, import_element.useMemo)(() => {
    if ((0, import_rich_text.isCollapsed)(record)) {
      return (0, import_rich_text.getTextContent)((0, import_rich_text.slice)(record, 0));
    }
    return "";
  }, [record]);
  (0, import_element.useEffect)(() => {
    const isTextChange = record.text !== prevRecordTextRef.current;
    prevRecordTextRef.current = record.text;
    function getTextAfterSelection() {
      return textContent ? (0, import_rich_text.getTextContent)((0, import_rich_text.slice)(record, void 0, (0, import_rich_text.getTextContent)(record).length)) : "";
    }
    const match = (0, import_get_autocomplete_match.getAutocompleteMatch)(textContent, completers, {
      matchCount: filteredOptions.length,
      isBackspacing: backspacingRef.current,
      getTextAfterSelection,
      lastCompletion: lastCompletionRef.current
    });
    if (!match) {
      if (autocompleter) {
        dispatch({
          type: "RESET"
        });
      }
      return;
    }
    const {
      completer,
      filterValue: query
    } = match;
    if (!autocompleter && !isTextChange) {
      return;
    }
    if (lastCompletionRef.current && lastCompletionRef.current.name === completer.name) {
      lastCompletionRef.current = null;
    }
    dispatch({
      type: "MATCH",
      completer,
      query
    });
  }, [textContent]);
  const {
    key: selectedKey = ""
  } = filteredOptions[selectedIndex] || {};
  const {
    className
  } = autocompleter || {};
  const isExpanded = !!autocompleter && filteredOptions.length > 0;
  const listBoxId = isExpanded ? `components-autocomplete-listbox-${instanceId}` : void 0;
  const activeId = isExpanded ? `components-autocomplete-item-${instanceId}-${selectedKey}` : null;
  const hasSelection = record.start !== void 0;
  const showPopover = !!textContent && hasSelection && !!autocompleter;
  return {
    listBoxId,
    activeId,
    onKeyDown: (0, import_with_ignore_ime_events.withIgnoreIMEEvents)(handleKeyDown),
    popover: showPopover && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_autocompleter_ui.AutocompleterUI, {
      autocompleter,
      className,
      filterValue,
      instanceId,
      listBoxId,
      selectedIndex,
      onChangeOptions,
      onSelect: select,
      contentRef,
      reset: () => dispatch({
        type: "RESET"
      })
    }, autocompleter.name + autocompleter.triggerPrefix)
  };
}
function recordValuesMatch(a, b) {
  return a.text === b.text && a.start === b.start && a.end === b.end;
}
function useLastDifferentValue(value) {
  const history = (0, import_element.useRef)([]);
  const lastEntry = history.current[history.current.length - 1];
  if (!lastEntry || !recordValuesMatch(value, lastEntry)) {
    history.current.push(value);
  }
  if (history.current.length > 2) {
    history.current.shift();
  }
  return history.current[0];
}
function useAutocompleteProps(options) {
  const ref = (0, import_element.useRef)(null);
  const onKeyDownRef = (0, import_element.useRef)(void 0);
  const {
    record
  } = options;
  const previousRecord = useLastDifferentValue(record);
  const {
    popover,
    listBoxId,
    activeId,
    onKeyDown
  } = useAutocomplete({
    ...options,
    contentRef: ref
  });
  onKeyDownRef.current = onKeyDown;
  const mergedRefs = (0, import_compose.useMergeRefs)([ref, (0, import_compose.useRefEffect)((element) => {
    function _onKeyDown(event) {
      onKeyDownRef.current?.(event);
    }
    return subscribeDelegatedListener(element, "keydown", _onKeyDown, true);
  }, [])]);
  const didUserInput = record.text !== previousRecord?.text;
  if (!didUserInput) {
    return {
      ref: mergedRefs
    };
  }
  return {
    ref: mergedRefs,
    children: popover,
    "aria-autocomplete": listBoxId ? "list" : void 0,
    "aria-owns": listBoxId,
    "aria-activedescendant": activeId
  };
}
function Autocomplete({
  children,
  isSelected,
  ...options
}) {
  const {
    popover,
    ...props
  } = useAutocomplete(options);
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, {
    children: [children(props), isSelected && popover]
  });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  useAutocomplete,
  useAutocompleteProps,
  useLastDifferentValue
});
//# sourceMappingURL=index.cjs.map
