import * as React from 'react';
import {debounce as db, isArray, isEmpty, keys, template, templateSettings, trim,} from 'lodash';
import FormComponent from '../FormComponent';
import OutsideClickHandler from 'react-outside-click-handler';
import classNames from 'classnames';
import {getId, nullToString, searchDataFetch,} from '../function';
import RenderHtml from '../renderFunction';
import PropsTypes from './types';
import * as IMask from 'imask';
import Icon from '../Icon'
import * as JsSearch from 'js-search';
import Result from '../Result'
import InputDropdown from "../InputDropdown";

let jsoptions = new JsSearch.Search('id');

templateSettings.interpolate = /{{([\s\S]+?)}}/g;

interface State extends PropsTypes {
    focus: boolean,
    ddMenuIsActive: boolean,
    uuid: string,
    dropdownValue: any,
    listData: any[],
    selectedData: any[],
    maskOptions: any,
    passView: boolean,
    imask: any,
    isActiveComponent: boolean,
}

function getDropdownValue(dropdown: any) {
    return dropdown && dropdown.data.filter((item: any) => item[dropdown.displayValue] === dropdown.selected)[0]
}

function debounceTime(debounce: number) {
    return debounce ? debounce : 0
}

function addDocumentsForSearch(data: any) {
    if (data && data.data && isArray(data.data) && data.data.length) {
        keys(data.data[0]).forEach(c => {
            jsoptions.addIndex(c)
        });

        data.data.forEach((k: any) => {
            jsoptions.addDocuments([k])
        })
    } else {
        jsoptions.addDocuments([])
    }
}

function setDocumentIndex(data: any) {
    if (data && data.length) {
        keys(data[0]).forEach(c => {
            jsoptions.addIndex(c)
        });
        jsoptions.addDocuments(data)
    }
}

function initialMask(maskOptions: any, element: any) {
    if (maskOptions && !isEmpty(maskOptions)) {
        return new IMask(element, maskOptions);
    }

    return false
}

export default class Input extends FormComponent<PropsTypes, State> {

    public state: State;
    private input = React.createRef<HTMLInputElement>();

    constructor(props: PropsTypes, {}) {
        super(props);
        const uuid = getId(props.id, this.defaultId);
        const {search, value, dropdown, mask, debounce, type} = props;

        if (search) {
            jsoptions = new JsSearch.Search(this.getDisplayItem('displayValue'));
        }

        if (search && !isEmpty(search)) {
            addDocumentsForSearch(search);
        }

        this.state = {
            focus: false,
            ddMenuIsActive: false,
            listData: [],
            selectedData: [],
            uuid: uuid,
            value: nullToString(value),
            dropdownValue: getDropdownValue(dropdown),
            maskOptions: mask && !isEmpty(mask) ? {mask: String(mask)} : {},
            imask: null,
            autoFocus: false,
            passView: false,
            isActiveComponent: false,
            type: type
        };

        this.debounce = db(this.debounce, debounceTime(debounce || 0));
    }

    async componentDidMount() {
        const { value, maskOptions } = this.state;
        const { autoFocus } = this.props;

        this.setState({
            size: this.size(),
            readOnly: this.readOnly(),
        });

        await this.setMask(maskOptions.mask || '');
        if (autoFocus) {
            await this.focus();
        }
        this.validate(value);
        this.props.element && this.props.element(this.input);
    }

    setMask(newMask?: any) {
        const { imask } = this.state;
        const mOptions = { mask: newMask };

        if (!newMask) {
            return;
        }

        if (imask) {
            imask.destroy();
        }


        const element = this.input.current;
        const masked = initialMask(mOptions, element);

        this.setState({
            imask: masked,
            value: masked._value
        }, () => {
            masked.updateValue();
        })
    }

    updateMask(newMask?: any) {
        const { imask } = this.state;

        if (!newMask && imask) {
            this.setState({
                value: imask._unmaskedValue
            })
        } else if (newMask) {
            this.setMask(newMask);
        }
    }

    focus() {

        const {value} = this.state;
        const {search} = this.props;

        this.input.current && this.input.current.focus();

        if (value && search) {
            this.debounce(value)
        }
        this.setState({
            focus: true,
            isActiveComponent: true
        })
    }

    debounce(val: string) {
        const {selectedData} = this.state;
        const {search} = this.props;

        if (search && !isEmpty(search) && val) {
            if (search.remote && !isEmpty(search.remote)) {
                let url: any = search && template(search.remote);
                url = url({key: val});

                searchDataFetch(url, val, (callback: any) => {
                    setDocumentIndex(callback.data);
                    this.setState({
                        listData: val ? callback.data : selectedData,
                        loading: callback.loading
                    }, () => {
                        this.dataOnReady()
                    })
                })
            }

            if (search.data && !isEmpty(search.data)) {
                let filterData: any[] = [];

                filterData = jsoptions.search(val);

                this.setState({
                    listData: trim(val) ? filterData : search.data
                }, () => {
                    this.dataOnReady()
                });
            }
        } else {
            this.setState({
                listData: [],
                loading: false
            }, () => {
                this.dataOnReady()
            });
        }
    }

    componentWillReceiveProps(nextProps: any) {
        if (nextProps.value !== this.props.value) {
            let newValue = nullToString(nextProps.value);

            this.setState({
                value: newValue
            }, () => {
                this.setInputValue()
            })
        }

        if (nextProps.mask !== this.props.mask) {
            this.updateMask(nextProps.mask)
        }

        if (nextProps.dropdown !== this.props.dropdown) {
            this.setState({
                dropdownValue: getDropdownValue(nextProps.dropdown)
            })
        }

        if (nextProps.search !== this.props.search) {
            if (nextProps.search && !isEmpty(nextProps.search)) {
                addDocumentsForSearch(nextProps.search);
            }
        }
    }

    dataOnReady() {
        this.props.onChange && this.props.onChange(this.getInputData()['output'])
    }

    handleChange(e: any) {
        const { imask } = this.state;
        let inputValue: string = e.target.value;

        if (imask) {
            imask.updateValue();
        }

        this.setState({
            value: inputValue
        }, () => {

            if (imask) {
                imask.updateValue();
            }

            this.debounce(inputValue);
            this.validate(inputValue);
        })
    }

    handleBlur = () => {
        this.props.onBlur && this.props.onBlur(this.getInputData()['output']);
    };

    handleClick = () => {
        this.props.onClick && this.props.onClick()
    };

    handlePress = (e: any) => {
        this.props.onKeyPress && this.props.onKeyPress(e)
    };

    handleDown = (e: any) => {
        this.props.onKeyDown && this.props.onKeyDown(e)
    };

    setInputValue() {
        let val: any = this.getInputData()['value'];

        this.setState({
            value: val,
        }, () => {
            this.validate(val);
            this.dataOnReady()
        })
    }

    async dataSelectItem(val: any) {

        const {selectedData} = this.state;
        const {search} = this.props;
        let afterData: any = {};
        let tempselectedData: any[] = selectedData;

        await (() => {
            if (search && !isEmpty(search)) {
                tempselectedData = [];
                val && tempselectedData.push(val);
                afterData = {
                    selectedData: tempselectedData,
                    listData: []
                }
            }
        })();

        this.setState({
            ...afterData
        }, () => {
            this.setInputValue();
        })
    }

    getInputData() {
        const {selectedData, value, dropdownValue, /* maskOptions */} = this.state;
        const {dropdown} = this.props;

        let result: Object = {};
        let dropdownData: Object = {};
        let output: any = null;
        let str: string | undefined = value;

        if (selectedData && selectedData.length) {
            str = selectedData[0][this.getDisplayItem('displayField')]
        }

        output = str;

        if (dropdown && !isEmpty(dropdown)) {
            dropdownData = {
                dropdown: dropdownValue[dropdown.displayValue]
            };

            output = {
                value: str,
                ...dropdownData
            }
        }

        result = {
            value: str,
            output
        };

        return result
    }

    removeAll() {
        this.setState({
            selectedData: [],
            listData: [],
            value: ''
        })
    }

    setDropdownValue(val: any) {
        this.setState({
            dropdownValue: val,
            ddMenuIsActive: false
        }, () => {
            this.dataOnReady()
        })
    }

    renderDropDown(): JSX.Element | null {
        const {ddMenuIsActive, dropdownValue} = this.state;
        const {dropdown, disabled} = this.props;



        if (dropdown && !isEmpty(dropdown)) {
            return <div className="krax-control">
                <div className={`krax-dropdown is-right ${ddMenuIsActive ? 'is-active' : ''}`}>
                    <div className="krax-dropdown-trigger">
                        <button
                            disabled={disabled}
                            onClick={() => this.setState({ddMenuIsActive: !ddMenuIsActive, focus: true})}
                            className={`krax-button ${this.size()}`} type="button" aria-haspopup="true"
                            aria-controls={`ddMenu-${this.defaultId}`}>
                            <span>{dropdownValue[dropdown.displayField]}</span>
                            <span className="krax-icon">
                                <Icon icon="fas fa-caret-down"/>
                            </span>
                        </button>
                    </div>
                    <div className="krax-dropdown-menu" id={`ddMenu-${this.defaultId}`} role="menu">
                        <div className="krax-dropdown-content">
                            {
                                dropdown.data.map((item, i) => {
                                    return <a key={i} className="krax-dropdown-item"
                                              onClick={() => this.setDropdownValue(item)}> {item[dropdown.displayField]} </a>
                                })
                            }
                        </div>
                    </div>
                </div>
            </div>
        }

        return null
    }

    renderPasswordView() {
        const {passView} = this.state;

        if (this.props.type === 'password' && this.props.passwordView) {
            return <span className={`krax-icon is-right ${passView}`} onClick={() => this.setState({passView: !passView})}
                         style={{pointerEvents: 'auto', cursor: 'pointer'}}>
                <span className={`${passView ? 'd-none' : ''}`}>
                    <Icon icon="fal fa-eye"/>
                </span>
                <span className={`${!passView ? 'd-none' : ''}`}>
                    <Icon icon="fal fa-eye-slash"/>
                </span>
            </span>
        }
        return null
    }

    outsideClick() {
        const {isActiveComponent} = this.state;

        if (isActiveComponent) {
            this.setState({
                focus: false,
                listData: [],
                selectedData: [],
                ddMenuIsActive: false,
                isActiveComponent: false,
            })
        }
    }

    renderResult() {
        const {listData, selectedData} = this.state;

        if (listData && listData.length) {
            return <Result
                data={listData}
                field={this.getDisplayItem('displayField')}
                selectData={selectedData}
                onSelectItem={(val: any) => this.dataSelectItem(val)}
                onRemoveAll={() => this.removeAll()}
            />
        }

        return null
    }

    renderInputDropdown() {
        const {customRender, inputDropdown} = this.props;
        const {listData} = this.state;

        if (customRender) {
            return <InputDropdown show={inputDropdown}>
                {customRender()}
            </InputDropdown>
        }

        if (listData && listData.length) {
            return <InputDropdown show={true} size="is-full">
                {this.renderResult()}
            </InputDropdown>
        }

        return null
    }

    public render(): JSX.Element {

        const {value, uuid, passView, listData} = this.state;
        const {autoComplete, spellCheck, color, disabled, readOnlyInput, maxLength, icon, customStyle, rounded, loading, label, dropdown, infoText, button, caption, /* customStyle, label, , button, infoText, caption,*/ type, passwordView,  noBorder, noRadius, search} = this.props;
        const uiClass = classNames(`krax-input ${this.size()} is-${color || 'default'} ${customStyle}`, {
                'is-rounded': rounded,
                'is-danger': this.error(),
                'is-focused': this.error(),
                'is-bottom-radius-none': listData.length,
                'no-border': noBorder,
                'no-radius': noRadius,
            }),
            containerClass = classNames(`krax-control is-expanded ${this.size()}`, {
                'has-icons-left': this.iconLeft(),
                'has-icons-right': this.iconRight() || passwordView,
                'is-loading': loading,
                'is-data': value
            }),
            fieldClass = classNames(`krax-field`, {
                'has-addons': label || dropdown || button,
            }),
            captionClass = classNames('caption-text-ui', {
                'is-data': value
            });

        let t = type;
        if (passwordView && passView) {
            t = 'text'
        } else if (passwordView && !passView) {
            t = 'password'
        }

        return (
            <OutsideClickHandler onOutsideClick={() => {
                this.outsideClick()
            }}>
                <div className="krax-field is-expanded" onClick={() => this.focus()}>
                    <div className={fieldClass}>
                        {RenderHtml.renderLabel(label, 'left', this.size())}
                        <div className={containerClass} key={1}>
                            <input
                                className={uiClass}
                                id={this.props.id || `input-${uuid}`}
                                name={this.props.name || `name-${uuid}`}
                                type={t}
                                value={value}
                                placeholder={caption}
                                autoComplete={autoComplete ? autoComplete : 'off'}
                                onChange={(val: any) => this.handleChange(val)}
                                onBlur={() => this.handleBlur()}
                                // onFocus={() => this.focus()}
                                onClick={() => this.handleClick()}
                                onKeyPress={(e) => {
                                    this.handlePress(e)
                                }}
                                onKeyDown={(e) => this.handleDown(e)}
                                onKeyUp={(e) => !search && this.handleChange(e)}
                                spellCheck={spellCheck}
                                disabled={disabled}
                                readOnly={this.readOnly() || readOnlyInput}
                                maxLength={maxLength}
                                ref={this.input}
                                tabIndex={0}
                                autoCorrect={'off'}
                            />
                            <div className={`${captionClass}`}>
                                {caption}
                            </div>
                            {/*{this.renderResult()}*/}
                            {this.renderInputDropdown()}
                            {RenderHtml.renderIcon(icon, 'left', this.size())}
                            {!loading ? RenderHtml.renderIcon(icon, 'right', this.size()) : null}
                            {this.renderPasswordView()}
                        </div>
                        {RenderHtml.renderLabel(label, 'right', this.size())}
                        {this.renderDropDown()}

                        {button ? <div className="krax-control">
                            {button()}
                        </div> : null}

                    </div>
                    {RenderHtml.renderError(this.error())}
                    {isEmpty(this.error()) ? <div className="krax-help">{infoText}</div> : null}
                </div>
            </OutsideClickHandler>
        );
    }

    getDisplayItem(type: string) {
        const {search} = this.props;
        if (search && search[type] && !isEmpty(search[type])) {
            return search && search[type];
        }
        return false
    }
}
