import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
import { cx } from '@emotion/css';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useCombobox } from 'downshift';
import { debounce } from 'lodash';
import { useState, useCallback, useMemo, useId } from 'react';
import '@grafana/data';
import { useStyles2 } from '../../themes/ThemeContext.js';
import 'micro-memoize';
import '@emotion/react';
import 'tinycolor2';
import '../../utils/skeleton.js';
import '../../utils/dom.js';
import '../../utils/colors.js';
import 'slate';
import '../../utils/logger.js';
import { logOptions } from '../../utils/logOptions.js';
import { t, Trans } from '../../utils/i18n.js';
import { Icon } from '../Icon/Icon.js';
import { AutoSizeInput } from '../Input/AutoSizeInput.js';
import { Input } from '../Input/Input.js';
import { Box } from '../Layout/Box/Box.js';
import { Stack } from '../Layout/Stack/Stack.js';
import { Portal } from '../Portal/Portal.js';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer.js';
import { getComboboxStyles, MENU_OPTION_HEIGHT } from './getComboboxStyles.js';
import { useComboboxFloat } from './useComboboxFloat.js';
import { useLatestAsyncCall, StaleResultError } from './useLatestAsyncCall.js';

const RECOMMENDED_ITEMS_AMOUNT = 1e5;
function itemToString(item) {
  var _a, _b;
  if (!item) {
    return "";
  }
  if ((_a = item.label) == null ? void 0 : _a.includes("Custom value: ")) {
    return item.value.toString();
  }
  return (_b = item.label) != null ? _b : item.value.toString();
}
function itemFilter(inputValue) {
  const lowerCasedInputValue = inputValue.toLowerCase();
  return (item) => {
    var _a, _b;
    return !inputValue || ((_a = item.label) == null ? void 0 : _a.toLowerCase().includes(lowerCasedInputValue)) || ((_b = item.value) == null ? void 0 : _b.toString().toLowerCase().includes(lowerCasedInputValue));
  };
}
const noop = () => {
};
const asyncNoop = () => Promise.resolve([]);
const Combobox = (props) => {
  const {
    options,
    onChange,
    value: valueProp,
    placeholder: placeholderProp,
    isClearable = false,
    createCustomValue = false,
    id,
    width,
    minWidth,
    maxWidth,
    "aria-labelledby": ariaLabelledBy,
    autoFocus,
    onBlur,
    disabled,
    loading,
    invalid
  } = props;
  const value = typeof valueProp === "object" ? valueProp == null ? void 0 : valueProp.value : valueProp;
  const isAsync = typeof options === "function";
  const loadOptions = useLatestAsyncCall(isAsync ? options : asyncNoop);
  const [asyncLoading, setAsyncLoading] = useState(false);
  const [asyncError, setAsyncError] = useState(false);
  const [items, baseSetItems] = useState(isAsync ? [] : options);
  const setItems = useCallback(
    (items2, inputValue) => {
      let itemsToSet = items2;
      logOptions(itemsToSet.length, RECOMMENDED_ITEMS_AMOUNT, id, ariaLabelledBy);
      if (inputValue && createCustomValue) {
        const optionMatchingInput = items2.find(
          (opt) => opt.label === "Custom value: " + inputValue || opt.value === inputValue
        );
        if (!optionMatchingInput) {
          const customValueOption = {
            label: t("combobox.custom-value.label", "Custom value: ") + inputValue,
            // Type casting needed to make this work when T is a number
            value: inputValue
            /* TODO: Add this back when we do support descriptions and have need for it
            description: t('combobox.custom-value.create', 'Create custom value'),
            */
          };
          itemsToSet = items2.slice(0);
          itemsToSet.unshift(customValueOption);
        }
      }
      baseSetItems(itemsToSet);
    },
    [createCustomValue, id, ariaLabelledBy]
  );
  const selectedItemIndex = useMemo(() => {
    if (isAsync) {
      return null;
    }
    if (valueProp === void 0 || valueProp === null) {
      return null;
    }
    const index = options.findIndex((option) => option.value === value);
    if (index === -1) {
      return null;
    }
    return index;
  }, [valueProp, options, value, isAsync]);
  const selectedItem = useMemo(() => {
    if (valueProp === void 0 || valueProp === null) {
      return null;
    }
    if (selectedItemIndex !== null && !isAsync) {
      return options[selectedItemIndex];
    }
    return typeof valueProp === "object" ? valueProp : { value: valueProp, label: valueProp.toString() };
  }, [selectedItemIndex, isAsync, valueProp, options]);
  const menuId = `downshift-${useId().replace(/:/g, "--")}-menu`;
  const labelId = `downshift-${useId().replace(/:/g, "--")}-label`;
  const styles = useStyles2(getComboboxStyles);
  const virtualizerOptions = {
    count: items.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => MENU_OPTION_HEIGHT,
    overscan: 4
  };
  const rowVirtualizer = useVirtualizer(virtualizerOptions);
  const debounceAsync = useMemo(
    () => debounce((inputValue) => {
      loadOptions(inputValue).then((opts) => {
        setItems(opts, inputValue);
        setAsyncLoading(false);
        setAsyncError(false);
      }).catch((err) => {
        if (!(err instanceof StaleResultError)) {
          setAsyncError(true);
          setAsyncLoading(false);
        }
      });
    }, 200),
    [loadOptions, setItems]
  );
  const {
    isOpen,
    highlightedIndex,
    getInputProps,
    getMenuProps,
    getItemProps,
    selectItem
  } = useCombobox({
    menuId,
    labelId,
    inputId: id,
    items,
    itemToString,
    selectedItem,
    // Don't change downshift state in the onBlahChange handlers. Instead, use the stateReducer to make changes.
    // Downshift calls change handlers on the render after so you can get sync/flickering issues if you change its state
    // in them.
    // Instead, stateReducer is called in the same tick as state changes, before that state is committed and rendered.
    onSelectedItemChange: ({ selectedItem: selectedItem2 }) => {
      onChange(selectedItem2);
    },
    defaultHighlightedIndex: selectedItemIndex != null ? selectedItemIndex : 0,
    scrollIntoView: () => {
    },
    onInputValueChange: ({ inputValue, isOpen: isOpen2 }) => {
      if (!isOpen2) {
        if (isAsync) {
          setItems([], "");
        }
        return;
      }
      if (!isAsync) {
        const filteredItems = options.filter(itemFilter(inputValue));
        setItems(filteredItems, inputValue);
      } else {
        if (inputValue && createCustomValue) {
          setItems([], inputValue);
        }
        setAsyncLoading(true);
        debounceAsync(inputValue);
      }
    },
    onIsOpenChange: ({ isOpen: isOpen2, inputValue }) => {
      if (isAsync && isOpen2 && inputValue === "") {
        setAsyncLoading(true);
        loadOptions(inputValue).then((opts) => {
          setItems(opts, inputValue);
          setAsyncLoading(false);
          setAsyncError(false);
        }).catch((err) => {
          if (!(err instanceof StaleResultError)) {
            setAsyncError(true);
            setAsyncLoading(false);
          }
        });
      }
    },
    onHighlightedIndexChange: ({ highlightedIndex: highlightedIndex2, type }) => {
      if (type !== useCombobox.stateChangeTypes.MenuMouseLeave) {
        rowVirtualizer.scrollToIndex(highlightedIndex2);
      }
    },
    stateReducer(state, actionAndChanges) {
      let { changes } = actionAndChanges;
      const menuBeingOpened = state.isOpen === false && changes.isOpen === true;
      const menuBeingClosed = state.isOpen === true && changes.isOpen === false;
      if (menuBeingOpened && changes.inputValue === state.inputValue) {
        changes = {
          ...changes,
          inputValue: ""
        };
      }
      if (menuBeingClosed) {
        if (changes.selectedItem) {
          changes = {
            ...changes,
            inputValue: itemToString(changes.selectedItem)
          };
        } else if (changes.inputValue !== "") {
          changes = {
            ...changes,
            inputValue: ""
          };
        }
      }
      return changes;
    }
  });
  const { inputRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, rowVirtualizer.range, isOpen);
  const isAutoSize = width === "auto";
  const InputComponent = isAutoSize ? AutoSizeInput : Input;
  const suffixIcon = asyncLoading ? "spinner" : (
    // If it's loading, show loading icon. Otherwise, icon indicating menu state
    isOpen ? "search" : "angle-down"
  );
  const placeholder = (isOpen ? itemToString(selectedItem) : null) || placeholderProp;
  return /* @__PURE__ */ jsxs("div", { children: [
    /* @__PURE__ */ jsx(
      InputComponent,
      {
        width: isAutoSize ? void 0 : width,
        ...isAutoSize ? { minWidth, maxWidth } : {},
        autoFocus,
        onBlur,
        disabled,
        loading,
        invalid,
        className: styles.input,
        suffix: /* @__PURE__ */ jsxs(Fragment, { children: [
          !!value && value === (selectedItem == null ? void 0 : selectedItem.value) && isClearable && /* @__PURE__ */ jsx(
            Icon,
            {
              name: "times",
              className: styles.clear,
              title: t("combobox.clear.title", "Clear value"),
              tabIndex: 0,
              role: "button",
              onClick: () => {
                selectItem(null);
              },
              onKeyDown: (e) => {
                if (e.key === "Enter" || e.key === " ") {
                  selectItem(null);
                }
              }
            }
          ),
          /* @__PURE__ */ jsx(Icon, { name: suffixIcon })
        ] }),
        ...getInputProps({
          ref: inputRef,
          /*  Empty onCall to avoid TS error
           *  See issue here: https://github.com/downshift-js/downshift/issues/718
           *  Downshift repo: https://github.com/downshift-js/downshift/tree/master
           */
          onChange: noop,
          "aria-labelledby": ariaLabelledBy,
          // Label should be handled with the Field component
          placeholder
        })
      }
    ),
    /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
      "div",
      {
        className: cx(styles.menu, !isOpen && styles.menuClosed),
        style: {
          ...floatStyles
        },
        ...getMenuProps({
          ref: floatingRef,
          "aria-labelledby": ariaLabelledBy
        }),
        children: isOpen && /* @__PURE__ */ jsxs(ScrollContainer, { showScrollIndicators: true, maxHeight: "inherit", ref: scrollRef, children: [
          !asyncError && /* @__PURE__ */ jsx("ul", { style: { height: rowVirtualizer.getTotalSize() }, className: styles.menuUlContainer, children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
            var _a;
            return /* @__PURE__ */ jsx(
              "li",
              {
                "data-index": virtualRow.index,
                className: cx(
                  styles.option,
                  selectedItem && items[virtualRow.index].value === selectedItem.value && styles.optionSelected,
                  highlightedIndex === virtualRow.index && styles.optionFocused
                ),
                style: {
                  height: virtualRow.size,
                  transform: `translateY(${virtualRow.start}px)`
                },
                ...getItemProps({
                  item: items[virtualRow.index],
                  index: virtualRow.index
                }),
                children: /* @__PURE__ */ jsxs("div", { className: styles.optionBody, children: [
                  /* @__PURE__ */ jsx("span", { className: styles.optionLabel, children: (_a = items[virtualRow.index].label) != null ? _a : items[virtualRow.index].value }),
                  items[virtualRow.index].description && /* @__PURE__ */ jsx("span", { className: styles.optionDescription, children: items[virtualRow.index].description })
                ] })
              },
              `${items[virtualRow.index].value}-${virtualRow.index}`
            );
          }) }),
          /* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
            asyncError && /* @__PURE__ */ jsxs(MessageRow, { children: [
              /* @__PURE__ */ jsx(Icon, { name: "exclamation-triangle", size: "md", className: styles.warningIcon }),
              /* @__PURE__ */ jsx(Trans, { i18nKey: "combobox.async.error", children: "An error occurred while loading options." })
            ] }),
            items.length === 0 && !asyncError && /* @__PURE__ */ jsx(MessageRow, { children: /* @__PURE__ */ jsx(Trans, { i18nKey: "combobox.options.no-found", children: "No options found." }) })
          ] })
        ] })
      }
    ) })
  ] });
};
const MessageRow = ({ children }) => {
  return /* @__PURE__ */ jsx(Box, { padding: 2, color: "secondary", children: /* @__PURE__ */ jsx(Stack, { justifyContent: "center", alignItems: "center", children }) });
};

export { Combobox };
//# sourceMappingURL=Combobox.js.map
