"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.createCommand = createCommand;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _useTheme = require("../theme/use-theme.js");
var _useControllableState = require("../hooks/useControllableState.js");
var _index = require("../typography/index.js");
var _index2 = require("../item/index.js");
var _index3 = require("../kbd/index.js");
var _index4 = require("../search-input/index.js");
var _tokens = require("../styles/tokens.js");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function defaultFilter(item, query) {
  const q = query.trim().toLowerCase();
  if (!q) return true;
  if (item.label.toLowerCase().includes(q)) return true;
  if (item.description?.toLowerCase().includes(q)) return true;
  return (item.keywords ?? []).some(k => k.toLowerCase().includes(q));
}
/**
 * Build the `Command` palette bound to a platform `CenteredDialog`. The
 * platform entry files (`index.ts` / `index.web.ts`) inject the correct one
 * (native RN `Modal` vs pure-DOM web overlay) — this repo never relies on
 * implicit `.web` resolution. The body is single-source.
 *
 * `Command` is a ⌘K command palette built on Bloom's `CenteredDialog`,
 * `SearchInput`, an `Item` results list and `Kbd` shortcut hints. Items group
 * by their `group` field (ungrouped first). On web the list is
 * keyboard-navigable (Up/Down/Enter); on native, tap to select.
 */
function createCommand(CenteredDialog) {
  const Command = function Command({
    visible,
    onClose,
    items,
    placeholder = 'Type a command or search…',
    emptyText = 'No results found.',
    query: queryProp,
    onQueryChange,
    filter = defaultFilter,
    maxListHeight = 360,
    style,
    testID
  }) {
    const theme = (0, _useTheme.useTheme)();
    const searchRef = (0, _react.useRef)(null);
    const [activeIndex, setActiveIndex] = (0, _react.useState)(0);
    const [query, setQuery] = (0, _useControllableState.useControllableState)({
      value: queryProp,
      defaultValue: '',
      onChange: onQueryChange
    });
    const filtered = (0, _react.useMemo)(() => items.filter(it => filter(it, query)), [items, filter, query]);

    // Build a flattened render list with group headers, and a parallel list of
    // just the selectable items for keyboard navigation.
    const {
      entries,
      selectable
    } = (0, _react.useMemo)(() => {
      const groups = new Map();
      const ungrouped = [];
      for (const it of filtered) {
        if (it.group) {
          const arr = groups.get(it.group) ?? [];
          arr.push(it);
          groups.set(it.group, arr);
        } else {
          ungrouped.push(it);
        }
      }
      const flat = [];
      const sel = [];
      const pushItem = it => {
        flat.push({
          type: 'item',
          item: it,
          selectableIndex: sel.length
        });
        sel.push(it);
      };
      for (const it of ungrouped) pushItem(it);
      for (const [group, arr] of groups) {
        flat.push({
          type: 'header',
          group
        });
        for (const it of arr) pushItem(it);
      }
      return {
        entries: flat,
        selectable: sel
      };
    }, [filtered]);

    // Keep the active index in range as the filtered set changes. Reset to the
    // first selectable item whenever the query changes (derive, no effect).
    const prevQueryRef = (0, _react.useRef)(query);
    if (prevQueryRef.current !== query) {
      prevQueryRef.current = query;
      if (activeIndex !== 0) setActiveIndex(0);
    }

    // Reset the active index and focus the search field on the closed→open
    // transition, derived from the `visible` prop (no effect). Focusing happens
    // on the next frame so the dialog has mounted.
    const prevVisibleRef = (0, _react.useRef)(visible);
    if (prevVisibleRef.current !== visible) {
      prevVisibleRef.current = visible;
      if (visible) {
        if (activeIndex !== 0) setActiveIndex(0);
        if (_reactNative.Platform.OS === 'web') {
          requestAnimationFrame(() => searchRef.current?.focus());
        }
      }
    }
    const clampedActive = Math.min(activeIndex, Math.max(0, selectable.length - 1));
    const select = (0, _react.useCallback)(item => {
      if (!item || item.disabled) return;
      item.onSelect();
      setQuery('');
      onClose();
    }, [setQuery, onClose]);

    // Web keyboard navigation on the search input.
    const webKeyHandler = _reactNative.Platform.OS === 'web' ? {
      onKeyDown: e => {
        if (selectable.length === 0) return;
        switch (e.key) {
          case 'ArrowDown':
            e.preventDefault();
            setActiveIndex(i => (i + 1) % selectable.length);
            break;
          case 'ArrowUp':
            e.preventDefault();
            setActiveIndex(i => (i - 1 + selectable.length) % selectable.length);
            break;
          case 'Enter':
            e.preventDefault();
            select(selectable[clampedActive]);
            break;
          default:
            break;
        }
      }
    } : {};
    return /*#__PURE__*/(0, _jsxRuntime.jsx)(CenteredDialog, {
      visible: visible,
      onClose: onClose,
      dismissible: true,
      showClose: false,
      maxWidth: 560,
      accessibilityLabel: "Command palette",
      cardStyle: style,
      contentStyle: styles.content,
      testID: testID,
      children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
        ...webKeyHandler,
        children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
          style: [styles.searchWrap, {
            borderBottomColor: theme.colors.borderLight
          }],
          children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index4.SearchInput, {
            ref: searchRef,
            label: placeholder,
            placeholder: placeholder,
            value: query,
            onChangeText: setQuery,
            onClearText: () => setQuery(''),
            autoFocus: _reactNative.Platform.OS !== 'web'
          })
        }), selectable.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
          style: styles.empty,
          children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Text, {
            style: {
              color: theme.colors.textSecondary,
              fontSize: _tokens.fontSize.sm
            },
            children: emptyText
          })
        }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
          style: {
            maxHeight: maxListHeight
          },
          keyboardShouldPersistTaps: "handled",
          showsVerticalScrollIndicator: false,
          children: entries.map((entry, i) => {
            if (entry.type === 'header') {
              return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Text, {
                style: [styles.groupHeader, {
                  color: theme.colors.textTertiary
                }],
                children: entry.group
              }, `h-${entry.group}-${i}`);
            }
            const item = entry.item;
            if (!item) return null;
            const Icon = item.icon;
            const isActive = entry.selectableIndex === clampedActive;
            return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.Item, {
              title: item.label,
              subtitle: item.description,
              density: "compact",
              disabled: item.disabled,
              active: isActive,
              role: "option",
              onPress: () => select(item),
              leading: Icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)(Icon, {
                size: "sm",
                fill: theme.colors.textSecondary
              }) : null,
              trailing: item.shortcut ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_index3.Kbd, {
                size: "sm",
                children: item.shortcut
              }) : null
            }, item.id);
          })
        })]
      })
    });
  };
  Command.displayName = 'Command';
  return Command;
}
const styles = _reactNative.StyleSheet.create({
  content: {
    paddingHorizontal: 0,
    paddingTop: 0,
    paddingBottom: _tokens.space.xs
  },
  searchWrap: {
    paddingHorizontal: _tokens.space.md,
    paddingTop: _tokens.space.sm,
    paddingBottom: _tokens.space.sm,
    borderBottomWidth: _reactNative.StyleSheet.hairlineWidth
  },
  groupHeader: {
    fontSize: _tokens.fontSize.xs,
    fontWeight: '600',
    textTransform: 'uppercase',
    letterSpacing: 0.6,
    paddingHorizontal: _tokens.space.lg,
    paddingTop: _tokens.space.sm,
    paddingBottom: _tokens.space.xs
  },
  empty: {
    paddingVertical: _tokens.space._2xl,
    alignItems: 'center'
  }
});
//# sourceMappingURL=Command.js.map