<!-- Created by henian.xu on 2018/8/2. -->

<template>
    <el-cascader
        ref="cascader"
        :filterable="filterable"
        :props="props_"
        @change="onItemChange"
        @expand-change="onItemExpandChange"
        @visible-change="onVisibleChange"
        :before-filter="onBeforeFilter"
        :filter-method="onFilterMethod"
        :clearable="clearable"
        :disabled="isDisabled"
        :placeholder="placeholder"
        :show-all-levels="isShowAllLevels"
        v-bind="$attrs"
        v-on="$listeners"
        v-model="model"
    />
</template>

<script>
export default {
    name: 'CascaderPicker',
    inject: {
        elForm: {
            default: '',
        },
        elFormItem: {
            default: '',
        },
    },
    data() {
        return {
            model: [],
            props: {
                value: 'id',
                label: 'name',
                leaf: 'isLeaf',
                children: 'children',
                disabled: 'disabled',
                lazy: true,
                lazyLoad: this.lazyLoad,
            },
            innerCtrl: false,
            cancelSource: null,
            showAllLevels_: null,
        };
    },
    props: {
        value: {
            type: [Number, String],
            default: '',
        },
        /**
         * api 类，必需实现 select方法
         */
        apiClass: {
            // type: Object,
            default: null,
            validator(val) {
                return typeof val === 'object';
            },
        },
        selectApi: {
            type: String,
            default: '',
        },
        loadSelectNodeApi: {
            type: String,
            default: '',
        },
        queryParams: {
            type: Object,
            default: () => ({}),
        },
        minLv: {
            type: Number,
            default: 1,
        },
        pId: {
            type: Number,
            default: 0,
        },
        // element-ui 已弃用此属性，用 props.checkStrictly 替代
        changeOnSelect: {
            type: Boolean,
            default: false,
        },
        checkStrictly: {
            type: Boolean,
            default: null,
        },
        filterable: {
            type: Boolean,
            default: false,
        },
        clearable: {
            type: Boolean,
            default: true,
        },
        disabled: {
            type: Boolean,
            default: false,
        },
        placeholder: {
            type: String,
            default() {
                return this.$$t('vmf_placeholder', '请选择');
            },
        },
        showAllLevels: {
            type: Boolean,
            default: true,
        },
    },
    computed: {
        props_() {
            const { props, checkStrictly, changeOnSelect } = this;
            return {
                ...props,
                checkStrictly: checkStrictly != null ? checkStrictly : changeOnSelect,
            };
        },
        apiSelect() {
            const { selectApi, apiClass } = this;
            return data => {
                this.createCancelToken();
                if (apiClass) {
                    return apiClass.select(data, {
                        cancelSource: this.cancelSource,
                        isAxiosResponseData: true,
                    });
                }
                return this.$http.get(selectApi, data, {
                    cancelSource: this.cancelSource,
                    isAxiosResponseData: true,
                    isRepeat: true,
                });
            };
        },
        apiLoadSelectNode() {
            const { loadSelectNodeApi, apiClass } = this;
            return data => {
                this.createCancelToken();
                if (apiClass) {
                    return apiClass.loadSelectNode(data, {
                        cancelSource: this.cancelSource,
                        isAxiosResponseData: true,
                    });
                }
                return this.$http.get(loadSelectNodeApi, data, {
                    cancelToken: this.cancelSource,
                    isAxiosResponseData: true,
                    isRepeat: true,
                });
            };
        },
        isDisabled() {
            return this.disabled || (this.elForm || {}).disabled;
        },
        $$cascader() {
            return this.$refs.cascader;
        },
        $$cascaderPanel() {
            return this.$$cascader.$refs.panel;
        },
        panelStore() {
            return this.$$cascaderPanel.store;
        },
        isShowAllLevels() {
            return this.showAllLevels_ !== null ? this.showAllLevels_ : this.showAllLevels;
        },
    },
    watch: {
        value: {
            handler(val) {
                if (this.innerCtrl) {
                    this.innerCtrl = false;
                    return;
                }
                this.init(val);
            },
            immediate: true,
        },
        queryParams() {
            this.init(this.value);
        },
    },
    methods: {
        async init(val) {
            if (!val) {
                this.model = [];
                return;
            }
            const data = await this.getLoadSelectNode({ id: val });
            data.forEach(item => this.appendNode(item));
            this.upgradeModel(val);
        },
        upgradeModel(value) {
            value += '';
            this.$nextTick(() => {
                const node = this.panelStore.getNodeByValue(value);
                if (!node) return;
                const list = [...node.getPath()];
                this.model = list;
                list.forEach(val => {
                    const n = this.panelStore.getNodeByValue(val);
                    this.$$cascaderPanel.handleExpand(n, true);
                });
            });
        },
        appendNode(nodeData) {
            const node = this.panelStore.getNodeByValue(nodeData.id);
            if (!node) {
                this.panelStore.appendNode(nodeData, null);
            } else if (node.loaded) {
                nodeData.children.filter(item => item.children && item.children.length).forEach(item => {
                    this.appendNode(item);
                });
            } else if (nodeData.children && nodeData.children.length) {
                if (Array.isArray(node.children)) node.children.splice(0, node.children.length);
                this.panelStore.appendNodes(nodeData.children, node);
                node.loading = false;
                node.loaded = true;
            }
        },
        async lazyLoad(node, resolve) {
            const { pId, value } = this;
            const { value: val, root, children } = node;
            if (children && children.length) return resolve();
            let data;
            if (root && value) {
                // 初始有 value 交给 watch.value 处理
                // data = await this.getLoadSelectNode({ id: value });
                // this.upgradeModel(value);
            } else {
                data = await this.getChildren({ pId: val || pId }).catch(() => {
                    // noop
                });
            }
            return resolve(data);
        },
        createCancelToken() {
            if (this.cancelSource) this.cancelSource.cancel({ msg: '取消子查询' });
            this.cancelSource = this.$http.createCancelToken();
            return this.cancelSource;
        },
        // 可以合并到 Convert.listToTree 方法里
        buildData(data) {
            data.forEach(item => {
                item.id += '';
                item.children = item.children || (item.isLeaf ? null : []);
            });
        },
        async getLoadSelectNode(params) {
            const { queryParams } = this;
            const { data } = await this.apiLoadSelectNode({ ...queryParams, ...params });
            this.buildData(data);
            return this.$utils.Convert.listToTree(data, this.lv);
        },
        async getChildren(params) {
            const { queryParams } = this;
            const { data } = await this.apiSelect({ ...queryParams, ...params });
            this.buildData(data);
            this.$utils.Convert.sortAtOrderNum(data);
            return data;
        },
        onItemChange(val) {
            this.innerCtrl = true;
            this.$emit('input', `${[...val].pop() || ''}`);
            this.$emit('change', val);
        },
        onItemExpandChange(val) {
            this.$emit('expand-change', val);
        },
        onVisibleChange(val) {
            if (!val) this.showAllLevels_ = null;
            this.$emit('visible-change', val);
        },
        async onBeforeFilter(val) {
            if (val) {
                const data = await this.getLoadSelectNode({ keywords: val });
                data.forEach(item => this.appendNode(item));
                this.showAllLevels_ = true;
            }
        },
        onFilterMethod(node, keyword) {
            const { text } = node;
            const texts = text.replace(/\s*\/\s*/g, '');
            let includes = text.includes(keyword);
            if (!includes) {
                includes = texts.includes(keyword);
            }
            return includes;
        },
    },
};
</script>

<style lang="scss">
</style>
