'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import {FormControl} from './FormControl';
import {default as ReactAutosuggest} from 'react-autosuggest';
import {TextInputControl} from './TextInputControl';
import {highlight} from '../list/utils';
import {Store} from '../../data/Store';
import {playMessage} from '../../utils/playMessage';
import {translate} from '../../../utils';
import lang from '../../../lang/common/index-en_US';

const ENTER_CHAR_CODE = 13;
const READ_KEY_DOWN_TIMEOUT = 1000;

export class AutosuggestControl extends FormControl<any> {
    static propTypes = {
        ...FormControl.propTypes,
        /* if getSuggestions() is provided values is not necessary */
        values: PropTypes.array,
        getSuggestionValue: PropTypes.func.isRequired,
        getSuggestionText: PropTypes.func.isRequired,
        valueMatcher: PropTypes.func,
        placeholder: PropTypes.string,
        maxSuggestionsCount: PropTypes.number,
        minCharactersToShowSuggestions: PropTypes.number,
        disabled: PropTypes.bool,
        allowMismatching: PropTypes.bool,

        /**
         * @function (query: String, innerFilter: function(values))
         * @returns { Array| Promise<Array>}
         * */
        getSuggestions: PropTypes.func,
        onSubmitChanges: PropTypes.func,
        autoFocus: PropTypes.bool,
        onInputChange: PropTypes.func,
        suggestionPromptLabel: PropTypes.string,
        noSuggestionPromptLabel: PropTypes.string
    };

    static defaultProps = {
        ...FormControl.defaultProps,
        maxSuggestionsCount: 10,
        minCharactersToShowSuggestions: 2,
        valueMatcher(inputValue, value) {
            return inputValue === this.props.getSuggestionValue(value);
        },
        allowMismatching: false,
        onInputChange: v => v,
        get suggestionPromptLabel() {
            return lang.AUTO_SUGGEST_SUGGESTION_PROMPT_LABEL;
        },
        get noSuggestionPromptLabel() {
            return lang.NO_SUGGESTION_PROMPT_LABEL;
        }
    };

    constructor(props, context) {
        super(props, context);

        let propsFromContext = this.getPropsFromContext();

        this.state = {
            suggestions: [],
            isInputFocused: false,
            value: propsFromContext.value
                ? propsFromContext.getSuggestionValue(propsFromContext.value)
                : ''
        };
    }

    componentWillReceiveProps(nextProps) {
        let propsFromContext = this.getPropsFromContext();

        if (nextProps.value !== undefined) {
            this.setState({
                value:
                    nextProps.value === null
                        ? ''
                        : propsFromContext.getSuggestionValue(nextProps.value)
            });
        }
    }

    componentDidUpdate(oldProps, oldState) {
        super.componentDidUpdate(oldProps, oldState);

        const getCount = list => (_.isEmpty(list) ? 0 : list.length);
        const {suggestions: oldSuggestions} = oldState;
        const {suggestions} = this.state;
        const oldCount = getCount(oldSuggestions);
        const newCount = getCount(suggestions);

        if (oldCount !== newCount) {
            this.playSuggestionPromptMessage(newCount);
        }
    }

    onChange(event, {newValue}) {
        this.setState({
            value: newValue
        });
        this.props.onInputChange(newValue);
    }

    onSubmitChanges(newValue) {
        let values = this.props.values || this.state.suggestions || [];

        let propsFromContext = this.getPropsFromContext();

        let matchedValue = values.find(value =>
            this.props.valueMatcher.call(this, newValue, value)
        );

        let allowMismatching = this.props.allowMismatching || this.props.getSuggestions;

        let mismatchedValue = allowMismatching ? newValue : '';

        this.setState({
            value: matchedValue
                ? propsFromContext.getSuggestionValue(matchedValue)
                : mismatchedValue
        });

        let updateValue = matchedValue || mismatchedValue || null;
        propsFromContext.onChange(updateValue);

        if (_.isFunction(this.props.onSubmitChanges)) {
            this.props.onSubmitChanges(updateValue);
        }
    }

    handleFocus = () => {
        this.setState({isInputFocused: true});
    };

    handleBlur = event => {
        this.onSubmitChanges(event.target.value);
        this.setState({isInputFocused: false});
    };

    onKeyPress(event) {
        if (event.charCode === ENTER_CHAR_CODE) {
            this.onSubmitChanges(event.target.value);
        }
    }

    onSuggestionSelected(event, {suggestionValue}) {
        this.onSubmitChanges(suggestionValue);
    }

    onSuggestionsFetchRequested({value}) {
        return Promise.resolve(this.getSuggestions(value)).then(suggestions =>
            this.setState({suggestions})
        );
    }

    playSuggestionPromptMessage = _.debounce(count => {
        const {isInputFocused} = this.state;
        if (!isInputFocused) {
            return;
        }

        const {suggestionPromptLabel, noSuggestionPromptLabel} = this.props;
        const message =
            count === 0
                ? translate(noSuggestionPromptLabel, {count})
                : translate(suggestionPromptLabel, {count});
        playMessage(message);
    }, READ_KEY_DOWN_TIMEOUT);

    onSuggestionsClearRequested() {
        this.setState({
            suggestions: []
        });
    }

    filterSuggestions = (query, values) => {
        if (query.length >= this.props.minCharactersToShowSuggestions) {
            const regex: any = Store.searchStringToRegExp(query);

            let filteredValues = values.filter(value => {
                regex.lastIndex = 0;
                return regex.test(this.props.getSuggestionText(value));
            });

            return _.take(filteredValues, this.props.maxSuggestionsCount);
        }
        return [];
    };

    getSuggestions(query) {
        if (_.isFunction(this.props.getSuggestions)) {
            return this.props.getSuggestions(
                query,
                /**innerFilter*/ values => this.filterSuggestions(query, values)
            );
        }
        return this.filterSuggestions(query, this.props.values);
    }

    renderSuggestion(value, {query}) {
        const regex: any = Store.searchStringToRegExp(query);
        regex.lastIndex = 0;

        return highlight(regex, this.props.getSuggestionText(value));
    }

    renderInputComponent = props => <TextInputControl {...props} />;

    renderSuggestionsContainer = renderOptions => {
        const {containerProps, children} = renderOptions;

        return (
            <div {...containerProps} role={null}>
                {children}
            </div>
        );
    };

    render() {
        const {suggestions} = this.state;

        const inputProps = {
            placeholder: this.props.placeholder,
            name: this.props.name,
            onChange: this.onChange.bind(this),
            value: this.state.value,
            disabled: this.props.disabled,
            onBlur: this.handleBlur,
            onFocus: this.handleFocus,
            onKeyPress: this.onKeyPress.bind(this),
            autoFocus: this.props.autoFocus,
            maxLength: this.props.maxLength
        };

        const ariaDescId = this.props['aria-describedby'];
        if (ariaDescId) {
            inputProps['aria-describedby'] = ariaDescId;
        }

        const ariaLabel = this.props['aria-label'];
        if (ariaLabel) {
            inputProps['aria-label'] = ariaLabel;
        }

        const theme = {
            container: 'autosuggest-container',
            containerOpen: 'autosuggest-container-open',
            input: 'autosuggest-input',
            suggestionsContainer: 'autosuggest-suggestions-container',
            suggestionsList: 'autosuggest-suggestions-list',
            suggestion: 'autosuggest-suggestion',
            itemHighlighted: 'autosuggest-suggestion-focused',
            sectionContainer: 'autosuggest-section-container',
            sectionTitle: 'autosuggest-section-title'
        };

        return (
            <ReactAutosuggest
                ref="autoSuggest"
                suggestions={suggestions}
                onSuggestionsFetchRequested={this.onSuggestionsFetchRequested.bind(this)}
                onSuggestionsClearRequested={this.onSuggestionsClearRequested.bind(this)}
                getSuggestionValue={this.props.getSuggestionValue.bind(this)}
                onSuggestionSelected={this.onSuggestionSelected.bind(this)}
                renderSuggestion={this.renderSuggestion.bind(this)}
                renderInputComponent={this.renderInputComponent}
                renderSuggestionsContainer={this.renderSuggestionsContainer}
                inputProps={inputProps}
                theme={theme}
            />
        );
    }
}
