<!-- Created by henian.xu on 2019/11/8. -->

<template>
    <div
        class="remote-select expand-all"
        v-if="expandAll"
    >
        <div
            v-if="showAllItem"
            :class="['item',{'active':model===''}]"
            @click="onItem('')"
        >
            <div class="label">
                {{ $$t('vmf_all', '全部') }}
            </div>
        </div>
        <div
            :class="['item',{'active':model===item[propsMap.id]}]"
            v-for="item in options"
            :key="item[propsMap.id]"
            @click="onItem(item)"
        >
            <div class="label">
                {{ item[propsMap.name] }}
            </div>
        </div>
    </div>
    <div
        v-else
        :class="classes"
    >
        <el-select
            ref="select"
            popper-class="remote-select-popper"
            :disabled="disabled"
            :placeholder="placeholder"
            :clearable="clearable"
            :multiple="multiple"
            :filterable="filterable"
            reserve-keyword
            :allow-create="!!allowCreate"
            :default-first-option="defaultFirstOption||!!allowCreate"
            :remote="isRemote"
            :remote-method="remoteMethod_"
            :value-key='propsMap.id'
            @visible-change="onVisibleChange"
            @change="onChange"
            v-model="model"
            v-bind="$attrs"
        >
            <!--:remote="isPagination_"-->
            <!--:loading="loading"-->
            <el-option
                v-for="item in options"
                :key="item[propsMap.id]"
                :value="item[propsMap.id]"
                :label="item[propsMap.name]"
                :disabled="item.__disabled"
            />
            <template v-if="pagination && pagination.totalRows">
                <div
                    v-if="completed"
                    class="el-select-dropdown__empty"
                >
                    {{ $$t('vmf_no_more', '没有更多了') }}
                </div>
                <div
                    v-else
                    class="el-select-dropdown__empty"
                >
                    <i class="el-icon-loading" />{{ $$t('vmf_loading', '正在加载') }}...
                </div>
            </template>
        </el-select>

        <template v-if="isOtherSelected">
            <el-input
                class="bor-a pa-ls pa-rs"
                :placeholder="$$t('vmf_placeholder2', '请输入')"
                v-model="otherModel"
            ></el-input>
        </template>
    </div>
</template>

<script>
import emitter from 'element-ui/src/mixins/emitter';
import Qs from 'qs';
import { GlobalVar, registerModule } from 'vmf';
import { mapState } from 'vuex';
import hijackScrollbar from './hijackScrollbar';

registerModule('remoteSelectCache', {
    namespaced: true,
    state: {
        cacheData: {},
    },
    mutations: {
        clearCacheData(state) {
            state.cacheData = {};
        },
    },
});

export default {
    name: 'RemoteSelect',
    mixins: [emitter, hijackScrollbar],
    data() {
        return {
            currentOptions: [], // 缓存选项
            options: [],
            optionsMap: {},
            keyword: '',
            visible: false,
            setCurrentOptions_: this.$utils.debounce(this.setCurrentOptions),
            remoteMethod_: this.$utils.debounce(this.remoteMethod),
            otherModel_: '',
            setOtherModel_: this.$utils.debounce(this.setOtherModel, 500),
        };
    },
    props: {
        isFullValue: {
            type: Boolean,
            default: false,
        },
        // 为 true 时 isFullValue 必需为 true
        enableOther: {
            type: [Number, String],
            default: 0, // 999999
        },
        // enableOther:true 时忽略
        extraOptions: {
            type: Array,
            default: () => [],
        },
        value: {
            type: [Number, String, Array],
            required: true,
        },
        remote: {
            type: String,
            required: true,
        },
        remoteParams: {
            type: Object,
            default() {
                return {};
            },
        },
        data: {
            type: [Array, String],
            default: () => [],
        },
        props: {
            type: Object,
            default: () => ({}),
        },
        initData: {
            type: Array,
            default: () => [],
        },
        // 排除不显示的的 id, 如果包含 value.id 依然可选
        exclude: {
            type: Array,
            default() {
                return [];
            },
        },
        expandAll: {
            type: Boolean,
            default: false,
        },
        showAllItem: {
            type: Boolean,
            default: false,
        },
        // 原生
        placeholder: {
            type: String,
            default() {
                return this.$$t('vmf_placeholder', '请选择');
            },
        },
        multiple: {
            type: Boolean,
            default: false,
        },
        // 原生的为 Boolean 类型,这里用函数是因为需要回调接口去创建对应的选项
        allowCreate: {
            type: Function,
            default: null,
        },
        filterable: {
            type: Boolean,
            default: false,
        },
        defaultFirstOption: {
            type: Boolean,
            default: false,
        },
        clearable: {
            type: Boolean,
            default: false,
        },
        disabled: {
            type: Boolean,
            default: false,
        },
        isPagination: {
            type: Boolean,
            default: false,
        },
        // 禁用用缓存
        disabledCache: {
            type: Boolean,
            default: false,
        },
        // 自动清除无用 value 值
        autoClearUselessValue: {
            type: Boolean,
            default: false,
        },
    },
    computed: {
        ...mapState('remoteSelectCache', ['cacheData']),
        model: {
            get() {
                const { value, propsMap } = this;
                let res = '';
                if (value === null || value === undefined) return value;
                if (this.$utils.isArray(value)) {
                    res = value.reduce((pre, item) => {
                        const v = this.$utils.isObject(item) ? `${item[propsMap.id]}` : `${item}`;
                        if (v) pre.push(v);
                        return pre;
                    }, []);
                } else {
                    res = this.$utils.isObject(value) ? `${value[propsMap.id]}` : `${value}`;
                }
                return res;
            },
            set(val) {
                // console.log(val);
                // this.setCurrentOptions(val); // 交给 watch.value 处理
                this.onAllowCreate(val).then(value => {
                    this.$emit('input', value);
                    this.$emit('change', value);
                    this.dispatch('ElFormItem', 'el.form.change', [value]);
                });
            },
        },
        otherModel: {
            get() {
                return this.otherModel_;
            },
            set(val) {
                this.otherModel_ = val;
                this.setOtherModel_();
            },
        },
        propsMap() {
            const { props } = this;
            return {
                id: 'id',
                name: 'name',
                ...props,
            };
        },
        classes() {
            const { visible, isRemote } = this;
            return ['remote-select', { visible: visible && isRemote }];
        },
        excludeMap() {
            const { model, exclude } = this;
            return exclude.reduce((pre, curr) => {
                pre[curr] = curr !== model;
                return pre;
            }, {});
        },
        isRemote() {
            const { isPagination_, isPagination } = this;
            // 初始为真是为了当有第二页的默认值时首次展开不会展示默认值
            return isPagination || isPagination_ || isPagination_ === null;
        },
        dictionary() {
            const { data } = this;
            if (!this.$utils.isString(data)) return '';
            if (!/\[DICTIONARY\]/.test(data)) return '';
            const [, dic] = data.split(':');
            const apiUrl = GlobalVar.appConfig.DICTIONARY_API || '/rs/dic/entry/';
            return `${apiUrl}${dic}`;
        },
        remoteUrl() {
            const { remote, dictionary } = this;
            return remote || dictionary || '';
        },
        remoteParamsStr() {
            const { remoteUrl, remoteParams } = this;
            return remoteUrl + Qs.stringify(remoteParams);
        },
        isOtherSelected() {
            const { enableOther, model } = this;
            if (!enableOther) return false;
            if (this.$utils.isArray(model)) {
                return model.some(item => `${item}` === `${enableOther}`);
            }
            return `${model}` === `${enableOther}`;
        },
    },
    watch: {
        // TODO 整合 value 与 initData
        value: {
            handler(val) {
                this.setCurrentOptions_();
                if (val) {
                    const { propsMap, enableOther } = this;
                    const id = val[propsMap.id];
                    if (id && `${id}` === `${enableOther}`) {
                        this.otherModel_ = val[propsMap.name];
                    }
                }
            },
            immediate: true,
        },
        initData: {
            handler(val) {
                if (!val || !val.length) return;
                this.setCurrentOptions_();
            },
            immediate: true,
            deep: true,
        },
        remoteParamsStr() {
            this.init();
        },
        model: {
            async handler(val) {
                if (!val || !val.length) return;
                const { autoClearUselessValue, propsMap } = this;
                if (!autoClearUselessValue) return;
                const ids = [].concat(val);
                const items = await this.getItem(ids);

                if (ids.length !== items.length) {
                    let value = '';
                    if (Array.isArray(val)) {
                        value = items.reduce((pre, cur) => {
                            const id = (cur || {})[propsMap.id];
                            if (id) pre.push(id);
                            return pre;
                        }, []);
                    }
                    this.model = value;
                }
            },
            immediate: true,
        },
    },
    methods: {
        // 添加选项与选项索引
        buildOptions(list, clear = false) {
            const { optionsMap, propsMap, options, extraOptions, excludeMap } = this;
            const target = clear ? [] : options;
            if (!target.length) target.push(...extraOptions);

            const currentLength = target.length;
            const map = list.reduce((pre, curr, index) => {
                curr[propsMap.id] += '';
                const id = curr[propsMap.id];
                const oldOption = pre[id];
                curr.__disabled = excludeMap[id];
                if (oldOption) {
                    const { __index } = oldOption;
                    curr.__index = __index;
                    target.splice(__index, 1, curr);
                } else {
                    curr.__index = currentLength + index;
                    target.push(curr);
                }
                pre[id] = curr;
                return pre;
            }, !clear ? optionsMap : {});
            if (clear) {
                this.options = target;
                this.optionsMap = map;
            }
        },
        getItemByCache(ids) {
            const { currentOptions, initData, propsMap, remoteUrl, cacheData } = this;
            const currentCacheData = cacheData[remoteUrl];
            const res = { options: [], ids: [...ids] };
            const temOptions = [...currentOptions, ...initData];
            if (currentCacheData) {
                Object.values(currentCacheData).forEach(item => temOptions.push(...item.data));
            }
            if (!temOptions || !temOptions.length) return res;
            ids = Array.isArray(ids) ? ids : [ids];
            const len = ids.length;
            const temLen = temOptions.length;
            for (let i = 0; i < temLen; i += 1) {
                const option = temOptions[i];
                const optionId = option[propsMap.id];
                const bool = res.ids.some((id, index) => {
                    const b = `${optionId}` === `${id}`;
                    if (b) res.ids.splice(index, 1);
                    return b;
                });
                if (bool) {
                    const n = res.options.push(option);
                    if (n === len) break;
                }
            }
            return res;
        },
        async getItem(ids) {
            ids = Array.isArray(ids) ? ids : [ids];
            const cacheOptions = this.getItemByCache(ids);
            if (cacheOptions && cacheOptions.ids && cacheOptions.ids.length) {
                // 未取全
                const res = await this.getDataList('', cacheOptions.ids);
                const temIds = cacheOptions.ids.map(n => `${n}`);
                (res.data || []).forEach(item => {
                    const id = item[this.propsMap.id];
                    if (id && temIds.includes(`${id}`)) cacheOptions.options.push(item);
                });
            }
            return cacheOptions.options;
        },
        async getDataList(keyword = this.keyword, ids = []) {
            const { remoteUrl, remoteParams, pagination, propsMap, disabledCache, cacheData } = this;
            const params = {
                ...remoteParams,
                keyword,
                currentPage: pagination && pagination.currentPage ? pagination.currentPage + 1 : 1,
            };
            if (ids && ids.length) {
                params[propsMap.id] = ids.join(',');
                params.currentPage = 1;
                params.pageSize = 1000;
            }

            let result;
            let cacheKey;
            const isCache = !disabledCache && !keyword && (!ids || !ids.length) && !!remoteUrl;
            if (!cacheData[remoteUrl]) cacheData[remoteUrl] = {};
            const currentCacheData = cacheData[remoteUrl];
            if (isCache) {
                cacheKey = Qs.stringify(params);
                result = currentCacheData[cacheKey];
            }
            if (!result) {
                // 未命中缓存，请求接口
                if (!remoteUrl) {
                    result = {
                        data: this.data,
                    };
                } else {
                    result = await this.$http.get(remoteUrl, params, {
                        isRepeat: true,
                        isAxiosResponseData: true,
                    });
                }
                if (isCache) {
                    currentCacheData[cacheKey] = JSON.parse(JSON.stringify(result));
                }
            }

            // this.buildOptions(result.data); // 移到 onLoad 中去
            return result;
        },
        onVisibleChange(visible) {
            this.visible = visible;
            const { isRemote, disabledCache } = this;
            if (!disabledCache && !isRemote) return;
            if (visible) {
                this.refreshData();
            } else {
                setTimeout(() => {
                    this.buildOptions(this.currentOptions, true);
                }, 50);
            }
        },
        async createValue(value) {
            const { keyword, allowCreate, propsMap, remoteUrl, enableOther, otherModel, isFullValue, cacheData } = this;
            const val = this.$utils.isDef(value[propsMap.id]) ? value[propsMap.id] : value;
            const name = this.$utils.isDef(value[propsMap.name]) ? value[propsMap.name] : '';
            if (!allowCreate || !keyword || !val || `${keyword}` !== `${val}`) {
                if (!isFullValue) return val;
                let res = null;
                if (!name && val) {
                    [res] = await this.getItem(val);
                } else {
                    res = value;
                }
                if (res) {
                    const id = res[propsMap.id];
                    return {
                        [propsMap.id]: id,
                        [propsMap.name]: `${id}` === `${enableOther}` ? otherModel : res[propsMap.name],
                    };
                }
                return '';
            }
            const resultId = await allowCreate(val);
            const res = {
                [propsMap.id]: resultId,
                [propsMap.name]: val,
            };
            this.buildOptions([res]);
            // 因为创建了新的选项，所以应该清空当前的列表缓存
            delete cacheData[remoteUrl];
            return isFullValue ? res : resultId;
        },
        async onAllowCreate(value) {
            if (this.$utils.isArray(value)) {
                return value.reduce(async (pre, cur) => {
                    const res = await pre.then();
                    try {
                        const val = await this.createValue(cur);
                        res.push(val);
                    } catch (e) {
                        console.error(e);
                    }
                    return res;
                }, Promise.resolve([]));
            }
            return this.createValue(value);
        },
        async setOtherModel() {
            const { isFullValue, propsMap, otherModel_, enableOther } = this;
            let [item] = await this.getItem(enableOther);
            if (isFullValue) {
                if (!item) {
                    item = {
                        [propsMap.id]: enableOther,
                        [propsMap.name]: otherModel_,
                    };
                }
                this.onItem({
                    ...item,
                    [propsMap.name]: otherModel_,
                });
            }
        },
        onItem(item) {
            const { propsMap, model, enableOther } = this;
            const val = item[propsMap.id];
            if (`${enableOther}` !== `${val}` && this.$utils.isArray(model) && model.some(v => `${v}` === `${val}`))
                return;
            if (`${enableOther}` !== `${val}` && `${model}` === `${val}`) return;
            this.model = item;
        },
        onChange(/* val */) {
            // noop
        },
        // 远程加载
        remoteMethod(keyword) {
            // console.log('remoteMethod');
            this.refreshData({ keyword });
        },
        async setCurrentOptions(val = this.model) {
            const valFalse = val === undefined || val === null || val === '';
            const value = valFalse || Array.isArray(val) ? val : [val];
            // console.log('setCurrentOptions', val, value);
            if (!value || !value.length) {
                this.currentOptions = [];
                return;
            }

            this.currentOptions = await this.getItem(value);

            if (this.visible || !this.isRemote) return;

            this.buildOptions(this.currentOptions, true);
        },
        // 刷新所有数据
        refreshData({ keyword, clearCache } = {}) {
            // console.log('refreshData');
            if (clearCache) {
                const { remoteUrl, cacheData } = this;
                delete cacheData[remoteUrl];
            }
            this.keyword = keyword;
            this.pagination = {};
            this.options = [];
            this.optionsMap = {};
            this.onLoad();
        },
        // 对外暴露方法
        getData(id) {
            return this.optionsMap[id];
        },
        init() {
            // console.log(!this.expandAll, this.model, this.isPagination);
            if (!this.expandAll && this.model && this.isPagination) return;
            this.refreshData();
        },
    },
    mounted() {
        this.init();
    },
};
</script>

<style lang="scss">
.remote-select-popper {
    max-width: 480px;
    overflow: auto;
}
.remote-select {
    display: inline-block;
    > .el-select {
        > .el-input--suffix {
            > .el-input__suffix {
                > .el-input__suffix-inner {
                    > .el-input__icon.el-icon- {
                        &:before {
                            content: '\e6e1';
                        }
                    }
                }
            }
        }
        > .el-select__tags {
            .el-tag {
                position: relative;
                > .el-select__tags-text {
                    margin-right: $margin-big;
                    max-width: 300px;
                    @include text-line(1);
                }
                > .el-tag__close {
                    position: absolute;
                    right: 0;
                    top: 50%;
                    margin-top: -8px;
                }
            }
        }
    }

    &.visible {
        > .el-select {
            > .el-input--suffix {
                > .el-input__suffix {
                    > .el-input__suffix-inner {
                        > .el-input__icon.el-icon- {
                            transform: rotateZ(0);
                        }
                    }
                }
            }
        }
    }

    &.expand-all {
        display: flex;
        flex-direction: row;
        justify-content: flex-start;
        align-items: center;
        > .item {
            cursor: pointer;
            + .item {
                > .label {
                    border-left: 1px solid $color-border;
                }
            }
            > .label {
                display: inline-block;
                padding: 0 $padding-small;
                line-height: 1.5;
                border-radius: 5px;
            }
            &.active {
                > .label {
                    background-color: $color-primary;
                    color: #fff;
                }
            }
        }
    }
}
</style>
