<script lang="ts">
import { isArray, isString } from 'lodash-es'
import type { PropType, VNode } from 'vue'
import { computed, createVNode, defineComponent, reactive, resolveComponent } from 'vue'
import { getArea } from '/@/api/common'
import type { InputAttr, InputData, ModelValueTypes } from '/@/components/baInput'
import { inputTypes } from '/@/components/baInput'
import Array from '/@/components/baInput/components/array.vue'
import BaUpload from '/@/components/baInput/components/baUpload.vue'
import Editor from '/@/components/baInput/components/editor.vue'
import IconSelector from '/@/components/baInput/components/iconSelector.vue'
import RemoteSelect from '/@/components/baInput/components/remoteSelect.vue'

export default defineComponent({
    name: 'baInput',
    props: {
        // 输入框类型,支持的输入框见 inputTypes
        type: {
            type: String,
            required: true,
            validator: (value: string) => {
                return inputTypes.includes(value)
            },
        },
        // 双向绑定值
        modelValue: {
            type: null,
            required: true,
        },
        // 输入框的附加属性
        attr: {
            type: Object as PropType<InputAttr>,
            default: () => {},
        },
        // 额外数据,radio、checkbox的选项等数据
        data: {
            type: Object as PropType<InputData>,
            default: () => {},
        },
    },
    emits: ['update:modelValue'],
    setup(props, { emit, slots }) {
        // 合并 props.attr 和 props.data
        const attrs = computed(() => {
            return { ...props.attr, ...props.data }
        })

        // 通用值更新函数
        const onValueUpdate = (value: ModelValueTypes) => {
            emit('update:modelValue', value)
        }

        // 基础用法 string textarea password
        const bases = () => {
            return () =>
                createVNode(
                    resolveComponent('el-input'),
                    {
                        type: props.type == 'string' ? 'text' : props.type,
                        ...attrs.value,
                        modelValue: props.modelValue,
                        'onUpdate:modelValue': onValueUpdate,
                    },
                    slots
                )
        }
        // radio checkbox
        const rc = () => {
            if (!attrs.value.content) {
                console.warn('请传递 ' + props.type + ' 的 content')
            }

            const vNodes = computed(() => {
                const vNode: VNode[] = []
                const contentIsArray = isArray(attrs.value.content)
                const type = attrs.value.button ? props.type + '-button' : props.type
                for (const key in attrs.value.content) {
                    let nodeProps = {}
                    if (contentIsArray) {
                        if (typeof attrs.value.content[key].value == 'number') {
                            console.warn(props.type + ' 的 content.value 不能是数字')
                        }

                        nodeProps = {
                            ...attrs.value.content[key],
                            border: attrs.value.border ? attrs.value.border : false,
                            ...(attrs.value.childrenAttr || {}),
                        }
                    } else {
                        nodeProps = {
                            value: key,
                            label: attrs.value.content[key],
                            border: attrs.value.border ? attrs.value.border : false,
                            ...(attrs.value.childrenAttr || {}),
                        }
                    }
                    vNode.push(createVNode(resolveComponent('el-' + type), nodeProps, slots))
                }
                return vNode
            })

            return () => {
                const valueComputed = computed(() => {
                    if (props.type == 'radio') {
                        if (props.modelValue == undefined) return ''
                        return '' + props.modelValue
                    } else {
                        let modelValueArr: anyObj = []
                        for (const key in props.modelValue) {
                            modelValueArr[key] = '' + props.modelValue[key]
                        }
                        return modelValueArr
                    }
                })
                return createVNode(
                    resolveComponent('el-' + props.type + '-group'),
                    {
                        ...attrs.value,
                        modelValue: valueComputed.value,
                        'onUpdate:modelValue': onValueUpdate,
                    },
                    () => vNodes.value
                )
            }
        }
        // select selects
        const select = () => {
            if (!attrs.value.content) {
                console.warn('请传递 ' + props.type + '的 content')
            }

            const vNodes = computed(() => {
                const vNode: VNode[] = []
                for (const key in attrs.value.content) {
                    vNode.push(
                        createVNode(
                            resolveComponent('el-option'),
                            {
                                key: key,
                                label: attrs.value.content[key],
                                value: key,
                                ...(attrs.value.childrenAttr || {}),
                            },
                            slots
                        )
                    )
                }
                return vNode
            })

            return () => {
                const valueComputed = computed(() => {
                    if (props.type == 'select') {
                        if (props.modelValue == undefined) return ''
                        return '' + props.modelValue
                    } else {
                        let modelValueArr: anyObj = []
                        for (const key in props.modelValue) {
                            modelValueArr[key] = '' + props.modelValue[key]
                        }
                        return modelValueArr
                    }
                })
                return createVNode(
                    resolveComponent('el-select'),
                    {
                        class: 'w100',
                        multiple: props.type == 'select' ? false : true,
                        clearable: true,
                        ...attrs.value,
                        modelValue: valueComputed.value,
                        'onUpdate:modelValue': onValueUpdate,
                    },
                    () => vNodes.value
                )
            }
        }
        // datetime
        const datetime = () => {
            let valueFormat = 'YYYY-MM-DD HH:mm:ss'
            switch (props.type) {
                case 'date':
                    valueFormat = 'YYYY-MM-DD'
                    break
                case 'year':
                    valueFormat = 'YYYY'
                    break
            }
            return () =>
                createVNode(
                    resolveComponent('el-date-picker'),
                    {
                        class: 'w100',
                        type: props.type,
                        'value-format': valueFormat,
                        ...attrs.value,
                        modelValue: props.modelValue,
                        'onUpdate:modelValue': onValueUpdate,
                    },
                    slots
                )
        }
        // upload
        const upload = () => {
            return () =>
                createVNode(
                    BaUpload,
                    {
                        type: props.type,
                        modelValue: props.modelValue,
                        'onUpdate:modelValue': onValueUpdate,
                        ...attrs.value,
                    },
                    slots
                )
        }

        // remoteSelect remoteSelects
        const remoteSelect = () => {
            return () =>
                createVNode(
                    RemoteSelect,
                    {
                        modelValue: props.modelValue,
                        'onUpdate:modelValue': onValueUpdate,
                        multiple: props.type == 'remoteSelect' ? false : true,
                        ...attrs.value,
                    },
                    slots
                )
        }

        const buildFun = new Map([
            ['string', bases],
            [
                'number',
                () => {
                    return () =>
                        createVNode(
                            resolveComponent('el-input-number'),
                            {
                                class: 'w100',
                                'controls-position': 'right',
                                ...attrs.value,
                                modelValue: isString(props.modelValue) ? Number(props.modelValue) : props.modelValue,
                                'onUpdate:modelValue': onValueUpdate,
                            },
                            slots
                        )
                },
            ],
            ['textarea', bases],
            ['password', bases],
            ['radio', rc],
            ['checkbox', rc],
            [
                'switch',
                () => {
                    // 值类型:string,number,boolean,custom
                    const valueType = computed(() => {
                        if (typeof attrs.value.activeValue !== 'undefined' && typeof attrs.value.inactiveValue !== 'undefined') {
                            return 'custom'
                        }
                        return typeof props.modelValue
                    })

                    // 要传递给 el-switch 组件的绑定值，该组件对传入值有限制，先做处理
                    const valueComputed = computed(() => {
                        if (valueType.value === 'boolean' || valueType.value === 'custom') {
                            return props.modelValue
                        } else {
                            let valueTmp = parseInt(props.modelValue as string)
                            return isNaN(valueTmp) || valueTmp <= 0 ? false : true
                        }
                    })
                    return () =>
                        createVNode(
                            resolveComponent('el-switch'),
                            {
                                ...attrs.value,
                                modelValue: valueComputed.value,
                                'onUpdate:modelValue': (value: boolean) => {
                                    let newValue: boolean | string | number = value
                                    switch (valueType.value) {
                                        case 'string':
                                            newValue = value ? '1' : '0'
                                            break
                                        case 'number':
                                            newValue = value ? 1 : 0
                                    }
                                    emit('update:modelValue', newValue)
                                },
                            },
                            slots
                        )
                },
            ],
            ['datetime', datetime],
            [
                'year',
                () => {
                    return () => {
                        const valueComputed = computed(() => (!props.modelValue ? null : '' + props.modelValue))
                        return createVNode(
                            resolveComponent('el-date-picker'),
                            {
                                class: 'w100',
                                type: props.type,
                                'value-format': 'YYYY',
                                ...attrs.value,
                                modelValue: valueComputed.value,
                                'onUpdate:modelValue': onValueUpdate,
                            },
                            slots
                        )
                    }
                },
            ],
            ['date', datetime],
            [
                'time',
                () => {
                    return () =>
                        createVNode(
                            resolveComponent('el-time-picker'),
                            {
                                class: 'w100',
                                clearable: true,
                                format: 'HH:mm:ss',
                                valueFormat: 'HH:mm:ss',
                                ...attrs.value,
                                modelValue: props.modelValue,
                                'onUpdate:modelValue': onValueUpdate,
                            },
                            slots
                        )
                },
            ],
            ['select', select],
            ['selects', select],
            [
                'array',
                () => {
                    return () =>
                        createVNode(
                            Array,
                            {
                                modelValue: props.modelValue,
                                'onUpdate:modelValue': onValueUpdate,
                                ...attrs.value,
                            },
                            slots
                        )
                },
            ],
            ['remoteSelect', remoteSelect],
            ['remoteSelects', remoteSelect],
            [
                'city',
                () => {
                    type Node = { value?: number; label?: string; leaf?: boolean }
                    let maxLevel = attrs.value.level ? attrs.value.level - 1 : 2
                    const lastLazyValue: {
                        value: string | number[] | unknown
                        nodes: Node[]
                        key: string
                        currentRequest: any
                    } = reactive({
                        value: 'ready',
                        nodes: [],
                        key: '',
                        currentRequest: null,
                    })

                    // 请求到的node备份-s
                    let nodeEbak: anyObj = {}
                    const getNodes = (level: number, key: string) => {
                        if (nodeEbak[level] && nodeEbak[level][key]) {
                            return nodeEbak[level][key]
                        }
                        return false
                    }
                    const setNodes = (level: number, key: string, nodes: Node[] = []) => {
                        if (!nodeEbak[level]) {
                            nodeEbak[level] = {}
                        }
                        nodeEbak[level][key] = nodes
                    }
                    // 请求到的node备份-e

                    return () =>
                        createVNode(
                            resolveComponent('el-cascader'),
                            {
                                modelValue: props.modelValue,
                                'onUpdate:modelValue': onValueUpdate,
                                class: 'w100',
                                clearable: true,
                                // city 数据使用 varchar 存储，所以清空时使用 empty string 而不是 null
                                valueOnClear: '',
                                props: {
                                    lazy: true,
                                    lazyLoad(node: any, resolve: any) {
                                        // lazyLoad会频繁触发,在本地存储请求结果,供重复触发时直接读取
                                        const { level, pathValues } = node
                                        let key = pathValues.join(',')
                                        key = key ? key : 'init'

                                        let locaNode = getNodes(level, key)
                                        if (locaNode) {
                                            return resolve(locaNode)
                                        }

                                        if (lastLazyValue.key == key && lastLazyValue.value == props.modelValue) {
                                            if (lastLazyValue.currentRequest) {
                                                return lastLazyValue.currentRequest
                                            }
                                            return resolve(lastLazyValue.nodes)
                                        }

                                        let nodes: Node[] = []
                                        lastLazyValue.key = key
                                        lastLazyValue.value = props.modelValue
                                        lastLazyValue.currentRequest = getArea(pathValues).then((res) => {
                                            let toStr = false
                                            if (props.modelValue && typeof (props.modelValue as anyObj)[0] === 'string') {
                                                toStr = true
                                            }
                                            for (const key in res.data) {
                                                if (toStr) {
                                                    res.data[key].value = res.data[key].value.toString()
                                                }
                                                res.data[key].leaf = level >= maxLevel
                                                nodes.push(res.data[key])
                                            }
                                            lastLazyValue.nodes = nodes
                                            lastLazyValue.currentRequest = null
                                            setNodes(level, key, nodes)
                                            resolve(nodes)
                                        })
                                    },
                                },
                                ...attrs.value,
                            },
                            slots
                        )
                },
            ],
            ['image', upload],
            ['images', upload],
            ['file', upload],
            ['files', upload],
            [
                'icon',
                () => {
                    return () =>
                        createVNode(
                            IconSelector,
                            {
                                modelValue: props.modelValue,
                                'onUpdate:modelValue': onValueUpdate,
                                ...attrs.value,
                            },
                            slots
                        )
                },
            ],
            [
                'color',
                () => {
                    return () =>
                        createVNode(
                            resolveComponent('el-color-picker'),
                            {
                                modelValue: props.modelValue,
                                'onUpdate:modelValue': (newValue: string | null) => {
                                    // color 数据使用 varchar 存储，点击清空时的 null 值使用 empty string 代替
                                    emit('update:modelValue', newValue === null ? '' : newValue)
                                },
                                ...attrs.value,
                            },
                            slots
                        )
                },
            ],
            [
                'editor',
                () => {
                    return () =>
                        createVNode(
                            Editor,
                            {
                                class: 'w100',
                                modelValue: props.modelValue,
                                'onUpdate:modelValue': onValueUpdate,
                                ...attrs.value,
                            },
                            slots
                        )
                },
            ],
            [
                'default',
                () => {
                    console.warn('暂不支持' + props.type + '的输入框类型，你可以自行在 BaInput 组件内添加逻辑')
                },
            ],
        ])

        let action = buildFun.get(props.type) || buildFun.get('default')
        return action!.call(this)
    },
})
</script>

<style scoped lang="scss">
.ba-upload-image :deep(.el-upload--picture-card) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ba-upload-file :deep(.el-upload-list) {
    margin-left: -10px;
}
</style>
