import * as React from 'react';
import {debounce as db, filter, isArray, isEmpty, isEqual, keys, remove, template, templateSettings, trim, trimStart} from 'lodash';
import OutsideClickHandler from 'react-outside-click-handler';
import classNames from 'classnames';
import FormComponent from '../FormComponent';
import {getId, searchDataFetch,} from '../function';
import RenderHtml from '../renderFunction';
import PropsTypes from './types';
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,
    uuid: string,
    listData: any[],
    selectedData: any[],
    isActiveComponent: boolean,
    inputClear: boolean,
    value:string
}

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])
        })
    }
}

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

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 {options} = this.props;

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

        this.state = {
            focus: false,
            listData: [],
            selectedData: [],
            uuid: uuid,
            value: '',
            autoFocus: false,
            inputClear: false,
            isActiveComponent: false,
            ...props,
        };

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

    async componentDidMount() {
        await this.setSelectedData();

        const {autoFocus} = this.state;

        if (autoFocus) {
            await this.focus();
        }

        this.validate(this.getInputData()['value']);
        this.props.element && this.props.element(this.input);
    }

    setSelectedData() {
        const {options} = this.props;

        if (options && !isEmpty(options) && options.selected) {
            if (options.selected && isArray(options.selected) && options.selected.length) {
                this.setState({
                    selectedData: this.getDisplayItem('selected'),
                    listData: []
                },() => {
                    this.setInputValue();
                })
            } else {
                this.dataSelectItem(this.getDisplayItem('selected'))
            }
        }
    }

    inputClear() {
        this.setState({
            inputClear: true
        })
    }

    focus() {
        const {selectedData, value} = this.state;
        const {options} = this.props;

        this.input.current && this.input.current.focus();
        this.setState({
            focus: true,
            listData: this.getDisplayItem('data') ? this.getDisplayItem('data') : selectedData,
            value: options && !isEmpty(options) ? '' : value,
            isActiveComponent: true
        })
    }

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

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

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

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

                this.setState({
                    listData: trim(val) ? filterData : options.data
                });
            }
        }

    }

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

    handleChange(e: any) {
        let inputValue: string = e.target.value;

        this.setState({
            value: inputValue
        }, () => {
            this.debounce(inputValue);
            this.validate(this.getInputData()['value']);
        })

    }

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

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

    handlePress = (e: any) => {

        const {value, listData} = this.state;
        const {addTag} = this.props;

        if (e.key === 'Enter') {
            if (addTag && !listData.length) {
                if(addTag && trim(value)) {
                    this.dataSelectItem({
                        [this.getDisplayItem('displayField')]: value,
                        [this.getDisplayItem('displayValue')]: Number(String(Math.random()).split('.')[1]),
                    });

                    this.inputClear()
                }
            }
            e.preventDefault();
        }

        this.props.onKeyPress && this.props.onKeyPress(e)
    };

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

    setInputValue() {
        const {inputClear} = this.state;
        let val: any = this.getInputData()['value'];
        let iClear:boolean = false;

        val = inputClear ? '' : this.getInputData()['value'];
        iClear= false;

        this.setState({
            value: val,
            inputClear: iClear
        }, () => {
            this.validate(this.getInputData()['value']);
            this.dataOnReady()
        })
    }

    async dataSelectItem(val: any) {

        const {selectedData, value} = this.state;
        const {options, multiple, openAfterAddTag} = this.props;

        let afterData: any = {};
        let tempselectedData: any[] = selectedData;

        await (() => {
            if (options && !isEmpty(options)) {
                if (multiple) {
                    if (filter(tempselectedData, k => isEqual(val, k)).length > 0) {
                        remove(tempselectedData, item => isEqual(val, item));
                    } else {
                        tempselectedData.push(val)
                    }

                    let ldata:any = {}

                    if (openAfterAddTag) {
                        ldata = {
                            listData: this.getDisplayItem('data')
                        }
                    }

                    afterData = {
                        selectedData: tempselectedData,
                        value: !selectedData.length ? '' : value,
                        ...ldata
                    }
                } else {
                    tempselectedData = [];
                    val && tempselectedData.push(val);
                    afterData = {
                        selectedData: tempselectedData,
                        listData: []
                    }
                }
            }
        })();

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

    getInputData() {
        const {selectedData, value} = this.state;

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

        if (selectedData && selectedData.length) {
            if (selectedData.length === 1) {
                str = selectedData[0][this.getDisplayItem('displayField')]
            } else {
                str = '';
                selectedData.forEach((item: any) => {
                    if (item) {
                        str = str + ', ' + item[this.getDisplayItem('displayField')]
                    }
                });
                str = trimStart(str, ' ,');
            }
        } else {
            str = '';
        }

        output = selectedData;
        result = {
            value: str,
            output
        };

        return result
    }

    async removeTag(val: any) {
        const {selectedData, value} = this.state;
        let tempselectedData: any[] = selectedData;

        if (val) {
            await remove(tempselectedData, item => isEqual(val, item));

            this.setState({
                selectedData: tempselectedData,
                value: !selectedData.length ? '' : value
            }, () => {
                this.setInputValue()
            })
        }
    }

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

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

        if (isActiveComponent) {
            this.setState({
                focus: false,
                listData: [],
                isActiveComponent: false,
            }, () => {
                this.setInputValue()
            })
        }
    }

    renderResult() {
        const {listData, selectedData, value} = this.state;
        const {addTag, multiple, removeAfterSelect} = this.props;

        if (listData && listData.length) {
            return <Result
                multiple={multiple}
                data={listData}
                field={this.getDisplayItem('displayField')}
                cid={this.getDisplayItem('displayValue')}
                selectData={selectedData}
                addTag={addTag}
                removeAfterSelect={removeAfterSelect}
                value={value}
                onRemoveItem={(item:any) => this.removeTag(item)}
                onRemoveAll={() => this.removeAll()}
                onSelectItem={(val:any) => this.dataSelectItem(val)}
            />
        }

        return null
    }

    renderInputDropdown() {
        const {listData} = this.state;

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

        return null
    }

    public render(): JSX.Element {
        const {value, listData, uuid} = this.state;
        const {label, icon, disabled, loading, color, rounded, customStyle, infoText, noBorder, noRadius} = this.props;
        const uiClass = classNames(`krax-input ${this.size()} is-${color || 'default'}`, {
                '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(),
                'is-loading': loading,
                'is-data': value
            }),
            fieldClass = classNames(`krax-field ${customStyle || ''}`, {
                'has-addons': label,
            }),
            captionClass = classNames('caption-text-ui', {
                'is-data': value
            });

        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}>
                            <input
                                className={uiClass}
                                id={this.props.id || `input-${uuid}`}
                                name={this.props.name || `name-${uuid}`}
                                type="text"
                                value={value}
                                placeholder={this.getCaption()}
                                autoComplete="off"
                                onChange={(val: any) => this.handleChange(val)}
                                onBlur={() => this.handleBlur()}
                                onClick={() => this.handleClick()}
                                onKeyPress={(e) => {this.handlePress(e)}}
                                onKeyDown={(e) => this.handleDown(e)}
                                spellCheck={false}
                                disabled={disabled}
                                readOnly={this.readOnly()}
                                ref={this.input}
                                tabIndex={0}
                                autoCorrect={'off'}
                            />
                            <div className={`${captionClass}`}>
                                {this.getCaption()}
                            </div>
                            {/*{this.renderResult()}*/}
                            {this.renderInputDropdown()}
                            {RenderHtml.renderIcon(icon, 'left', this.size())}
                            {!loading ? RenderHtml.renderIcon(icon, 'right', this.size()) : null}
                        </div>
                    </div>
                    {RenderHtml.renderError(this.error())}
                    {isEmpty(this.error()) ? <div className="krax-help">{infoText}</div> : null}
                </div>
            </OutsideClickHandler>
        );
    }

    getCaption() {
        const {selectedData, value} = this.state;
        const {multiple, caption} = this.props;

        if (multiple && selectedData.length && value) {
            return `${selectedData.length} öğe seçildi`
        }

        if (multiple && selectedData.length && !value) {
            return 'ara..'
        }
        return caption
    }

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