import ngModule from '../../module';
import Input from '../../lib/input';
import assignProps from '../../lib/assign-props';
import './style.scss';

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

        componentHandler.upgradeElement(
            this.element.querySelector('.mdl-textfield'));
    }

    $postLink() {
        super.$postLink();
        this.assignDropdownProps();
        this.showField = this.$.dropDown.showField;
        this.valueField = this.$.dropDown.valueField;
        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 {any} The autocomplete value.
     */
    get field() {
        return this._field;
    }

    set field(value) {
        if (angular.equals(value, this._field))
            return;

        this._field = value;
        this.refreshSelection();
        this.emit('change', {value});
    }

    /**
     * @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;

        this.selection = this.fetchSelection();
    }

    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;

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

            let json = encodeURIComponent(JSON.stringify(filter));
            this.$http.get(`${this.url}?filter=${json}`).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 = '';
        let hasTemplate = this.$transclude && this.$transclude.isSlotFilled('tplItem');

        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];
                if (hasTemplate) {
                    let template = this.$transclude(() => {}, null, 'tplItem').text();
                    display = this.$interpolate(template)(this._selection);
                }
            }
        }

        this.input.value = display;

        if (this.translateFields) {
            if (this.translateFields.indexOf(this.showField) > -1)
                this.input.value = this.$translate.instant(display);
        }

        this.mdlUpdate();
    }

    mdlUpdate() {
        let field = this.element.querySelector('.mdl-textfield');
        let mdlField = field.MaterialTextfield;
        if (mdlField) mdlField.updateClasses_();
    }

    setValue(value) {
        this.field = value;
        if (this.form) this.form.$setDirty();
    }

    onDropDownSelect(value) {
        this.setValue(value);
        this.field = value;
    }

    onClearClick(event) {
        event.preventDefault();
        this.setValue(null);
    }

    onKeyDown(event) {
        if (event.defaultPrevented) return;

        switch (event.keyCode) {
        case 38: // Up
        case 40: // Down
        case 13: // Enter
            this.showDropDown();
            break;
        default:
            if (event.key.length == 1)
                this.showDropDown(event.key);
            else
                return;
        }

        event.preventDefault();
    }

    onMouseDown(event) {
        event.preventDefault();
        this.showDropDown();
    }

    onDataReady() {
        this.refreshSelection();
    }

    assignDropdownProps() {
        if (!this.$.dropDown) return;
        assignProps(this, this.$.dropDown, [
            'valueField',
            'showField',
            'showFilter',
            'multiple',
            '$transclude',
            'translateFields',
            'model',
            'data',
            'url',
            'fields',
            'include',
            'where',
            'order',
            'limit',
            'searchFunction'
        ]);
    }

    showDropDown(search) {
        this.assignDropdownProps();
        this.$.dropDown.show(this.input, search);
    }
}
Autocomplete.$inject = ['$element', '$scope', '$http', '$transclude', '$translate', '$interpolate'];

ngModule.component('vnAutocomplete', {
    template: require('./autocomplete.html'),
    controller: Autocomplete,
    bindings: {
        label: '@',
        field: '=?',
        disabled: '<?',
        showField: '@?',
        valueField: '@?',
        initialData: '<?',
        showFilter: '<?',
        selection: '=?',
        multiple: '<?',
        data: '<?',
        url: '@?',
        fields: '<?',
        include: '<?',
        where: '<?',
        order: '@?',
        limit: '<?',
        translateFields: '<?',
        searchFunction: '&?'
    },
    transclude: {
        tplItem: '?tplItem'
    },
    require: {
        form: '?^form'
    }
});