import ngModule from '../../module';
import Field from '../field';
import assignProps from '../../lib/assign-props';
import {mergeWhere} from 'vn-loopback/util/filter';
import './style.scss';

/**
 * Input with option selector.
 *
 * @property {String} showFiled The data field name that should be shown
 * @property {String} valueField The data field name that should be used as value
 * @property {Array} data Static data for the autocomplete
 * @property {Object} intialData An initial data to avoid the server request used to get the selection
 * @property {Boolean} multiple Whether to allow multiple selection
 * @property {Object} selection Current object selected
 *
 * @event change Thrown when value is changed
 */
export default class Autocomplete extends Field {
    constructor($element, $, $transclude) {
        super($element, $, $transclude);
        this.$transclude = $transclude;
        this._selection = null;
        this.input = this.element.querySelector('input');
    }

    $postLink() {
        super.$postLink();
        this.assignDropdownProps();
        this.showField = this.$.dropDown.showField;
        this.valueField = this.$.dropDown.valueField;
        this.refreshSelection();
    }

    /**
     * @type {any} The autocomplete value.
     */
    get field() {
        return super.field;
    }

    set field(value) {
        super.field = value;
        this.refreshSelection();
    }

    get model() {
        return this._model;
    }

    set model(value) {
        this.dropDownAssign({model: value});
    }

    get data() {
        return this._data;
    }

    set data(value) {
        this.dropDownAssign({data: value});
    }

    get url() {
        return this._url;
    }

    set url(value) {
        this.dropDownAssign({url: value});
    }

    dropDownAssign(props) {
        for (let prop in props)
            this[`_${prop}`] = props[prop];
        if (this.$.dropDown)
            Object.assign(this.$.dropDown, props);
    }

    /**
     * @type {Object} The selected data object, you can use this property
     * to prevent requests to display the initial value.
     */
    get selection() {
        return this._selection;
    }

    set selection(value) {
        this._selection = value;
        this.refreshDisplayed();
    }

    get initialData() {
        return this._initialData;
    }

    set initialData(value) {
        this._initialData = value;
        this.refreshSelection();
    }

    selectionIsValid(selection) {
        return selection
            && selection[this.valueField] == this._field
            && selection[this.showField] != null;
    }

    refreshSelection() {
        if (this._field == null) {
            this.selection = null;
            return;
        }

        if (!(this.valueField && this.showField)
        || this.selectionIsValid(this._selection))
            return;

        const selection = this.fetchSelection();
        this.selection = selection;
    }

    fetchSelection() {
        if (this.selectionIsValid(this.initialData))
            return this.initialData;

        if (!this.$.dropDown)
            return null;

        let data;
        if (this.$.dropDown.model)
            data = this.$.dropDown.model.orgData;

        if (data) {
            let selection = data.find(i => this.selectionIsValid(i));
            if (selection) return selection;
        }

        if (this.url) {
            let where = {};

            if (this.multiple)
                where[this.valueField] = {inq: this.field};
            else
                where[this.valueField] = this._field;

            where = mergeWhere(where, this.fetchFunction);

            let filter = {
                fields: this.$.dropDown.getFields(),
                where: where
            };

            if (this.include)
                filter.include = this.include;

            let json = encodeURIComponent(JSON.stringify(filter));

            let url;
            if (this.url.includes('?'))
                url = `${this.url}&filter=${json}`;
            else
                url = `${this.url}?filter=${json}`;

            this.$http.get(url).then(
                json => this.onSelectionRequest(json.data),
                () => this.onSelectionRequest()
            );
        }

        return null;
    }

    onSelectionRequest(data) {
        if (data && data.length > 0) {
            if (this.multiple)
                this.selection = data;
            else
                this.selection = data[0];
        } else
            this.selection = null;
    }

    refreshDisplayed() {
        let display = '';

        if (this._selection && this.showField) {
            if (this.multiple && Array.isArray(this._selection)) {
                for (let item of this._selection) {
                    if (display.length > 0) display += ', ';
                    display += item[this.showField];
                }
            } else
                display = this._selection[this.showField];
        }

        this.input.value = display;

        if (this.translateFields && this.selection) {
            const translations = [];
            for (let field of this.translateFields) {
                const fieldValue = this._selection[field];
                translations.push({
                    original: fieldValue,
                    value: this.$t(fieldValue)
                });
            }

            for (let translation of translations) {
                const orgValue = translation.original;
                const value = translation.value;

                display = display.replace(orgValue, value);
            }

            this.input.value = display;
        }
    }

    onDropDownSelect(item) {
        const value = item[this.valueField];
        this.selection = item;
        this.change(value);
    }

    onDropDownClose() {
        setTimeout(() => this.focus());
    }

    onContainerKeyDown(event) {
        if (event.defaultPrevented) return;
        switch (event.key) {
        case 'ArrowUp':
        case 'ArrowDown':
            this.showDropDown();
            break;
        default:
            if (event.key.length == 1)
                this.showDropDown(event.key);
            else
                return;
        }
    }

    onContainerClick(event) {
        if (event.defaultPrevented) return;
        this.showDropDown();
    }

    onDataReady() {
        this.refreshSelection();
    }

    assignDropdownProps() {
        if (!this.$.dropDown) return;
        this.$.dropDown.copySlot('tplItem', this.$transclude);

        assignProps(this, this.$.dropDown, [
            'valueField',
            'showField',
            'showFilter',
            'multiple',
            'translateFields',
            'model',
            'data',
            'url',
            'fields',
            'include',
            'where',
            'order',
            'limit',
            'searchFunction',
            'whereFunction'
        ]);
    }

    showDropDown(search) {
        if (!this.editable) return;
        this.assignDropdownProps();
        this.$.dropDown.show(this.container, search);
    }

    get fetchFunction() {
        return this._fetchFunction;
    }

    set fetchFunction(value) {
        this._fetchFunction = value;

        if (value)
            this.refreshSelection();
    }
}
Autocomplete.$inject = ['$element', '$scope', '$transclude'];

ngModule.vnComponent('vnAutocomplete', {
    template: require('./index.html'),
    controller: Autocomplete,
    bindings: {
        showField: '@?',
        valueField: '@?',
        initialData: '<?',
        showFilter: '<?',
        selection: '=?',
        multiple: '<?',
        data: '<?',
        url: '@?',
        fields: '<?',
        include: '<?',
        where: '<?',
        order: '@?',
        limit: '<?',
        translateFields: '<?',
        searchFunction: '&?',
        whereFunction: '&?',
        fetchFunction: '<?'
    },
    transclude: {
        tplItem: '?tplItem'
    }
});