import React, { Component } from 'react';
import InputWithOptions from '../InputWithOptions';
import { Search as SearchIcon, SearchSmall } from '@wix/wix-ui-icons-common';
import { StringUtils } from '../utils/StringUtils';
import { st, classes } from './Search.st.css.js';
import Input from '../Input/Input';
import { WixStyleReactDefaultsOverrideContext } from '../WixStyleReactDefaultsOverrideProvider';
import { IconThemeContext } from '../WixDesignSystemIconThemeProvider/IconThemeContext';
// because lodash debounce is not compatible with jest timeout mocks
function debounce(fn, wait) {
    let timeout;
    return function (...args) {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => fn.apply(context, args), wait);
    };
}
/**
 * Search component with suggestions based on input value listed in dropdown
 */
class Search extends Component {
    constructor(props) {
        super(props);
        this.searchInput = React.createRef();
        this.contentElementRef = React.createRef();
        this._onContentTransitionEnd = () => {
            this.props.onExpandTransitionEnd?.(this.state.collapsed);
        };
        this._onContentTransitionStart = () => {
            this.props.onExpandTransitionStart?.(this.state.collapsed);
        };
        /**
         * Creates an onChange debounced function
         */
        this._createDebouncedOnChange = () => {
            const { debounceMs, onChange } = this.props;
            return debounceMs > 0 ? debounce(onChange, debounceMs) : onChange;
        };
        this._getIsControlled = () => 'value' in this.props && 'onChange' in this.props;
        this._getFilteredOptions = () => {
            const { options, predicate } = this.props;
            const searchText = this._currentValue();
            if (!searchText || !searchText.length) {
                return options;
            }
            const filterFn = predicate || this._stringFilter;
            return options.filter(filterFn);
        };
        this._stringFilter = option => {
            const searchText = this._currentValue();
            return StringUtils.includesCaseInsensitive(option.value, searchText.trim());
        };
        this._onChange = e => {
            e.persist();
            this.setState({
                inputValue: e.target.value,
            }, () => {
                this._onChangeHandler(e);
            });
        };
        this._onClear = event => {
            const { expandable } = this.props;
            const { collapsed } = this.state;
            const stateChanges = {};
            if (!this._getIsControlled()) {
                stateChanges.inputValue = '';
            }
            if (expandable && !collapsed && this._currentValue === '') {
                stateChanges.collapsed = true;
                this.searchInput.current && this.searchInput.current.blur();
            }
            this.setState(stateChanges, () => {
                this._onClearHandler(event);
            });
        };
        this._onClearHandler = event => {
            const { onClear } = this.props;
            if (onClear)
                onClear(event);
        };
        this._currentValue = () => this.state.inputValue;
        this._onFocus = event => {
            const { onFocus } = this.props;
            if (this.state.collapsed && this.props.expandable) {
                this.setState({
                    collapsed: false,
                });
            }
            onFocus && onFocus(event);
        };
        this._onBlur = async (event) => {
            const { onBlur } = this.props;
            onBlur && (await onBlur(event));
            if (!this.state.collapsed && this.props.expandable) {
                const value = this._currentValue();
                if (value === '') {
                    this.setState({
                        collapsed: true,
                    });
                }
            }
        };
        this._onWrapperClick = () => {
            if (!this.props.expandable ||
                (this.props.expandable && this.state.collapsed)) {
                this.searchInput.current && this.searchInput.current.focus();
            }
        };
        this._onWrapperMouseDown = e => {
            // We need to capture mouse down and prevent it's event if the input
            // is already open
            if (this.props.expandable && !this.state.collapsed) {
                const value = this._currentValue();
                if (value === '') {
                    e.preventDefault();
                }
            }
        };
        /**
         * Sets focus on the input element
         */
        this.focus = () => {
            this.searchInput.current && this.searchInput.current.focus();
        };
        /**
         * Removes focus on the input element
         */
        this.blur = () => {
            this.searchInput.current && this.searchInput.current.blur();
        };
        /**
         * Clears the input.
         *
         * @param event
         */
        this.clear = event => {
            this.searchInput.current && this.searchInput.current.clear(event);
        };
        const initialValue = this._getIsControlled()
            ? props.value
            : props.defaultValue || '';
        this._onChangeHandler = this._createDebouncedOnChange();
        this.state = {
            inputValue: initialValue,
            collapsed: props.expandable && !initialValue && !props.autoFocus,
        };
    }
    _addContentElementListeners() {
        const contentElement = this.contentElementRef.current;
        if (!contentElement) {
            return;
        }
        contentElement.addEventListener('transitionstart', this._onContentTransitionStart);
        contentElement.addEventListener('transitionend', this._onContentTransitionEnd);
    }
    _removeContentElementListeners() {
        const contentElement = this.contentElementRef.current;
        if (!contentElement) {
            return;
        }
        contentElement.removeEventListener('transitionstart', this._onContentTransitionStart);
        contentElement.removeEventListener('transitionend', this._onContentTransitionEnd);
    }
    componentDidMount() {
        this._addContentElementListeners();
    }
    componentWillUnmount() {
        this._removeContentElementListeners();
    }
    componentDidUpdate(prevProps) {
        const { value, expandable } = this.props;
        if (prevProps.value !== value) {
            // Collapse the expandable input when the input has no focus & no value in it.
            const collapsed = expandable &&
                this.searchInput.current &&
                !this.searchInput.current._focused &&
                prevProps.value &&
                !value
                ? true
                : this.props.collapsed;
            this.setState({ inputValue: value, collapsed });
        }
        if (prevProps.debounceMs !== this.props.debounceMs ||
            prevProps.onChange !== this.props.onChange) {
            this._onChangeHandler = this._createDebouncedOnChange();
        }
    }
    render() {
        const { input: inputPropsDefaults } = this.context;
        const { defaultValue, dataHook, expandWidth, highlight, ...restProps } = this.props;
        const { expandable, size = inputPropsDefaults.size } = restProps;
        const { collapsed, inputValue } = this.state;
        const contentStyle = expandable && !collapsed ? { width: expandWidth } : undefined;
        return (React.createElement(IconThemeContext.Consumer, null, ({ icons = {} }) => {
            const SearchIcons = {
                Search: icons.Search?.Search || SearchIcon,
                SearchSmall: icons.Search?.SearchSmall || SearchSmall,
            };
            return (React.createElement("div", { "data-hook": dataHook, className: st(classes.root, {
                    expandable,
                    collapsed: expandable && collapsed,
                    expandDirection: restProps.expandDirection,
                    size,
                    border: restProps.border,
                }), onClick: this._onWrapperClick, onMouseDown: this._onWrapperMouseDown, "data-expandable": expandable || null, "data-collapsed": (expandable && collapsed) || null },
                React.createElement("div", { ref: this.contentElementRef, "data-hook": "search-content", className: classes.content, style: contentStyle },
                    React.createElement(InputWithOptions, { ...restProps, showDrawerOnMobile: false, value: inputValue, ref: this.searchInput, prefix: React.createElement(Input.IconAffix, null, size === 'small' ? (React.createElement(SearchIcons.SearchSmall, null)) : (React.createElement(SearchIcons.Search, null))), dataHook: "search-inputwithoptions", menuArrow: false, closeOnSelect: true, options: this._getFilteredOptions(), customDropdownContent: !this._getFilteredOptions().length &&
                            this.props.renderEmptyState?.(inputValue), onClear: restProps.clearButton ? this._onClear : undefined, onChange: this._onChange, onFocus: this._onFocus, onBlur: this._onBlur, highlight: highlight }))));
        }));
    }
}
Search.contextType = WixStyleReactDefaultsOverrideContext;
Search.displayName = 'Search';
Search.defaultProps = {
    ...InputWithOptions.defaultProps,
    clearButton: true,
    placeholder: 'Search',
    expandable: false,
    expandWidth: '100%',
    expandDirection: 'right',
    debounceMs: 0,
    onChange: () => { },
    highlight: true,
    border: 'round',
};
export default Search;
//# sourceMappingURL=Search.js.map