import React from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';
import {debounce} from 'lodash';
import lang from '../../lang/common/index-en_US';
import {filterKeyCodes} from '../utils/functions';

import {Button} from './Button';
import {TextInput} from './form/TextInput';
import {Icon} from './Icon';

const ELEMENT_SHOW_ALL = 'ELEMENT_SHOW_ALL';
const KEY_CODE_ENTER = 13;

export class Search extends React.Component<any> {
    static propTypes = {
        value: PropTypes.string,
        name: PropTypes.string,
        inputDebounce: PropTypes.number,
        renderResult: PropTypes.func.isRequired,
        searchProvider: PropTypes.func.isRequired,
        onChange: PropTypes.func.isRequired,
        onSearch: PropTypes.func.isRequired,
        resultAction: PropTypes.func.isRequired,
        maxResults: PropTypes.number,
        testAutomationId: PropTypes.string,
        textSearchPlaceholder: PropTypes.string,
        textNoResults: PropTypes.string,
        textShowAllResults: PropTypes.string,
        textSearchLabel: PropTypes.string,
        textClearLabel: PropTypes.string
    };

    static defaultProps = {
        value: null,
        name: null,
        inputDebounce: 500,
        maxResults: 3,
        get textSearchPlaceholder() {
            return lang.SEARCH;
        },
        get textShowAllResults() {
            return lang.FASTSEARCH_SHOW_ALL;
        },
        get textNoResults() {
            return lang.FASTSEARCH_NO_RESULTS;
        },
        get textSearchLabel() {
            return lang.SEARCH;
        },
        get textClearLabel() {
            return lang.CLEAR;
        },
        testAutomationId: 'search'
    };

    state = {
        focused: false,
        focusedItem: null,
        initial: true,
        results: []
    };

    componentDidUpdate(prevProps) {
        const {value, inputDebounce} = this.props;

        if (value !== prevProps.value) {
            this.debouncedSearch(value);
        }

        if (inputDebounce !== prevProps.inputDebounce) {
            this.debouncedSearch = debounce(this.search, inputDebounce);
        }
    }

    resultRefs: any = [];

    inputRef: any = React.createRef();

    showAllRef: any = React.createRef();

    blurTimer;

    getResults() {
        const {maxResults} = this.props;
        const {results} = this.state;

        if (!maxResults) {
            return results;
        }

        return results.slice(0, maxResults);
    }

    handleFocus = () => {
        const {value} = this.props;
        const {initial} = this.state;

        clearTimeout(this.blurTimer);

        this.setState({focused: true});

        if (value && initial) {
            this.search();
        }
    };

    handleBlur = () => {
        this.blurTimer = setTimeout(() => {
            this.setState({focused: false, focusedItem: null});
        }, 0);
    };

    handleKeyboardNavigation = e => {
        if (this.resultRefs.length && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
            const {focusedItem} = this.state;

            let nextFocusedItem;

            e.stopPropagation();
            e.preventDefault();

            switch (e.key) {
                case 'ArrowDown': {
                    if (focusedItem === null || focusedItem === ELEMENT_SHOW_ALL) {
                        nextFocusedItem = 0;
                    } else if (focusedItem === this.resultRefs.length - 1) {
                        nextFocusedItem = ELEMENT_SHOW_ALL;
                    } else {
                        nextFocusedItem = focusedItem + 1;
                    }

                    break;
                }

                case 'ArrowUp': {
                    if (focusedItem === null || focusedItem === 0) {
                        nextFocusedItem = ELEMENT_SHOW_ALL;
                    } else if (focusedItem === ELEMENT_SHOW_ALL) {
                        nextFocusedItem = this.resultRefs.length - 1;
                    } else {
                        nextFocusedItem = focusedItem - 1;
                    }

                    break;
                }
            }

            this.setState({focusedItem: nextFocusedItem});

            nextFocusedItem === ELEMENT_SHOW_ALL
                ? (findDOMNode(this.showAllRef.current) as any).focus()
                : (this.resultRefs[nextFocusedItem] as any).current.focus();
        }
    };

    search = async () => {
        const {value, searchProvider} = this.props;

        if (!value) {
            this.setState({results: [], initial: true});

            return;
        }

        if (typeof searchProvider === 'function') {
            this.setState({
                results: await searchProvider(value),
                initial: false,
                focusedItem: null
            });
        }
    };

    debouncedSearch: any = debounce(this.search, this.props.inputDebounce);

    clear = () => {
        const {onChange} = this.props;

        this.setState({focused: false, focusedItem: null});

        onChange('');
    };

    handleChange = ({target}) => {
        const {onChange} = this.props;

        onChange(target.value);
    };

    handleFullSearch = () => {
        const {onSearch} = this.props;

        this.setState({focused: false});

        if (typeof onSearch === 'function') {
            onSearch();
        }
    };

    handleResultAction = item => {
        const {resultAction} = this.props;

        this.setState({focused: false});

        if (typeof resultAction === 'function') {
            resultAction(item);
        }
    };

    handleEnter = e => {
        if (e.keyCode === KEY_CODE_ENTER && e.currentTarget.tagName === 'INPUT') {
            const $inputContainer = findDOMNode(this.inputRef.current) as any;

            if ($inputContainer) {
                const $input = $inputContainer.querySelector(':focus');

                if ($input) {
                    $input.blur();
                }
            }

            this.handleFullSearch();
        }
    };

    renderClearButton() {
        const {textClearLabel} = this.props;

        return (
            <button
                className="rc-search__action-button"
                onClick={this.clear}
                data-test-automation-id="searchButton"
                aria-label={textClearLabel}>
                <Icon name="close" />
            </button>
        );
    }

    renderSearchButton() {
        const {textSearchLabel} = this.props;

        return (
            <button
                disabled
                className="rc-search__action-button"
                data-test-automation-id="searchClear"
                aria-label={textSearchLabel}>
                <Icon name="search" />
            </button>
        );
    }

    renderSearchInput() {
        const {value, name, textSearchPlaceholder} = this.props;
        const {focused} = this.state;

        const button = !focused || !value ? this.renderSearchButton() : this.renderClearButton();

        return (
            <div className="rc-search__input-container">
                <TextInput
                    testAutomationId="searchInput"
                    ref={this.inputRef}
                    autoComplete="off"
                    value={value}
                    name={name}
                    placeholder={textSearchPlaceholder}
                    onChange={this.handleChange}
                    onKeyDown={this.handleEnter}
                    aria-label={textSearchPlaceholder}
                />
                {button}
            </div>
        );
    }

    renderResult = (item, index) => {
        const {renderResult} = this.props;
        const handler = () => this.handleResultAction(item);

        this.resultRefs[index] = React.createRef();

        return (
            <li
                key={index}
                ref={this.resultRefs[index]}
                role="button"
                className="rc-search__result"
                tabIndex={-1}
                onClick={handler}
                onKeyDown={filterKeyCodes(handler)}
                data-test-automation-id={`searchResult${index}`}>
                {renderResult(item)}
            </li>
        );
    };

    renderResults() {
        const {value, textShowAllResults, textNoResults} = this.props;
        const {focused, results, initial} = this.state;

        this.resultRefs = [];

        if (!focused || !value || (!results.length && initial)) {
            return null;
        }

        const content = !results.length ? (
            <div className="rc-search__no-result" data-test-automation-id="searchNoResults">
                {textNoResults}
            </div>
        ) : (
            <React.Fragment>
                <ul className="rc-search__results-list" data-test-automation-id="searchResultsList">
                    {this.getResults().map(this.renderResult)}
                </ul>
                <Button
                    ref={this.showAllRef}
                    className="rc-search__results-footer"
                    onClick={this.handleFullSearch}
                    testAutomationId="searchShowAll">
                    {textShowAllResults} "{value}"
                </Button>
            </React.Fragment>
        );

        return (
            <div className="rc-search__results" data-test-automation-id="searchResults">
                {content}
            </div>
        );
    }

    render() {
        const {testAutomationId} = this.props;

        return (
            <div
                className="rc-search"
                data-test-automation-id={testAutomationId}
                onFocus={this.handleFocus}
                onBlur={this.handleBlur}
                onKeyDown={this.handleKeyboardNavigation}>
                {this.renderSearchInput()}
                {this.renderResults()}
            </div>
        );
    }
}
