<template>
<div :class="divClass">
    <label v-if="mega">{{title}}<span class="rfo" v-if="rfo">*</span><span class="rfq" v-if="rfq">*</span><span class="aster" v-if="aster">*</span></label>
    <select :id="id" :name="name" ref="choiceTarget" multiple="multiple" v-if="editLocal && multiple" :value="value" :data-placeholder="placeholder" style="display:none;">
        <slot></slot>
    </select>
    <select :id="id" :name="name" ref="choiceTarget" v-else-if="editLocal && !multiple" :value="value" :data-placeholder="placeholder" style="display:none;">
        <slot></slot>
    </select>
    <div v-else>
        <span class="noneditable">{{ getOptionLabel }}</span>
    </div>
    <span v-show="editLocal && hasError" class="vldtn-error" v-html="message"></span>
</div>
</template>

<script>
import { shallowCopy, debounce } from '../../utils/utils'
import { keys, extend, isNull, clone, isEqual, isEmpty, isArray, union, isObject, sortBy } from 'lodash'
/**
* Vue integration for Selectize JQuery plugin to display comboboxes.
* Events listened gc:select
* <br> <b>tasks: </b> <i>option, item, control</i>
* <br> <b>controls: </b> <i>lock, unlock, enable, disable</i>
*/
export default {
	name: 'Selectize',
	inject: ['$validator'],
	props: {
		/**
		* Unique identifier
		*/
		id: {
			default: 'SEL_',
			type: String,
			required: true
		},
		/**
		* Object configuration for the combobox
		*/
		init: Object,
		/**
         * The way in which the component should bind the value
         * @type {String}
         * @default 'value' [value, object, number]
        */
		returnType: {
			type: String,
			default: 'value',
			validate: function (val) {
				return ['value', 'object', 'number'].includes(val)
			}
		},
		title: {
			default: 'Enter value',
			type: String
		},
		// Mobile compatible material style input
		mega: Boolean,
		// Required for order style
		rfo: Boolean,
		// Required for Quote style
		rfq: Boolean,
		// Show aster mark
		aster: Boolean,
		// Boolean flag to indicate editable state or not.
		editable: {
			default: true,
			type: Boolean
		},
		// Error message to be shown when invalid
		message: String,
		multiple: {
			default: false,
			type: Boolean
		},
		options: Array,
		groups: Array,
		// Value binding for the component
		value: {
			default: null
		},
		/**
		* When included will disable searching inside combobox. Very useful when we need to display static options in the dropdown
		*/
		searchable: {
			default: true,
			type: Boolean
		},
		/**
		* Error message to be shown when invalid
		*/
		message: {
			default: '',
			type: String
		},
		placeholder: {
			type: String,
			default: 'Select an option'
		},
		valueKey: {
			type: String,
			default: 'value'
		},
		labelKey: {
			type: String,
			default: 'label'
		},
		disabledKey: {
			type: String,
			default: 'disabled'
		},
		filterKey: String | Array,
		sortKey: String | Array,
		taggable: Boolean | Function,
		allowEmpty: {
			type: Boolean,
			default: true
		},
		disabled: {
			type: Boolean,
			default: false
		},
		maxOptions: {
			type: Number,
			default: 1000
		},
		max: {
			type: Number,
			default: null
		},
		customTemplate: {
			type: Object | Function,
			default: undefined,
			validate: function (val) {
				if (typeof val == 'function') {
					val = val()
				}
				let keys = Object.keys(val)
				return val ? (keys.includes('option') && typeof val.option == 'function') || (keys.includes('item') && typeof val.item == 'function') : true
			}
		}
	},
	$_veeValidate: {
		name() {
			return this.id
		},
		value() {
			return this.value
		}
	},
	computed: {
		divClass() {
			let cls = ['bt-selectize']
			if (this.mega) {
				cls.push('megaform')
			} else {
				cls.push('normal')
			}
			if (this.hasError) {
				cls.push('error')
			}
			return cls.join(' ')
		},
		hasError() {
			return !isEmpty(this.message)
		},
		getOptionLabel() {
			let vals = []
			if (!isEmpty(this.optionLocal) && isArray(this.optionLocal)) {
				this.optionLocal.forEach(el => {
					vals = this.checkOptionLabel(vals, el)
				})
			}
			return vals.join(', ')
		}
	},
	created() {
		this.selInstance = null
		this.valCache = null
	},
	data() {
		return {
			name: this.id,
			internalValue: null,
			editLocal: this.editable,
			optionLocal: []
		}
	},
	methods: {
		prepareComp(optiongroups) {
			requestAnimationFrame(() => {
				if (this.editLocal) {
					let searchSettings = !isEmpty(this.filterKeys) ? (isArray(this.filterKeys) ? union([this.labelKey], this.filterKeys) : union([this.labelKey], this.filterKeys.split(','))) : [this.labelKey]

					let defOpts = {
						options: this.optionLocal,
						optgroups: optiongroups || this.groups,
						items: this.multiple ? this.value : [this.value],
						plugins: ['remove_button'],
						create: this.taggable,
						allowEmptyOptions: this.allowEmpty,
						searchEnabled: this.searchable,
						maxOptions: this.maxOptions,
						maxItems: this.multiple ? this.max : 1,
						valueField: this.valueKey,
						labelField: this.labelKey,
						optgroupField: 'group',
						optgroupValueField: this.valueKey,
						optgroupLabelField: this.labelKey,
						searchConjunction: searchSettings.length > 1 ? 'or' : 'and',
						searchField: searchSettings,
						disabledField: this.disabledKey,
						render: (typeof this.customTemplate == 'function' ? this.customTemplate() : this.customTemplate),
						score: function (search) {
							let score = this.getScoreFunction(search)
							return function (item) {
								return score(item) ? 1 : 0
							}
						}
					}
					if (keys(this.init).length > 0) {
						defOpts = extend(defOpts, this.init)
					}
					if (!this.selInstance && !this.$elx && this.editLocal) {
						this.$elx = $('#' + this.id)
						this.selInstance = this.$elx.selectize(defOpts)[0].selectize
						this.selInstance.on('change', (v) => {
							this.relayValue(v)
						})
					} else if (this.selInstance && this.$elx && this.editLocal) {
						this.selInstance = this.$elx.selectize(defOpts)[0].selectize
						this.setOptions(this.optionLocal)
						this.selInstance.on('change', (v) => {
							this.relayValue(v)
						})
					}
				}
				if (this.selInstance && !this.editLocal) {
					this.selInstance.destroy()
					this.selInstance = null
					this.$elx = null
				}
			})
		},
		setOptions(options, oldOptions) {
			this.optionLocal = this.adjustOptions(shallowCopy(options))
			if (this.selInstance) {
				this.selInstance.clear()
				this.selInstance.clearOptions()
				this.selInstance.addOption(this.optionLocal)
				this.enableControl(!this.disabled)
			}
		},
		adjustOptions(options) {
			if (!isEmpty(options) && !isObject(options[0])) {
				let temp = []
				options.forEach(o => {
					let obj = {}
					obj[this.valueKey] = o
					obj[this.labelKey] = o
					temp.push(obj)
				})
				options = this.sortOptions(temp)
			}
			return this.sortOptions(options)
		},
		sortOptions(opts) {
			let sortK = this.sortKey ? this.sortKey : this.labelKey
			if (!Array.isArray(sortK)) {
				sortK = sortK.contains(',') ? sortK.split(',') : [sortK]
				opts = sortBy(opts, sortK)
			} else if (!isEmpty(sortK)) {
				opts = sortBy(opts, sortK)
			}
			return opts
		},
		setItems(items, oldItems) {
			if (this.selInstance) {
				if (isEmpty(items) && this.returnType.toUpperCase() != 'NUMBER') {
					this.selInstance.clear()
				} else {
					this.selInstance.setValue(clone(items))
				}
			} else {
				this.internalValue = items
			}
		},
		relayValue(v) {
			if (this.selInstance) {
				if (this.returnType == 'number') {
					this.internalValue = this.multiple ? shallowCopy(v.map(Number)) : Number(v)
				} else {
					this.internalValue = v ? shallowCopy(v) : null
				}
				if (this.editLocal) {
					this.$emit('select', this.internalValue)
					this.$emit('input', this.internalValue)
				}
			}
		},
		handler(d) {
			if (this.selInstance) {
				//Change of options task
				if (d.task.toLowerCase() == 'option') {
					this.setOptions(d.val)
					this.setItems(this.value)
				} else if (d.task.toLowerCase() == 'groups') {
					this.prepareComp(d.val)
				} else if (d.task.toLowerCase() == 'item') { //This is for changing the selected values
					this.setItems(d.val)
				} else if (d.task.toLowerCase() == 'control') {
					switch (d.val.toLowerCase()) {
						case 'disable':
							this.enableControl(false)
							break;
						case 'enable':
							this.enableControl(true)
							break;
						case 'lock':
							this.selInstance.lock()
							break;
						case 'unlock':
							this.selInstance.unlock()
							break;
						case 'clear':
							this.selInstance.clear(true)
							break;
					}
				}
			} else {
				if (d.task.toLowerCase() == 'option') {
					this.setItems(this.value)
					this.setOptions(d.val)
				} else if (d.task.toLowerCase() == 'item') { //This is for changing the selected values
					this.internalValue = d.val
				} else if (d.task.toLowerCase() == 'control') {
					switch (d.val.toLowerCase()) {
						case 'disable':
						case 'lock':
							this.editLocal = false
							break;
						case 'enable':
						case 'unlock':
							this.editLocal = true
							break;
					}
				}
			}
		},
		checkOptionLabel(vals, choice) {
			if (isArray(this.value) && this.value.includes(choice[this.valueKey])) {
				vals.push(choice[this.labelKey])
			} else if (this.value == choice[this.valueKey]) {
				vals.push(choice[this.labelKey])
			}
			return vals
		},
		enableControl(f) {
			if (!isNull(this.selInstance)) {
				if (f) {
					this.selInstance.enable()
				} else {
					this.selInstance.disable()
				}
			}
		}
	},
	mounted() {
		this.optionLocal = this.adjustOptions(this.options)
		this.prepareComp()
		this.$events.on(`selectize|${this.id}`, this.handler)
	},
	beforeDestroy() {
		this.$events.off(`selectize|${this.id}`, this.handler)
		if (this.selInstance) {
			this.selInstance.destroy()
			this.selInstance = null
		}
	},
	watch: {
		init: {
			handler: 'prepareComp',
			deep: true
		},
		max() {
			this.prepareComp()
		},
		options(n, o) {
			if (!isEqual(shallowCopy(n), shallowCopy(o))) {
				this.setOptions(n)
			}
		},
		disabled: {
			handler: function (n, o) {
				this.enableControl(!n)
			}
		},
		editLocal: 'prepareComp',
		editable(n, o) {
			this.editLocal = n
		},
		value(n, o) {
			if (!isEqual(shallowCopy(n), shallowCopy(this.internalValue))) {
				if ((this.multiple && isArray(n)) || (!this.multiple && !isArray(n))) {
					this.setItems(n)
				} else if (this.multiple && !isArray(n)) {
					this.setItems([n])
				}
			}
		}
	}
}
</script>
