import type { FormInstance, TableColumnCtx } from 'element-plus'
import { ElNotification, dayjs } from 'element-plus'
import { cloneDeep, isArray, isEmpty } from 'lodash-es'
import Sortable from 'sortablejs'
import { reactive } from 'vue'
import { useRoute } from 'vue-router'
import type { baTableApi } from '/@/api/common'
import { findIndexRow } from '/@/components/table'
import { i18n } from '/@/lang/index'
import { auth, getArrayKey } from '/@/utils/common'

/**
 * 表格管家类
 */
export default class baTable {
    /** baTableApi 类的实例，开发者可重写该类 */
    public api: baTableApi

    /** 表格状态，属性对应含义请查阅 BaTable 的类型定义 */
    public table: BaTable = reactive({
        ref: undefined,
        pk: 'id',
        data: [],
        remark: null,
        loading: false,
        selection: [],
        column: [],
        total: 0,
        filter: {},
        dragSortLimitField: 'pid',
        acceptQuery: true,
        showComSearch: false,
        dblClickNotEditColumn: [undefined],
        expandAll: false,
        extend: {},
    })

    /** 表单状态，属性对应含义请查阅 BaTableForm 的类型定义 */
    public form: BaTableForm = reactive({
        ref: undefined,
        labelWidth: 160,
        operate: '',
        operateIds: [],
        items: {},
        submitLoading: false,
        defaultItems: {},
        loading: false,
        extend: {},
    })

    /** BaTable 前置处理函数列表（前置埋点） */
    public before: BaTableBefore

    /** BaTable 后置处理函数列表（后置埋点） */
    public after: BaTableAfter

    /** 公共搜索数据 */
    public comSearch: ComSearch = reactive({
        form: {},
        fieldData: new Map(),
    })

    constructor(api: baTableApi, table: BaTable, form: BaTableForm = {}, before: BaTableBefore = {}, after: BaTableAfter = {}) {
        this.api = api
        this.form = Object.assign(this.form, form)
        this.table = Object.assign(this.table, table)
        this.before = before
        this.after = after
    }

    /**
     * 表格内部鉴权方法
     * 此方法在表头或表行组件内部自动调用，传递权限节点名，如：add、edit
     * 若需自定义表格内部鉴权，重写此方法即可
     */
    auth(node: string) {
        return auth(node)
    }

    /**
     * 运行前置函数
     * @param funName 函数名
     * @param args 参数
     */
    runBefore(funName: string, args: any = {}) {
        if (this.before && this.before[funName] && typeof this.before[funName] == 'function') {
            return this.before[funName]!({ ...args }) === false ? false : true
        }
        return true
    }

    /**
     * 运行后置函数
     * @param funName 函数名
     * @param args 参数
     */
    runAfter(funName: string, args: any = {}) {
        if (this.after && this.after[funName] && typeof this.after[funName] == 'function') {
            return this.after[funName]!({ ...args }) === false ? false : true
        }
        return true
    }

    /**
     * 表格数据获取（请求表格对应控制器的查看方法）
     * @alias getIndex
     */
    getData = () => {
        if (this.runBefore('getData') === false) return
        if (this.runBefore('getIndex') === false) return
        this.table.loading = true
        return this.api
            .index(this.table.filter)
            .then((res) => {
                this.table.data = res.data.list
                this.table.total = res.data.total
                this.table.remark = res.data.remark
                this.runAfter('getData', { res })
                this.runAfter('getIndex', { res })
            })
            .catch((err) => {
                this.runAfter('getData', { err })
                this.runAfter('getIndex', { err })
            })
            .finally(() => {
                this.table.loading = false
            })
    }

    /**
     * 删除数据
     */
    postDel = (ids: string[]) => {
        if (this.runBefore('postDel', { ids }) === false) return
        this.api.del(ids).then((res) => {
            this.onTableHeaderAction('refresh', { event: 'delete', ids })
            this.runAfter('postDel', { res })
        })
    }

    /**
     * 获取被编辑行数据
     * @alias requestEdit
     */
    getEditData = (id: string) => {
        if (this.runBefore('getEditData', { id }) === false) return
        if (this.runBefore('requestEdit', { id }) === false) return
        this.form.loading = true
        this.form.items = {}
        return this.api
            .edit({
                [this.table.pk!]: id,
            })
            .then((res) => {
                this.form.items = res.data.row
                this.runAfter('getEditData', { res })
                this.runAfter('requestEdit', { res })
            })
            .catch((err) => {
                this.toggleForm()
                this.runAfter('getEditData', { err })
                this.runAfter('requestEdit', { err })
            })
            .finally(() => {
                this.form.loading = false
            })
    }

    /**
     * 双击表格
     * @param row 行数据
     * @param column 列上下文数据
     */
    onTableDblclick = (row: TableRow, column: TableColumnCtx<TableRow>) => {
        if (!this.table.dblClickNotEditColumn!.includes('all') && !this.table.dblClickNotEditColumn!.includes(column.property)) {
            if (this.runBefore('onTableDblclick', { row, column }) === false) return
            this.toggleForm('Edit', [row[this.table.pk!]])
            this.runAfter('onTableDblclick', { row, column })
        }
    }

    /**
     * 打开表单
     * @param operate 操作:Add=添加,Edit=编辑
     * @param operateIds 被操作项的数组:Add=[],Edit=[1,2,...]
     */
    toggleForm = (operate = '', operateIds: string[] = []) => {
        if (this.runBefore('toggleForm', { operate, operateIds }) === false) return
        if (operate == 'Edit') {
            if (!operateIds.length) {
                return false
            }
            this.getEditData(operateIds[0])
        } else if (operate == 'Add') {
            this.form.items = cloneDeep(this.form.defaultItems)
        }
        this.form.operate = operate
        this.form.operateIds = operateIds
        this.runAfter('toggleForm', { operate, operateIds })
    }

    /**
     * 提交表单
     * @param formEl 表单组件ref
     */
    onSubmit = (formEl?: FormInstance | null) => {
        // 当前操作的首字母小写
        const operate = this.form.operate!.replace(this.form.operate![0], this.form.operate![0].toLowerCase())

        if (this.runBefore('onSubmit', { formEl: formEl, operate: operate, items: this.form.items! }) === false) return

        // 表单验证通过后执行的 api 请求操作
        const submitCallback = () => {
            this.form.submitLoading = true
            this.api
                .postData(operate, this.form.items!)
                .then((res) => {
                    this.onTableHeaderAction('refresh', { event: 'submit', operate, items: this.form.items })
                    this.form.operateIds?.shift()
                    if (this.form.operateIds!.length > 0) {
                        this.toggleForm('Edit', this.form.operateIds)
                    } else {
                        this.toggleForm()
                    }
                    this.runAfter('onSubmit', { res })
                })
                .finally(() => {
                    this.form.submitLoading = false
                })
        }

        if (formEl) {
            this.form.ref = formEl
            formEl.validate((valid: boolean) => {
                if (valid) {
                    submitCallback()
                }
            })
        } else {
            submitCallback()
        }
    }

    /**
     * 获取表格选择项的主键数组
     */
    getSelectionIds() {
        const ids: string[] = []
        this.table.selection?.forEach((item) => {
            ids.push(item[this.table.pk!])
        })
        return ids
    }

    /**
     * 表格内的事件统一响应
     * @param event 事件名称，含义请参考其类型定义
     * @param data 携带数据
     */
    onTableAction = (event: BaTableActionEventName, data: anyObj) => {
        if (this.runBefore('onTableAction', { event, data }) === false) return
        const actionFun = new Map([
            [
                'selection-change',
                () => {
                    this.table.selection = data as TableRow[]
                },
            ],
            [
                'page-size-change',
                () => {
                    this.table.filter!.limit = data.size
                    this.onTableHeaderAction('refresh', { event: 'page-size-change', ...data })
                },
            ],
            [
                'current-page-change',
                () => {
                    this.table.filter!.page = data.page
                    this.onTableHeaderAction('refresh', { event: 'current-page-change', ...data })
                },
            ],
            [
                'sort-change',
                () => {
                    let newOrder: string | undefined
                    if (data.prop && data.order) {
                        newOrder = data.prop + ',' + data.order
                    }
                    if (newOrder != this.table.filter!.order) {
                        this.table.filter!.order = newOrder
                        this.onTableHeaderAction('refresh', { event: 'sort-change', ...data })
                    }
                },
            ],
            [
                'edit',
                () => {
                    this.toggleForm('Edit', [data.row[this.table.pk!]])
                },
            ],
            [
                'delete',
                () => {
                    this.postDel([data.row[this.table.pk!]])
                },
            ],
            [
                'field-change',
                () => {
                    if (data.field && data.field.prop && this.table.data![data.index]) {
                        this.table.data![data.index][data.field.prop!] = data.value
                    }
                },
            ],
            [
                'com-search',
                () => {
                    // 主动触发公共搜索，采用覆盖模式设定请求筛选数据
                    this.setFilterSearchData(this.getComSearchData(), 'cover')

                    // 刷新表格
                    this.onTableHeaderAction('refresh', { event: 'com-search', data: this.table.filter!.search })
                },
            ],
            [
                'default',
                () => {
                    console.warn('No action defined')
                },
            ],
        ])

        const action = actionFun.get(event) || actionFun.get('default')
        action!.call(this)
        return this.runAfter('onTableAction', { event, data })
    }

    /**
     * 表格顶栏按钮事件统一响应
     * @param event 事件名称，含义参考其类型定义
     * @param data 携带数据
     */
    onTableHeaderAction = (event: BaTableHeaderActionEventName, data: anyObj) => {
        if (this.runBefore('onTableHeaderAction', { event, data }) === false) return
        const actionFun = new Map([
            [
                'refresh',
                () => {
                    // 刷新表格在大多数情况下无需置空 data，但任需防范表格列组件的 :key 不会被更新的问题，比如关联表的数据列
                    this.table.data = []
                    this.getData()
                },
            ],
            [
                'add',
                () => {
                    this.toggleForm('Add')
                },
            ],
            [
                'edit',
                () => {
                    this.toggleForm('Edit', this.getSelectionIds())
                },
            ],
            [
                'delete',
                () => {
                    this.postDel(this.getSelectionIds())
                },
            ],
            [
                'unfold',
                () => {
                    if (!this.table.ref) {
                        console.warn('Collapse/expand failed because table ref is not defined. Please assign table ref when onMounted')
                        return
                    }
                    this.table.expandAll = data.unfold
                    this.table.ref.unFoldAll(data.unfold)
                },
            ],
            [
                'quick-search',
                () => {
                    this.onTableHeaderAction('refresh', { event: 'quick-search', ...data })
                },
            ],
            [
                'change-show-column',
                () => {
                    const columnKey = getArrayKey(this.table.column, 'prop', data.field)
                    this.table.column[columnKey].show = data.value
                },
            ],
            [
                'default',
                () => {
                    console.warn('No action defined')
                },
            ],
        ])

        const action = actionFun.get(event) || actionFun.get('default')
        action!.call(this)
        return this.runAfter('onTableHeaderAction', { event, data })
    }

    /**
     * 初始化默认排序
     * el-table 的 `default-sort` 在自定义排序时无效
     * 此方法只有在表格数据请求结束后执行有效
     */
    initSort = () => {
        if (this.table.defaultOrder && this.table.defaultOrder.prop) {
            if (!this.table.ref) {
                console.warn('Failed to initialize default sorting because table ref is not defined. Please assign table ref when onMounted')
                return
            }

            const defaultOrder = this.table.defaultOrder.prop + ',' + this.table.defaultOrder.order
            if (this.table.filter && this.table.filter.order != defaultOrder) {
                this.table.filter.order = defaultOrder
                this.table.ref.getRef()?.sort(this.table.defaultOrder.prop, this.table.defaultOrder.order == 'desc' ? 'descending' : 'ascending')
            }
        }
    }

    /**
     * 初始化表格拖动排序
     */
    dragSort = () => {
        const buttonsKey = getArrayKey(this.table.column, 'render', 'buttons')
        if (buttonsKey === false) return
        const moveButton = getArrayKey(this.table.column[buttonsKey]?.buttons, 'render', 'moveButton')
        if (moveButton === false) return
        if (!this.table.ref) {
            console.warn('Failed to initialize drag sort because table ref is not defined. Please assign table ref when onMounted')
            return
        }

        const el = this.table.ref.getRef()?.$el.querySelector('.el-table__body-wrapper .el-table__body tbody')
        const disabledTip = this.table.column[buttonsKey].buttons![moveButton].disabledTip
        Sortable.create(el, {
            animation: 200,
            handle: '.table-row-weigh-sort',
            ghostClass: 'ba-table-row',
            onStart: () => {
                this.table.column[buttonsKey].buttons![moveButton].disabledTip = true
            },
            onEnd: (evt: Sortable.SortableEvent) => {
                this.table.column[buttonsKey].buttons![moveButton].disabledTip = disabledTip

                // 目标位置不变
                if (evt.oldIndex == evt.newIndex || typeof evt.newIndex == 'undefined' || typeof evt.oldIndex == 'undefined') return

                // 找到对应行id
                const moveRow = findIndexRow(this.table.data!, evt.oldIndex) as TableRow
                const targetRow = findIndexRow(this.table.data!, evt.newIndex) as TableRow

                const eventData = {
                    move: moveRow[this.table.pk!],
                    target: targetRow[this.table.pk!],
                    order: this.table.filter?.order,
                    direction: evt.newIndex > evt.oldIndex ? 'down' : 'up',
                }

                if (this.table.dragSortLimitField && moveRow[this.table.dragSortLimitField] != targetRow[this.table.dragSortLimitField]) {
                    this.onTableHeaderAction('refresh', { event: 'sort', ...eventData })
                    ElNotification({
                        type: 'error',
                        message: i18n.global.t('utils.The moving position is beyond the movable range!'),
                    })
                    return
                }

                this.api.sortable(eventData).finally(() => {
                    this.onTableHeaderAction('refresh', { event: 'sort', ...eventData })
                })
            },
        })
    }

    /**
     * 表格初始化
     */
    mount = () => {
        if (this.runBefore('mount') === false) return

        // 记录表格的路由路径
        const route = useRoute()
        this.table.routePath = route.fullPath

        // 按需初始化公共搜索表单数据和字段Map
        if (this.comSearch.fieldData.size === 0) {
            this.initComSearch()
        }

        if (this.table.acceptQuery && !isEmpty(route.query)) {
            // 根据当前 URL 的 query 初始化公共搜索默认值
            this.setComSearchData(route.query)

            // 获取公共搜索数据合并至表格筛选条件
            this.setFilterSearchData(this.getComSearchData(), 'merge')
        }
    }

    /**
     * 公共搜索初始化
     */
    initComSearch = () => {
        const form: anyObj = {}
        const field = this.table.column

        if (field.length <= 0) return

        for (const key in field) {
            // 关闭搜索的字段
            if (field[key].operator === false) continue

            // 取默认操作符号
            if (typeof field[key].operator == 'undefined') {
                field[key].operator = 'eq'
            }

            // 公共搜索表单字段初始化
            const prop = field[key].prop
            if (prop) {
                if (field[key].operator == 'RANGE' || field[key].operator == 'NOT RANGE') {
                    // 范围查询
                    form[prop] = ''
                    form[prop + '-start'] = ''
                    form[prop + '-end'] = ''
                } else if (field[key].operator == 'NULL' || field[key].operator == 'NOT NULL') {
                    // 复选框
                    form[prop] = false
                } else {
                    // 普通文本框
                    form[prop] = ''
                }

                // 初始化字段的公共搜索数据
                this.comSearch.fieldData.set(prop, {
                    operator: field[key].operator,
                    render: field[key].render,
                    comSearchRender: field[key].comSearchRender,
                })
            }
        }

        this.comSearch.form = Object.assign(this.comSearch.form, form)
    }

    /**
     * 设置公共搜索表单数据
     */
    setComSearchData = (query: anyObj) => {
        // 必需已经完成公共搜索数据的初始化
        if (this.comSearch.fieldData.size === 0) {
            this.initComSearch()
        }

        for (const key in this.table.column) {
            const prop = this.table.column[key].prop
            if (prop && typeof query[prop] !== 'undefined') {
                const queryProp = query[prop] ?? ''
                if (this.table.column[key].operator == 'RANGE' || this.table.column[key].operator == 'NOT RANGE') {
                    const range = queryProp.split(',')
                    if (this.table.column[key].render == 'datetime' || this.table.column[key].comSearchRender == 'date') {
                        if (range && range.length >= 2) {
                            const rangeDayJs = [dayjs(range[0]), dayjs(range[1])]
                            if (rangeDayJs[0].isValid() && rangeDayJs[1].isValid()) {
                                if (this.table.column[key].comSearchRender == 'date') {
                                    this.comSearch.form[prop] = [rangeDayJs[0].format('YYYY-MM-DD'), rangeDayJs[1].format('YYYY-MM-DD')]
                                } else {
                                    this.comSearch.form[prop] = [
                                        rangeDayJs[0].format('YYYY-MM-DD HH:mm:ss'),
                                        rangeDayJs[1].format('YYYY-MM-DD HH:mm:ss'),
                                    ]
                                }
                            }
                        }
                    } else if (this.table.column[key].comSearchRender == 'time') {
                        if (range && range.length >= 2) {
                            this.comSearch.form[prop] = [range[0], range[1]]
                        }
                    } else {
                        this.comSearch.form[prop + '-start'] = range[0] ?? ''
                        this.comSearch.form[prop + '-end'] = range[1] ?? ''
                    }
                } else if (this.table.column[key].operator == 'NULL' || this.table.column[key].operator == 'NOT NULL') {
                    this.comSearch.form[prop] = queryProp ? true : false
                } else if (this.table.column[key].render == 'datetime' || this.table.column[key].comSearchRender == 'date') {
                    const propDayJs = dayjs(queryProp)
                    if (propDayJs.isValid()) {
                        this.comSearch.form[prop] = propDayJs.format(
                            this.table.column[key].comSearchRender == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'
                        )
                    }
                } else {
                    this.comSearch.form[prop] = queryProp
                }
            }
        }
    }

    /**
     * 获取公共搜索表单数据
     */
    getComSearchData = () => {
        // 必需已经完成公共搜索数据的初始化
        if (this.comSearch.fieldData.size === 0) {
            this.initComSearch()
        }

        const comSearchData: ComSearchData[] = []

        for (const key in this.comSearch.form) {
            if (!this.comSearch.fieldData.has(key)) continue

            let val = null
            const fieldDataTemp = this.comSearch.fieldData.get(key)
            if (
                (fieldDataTemp.render == 'datetime' || ['datetime', 'date', 'time'].includes(fieldDataTemp.comSearchRender)) &&
                (fieldDataTemp.operator == 'RANGE' || fieldDataTemp.operator == 'NOT RANGE')
            ) {
                if (this.comSearch.form[key] && this.comSearch.form[key].length >= 2) {
                    // 日期范围
                    if (fieldDataTemp.comSearchRender == 'date') {
                        val = this.comSearch.form[key][0] + ' 00:00:00' + ',' + this.comSearch.form[key][1] + ' 23:59:59'
                    } else {
                        // 时间范围、时间日期范围
                        val = this.comSearch.form[key][0] + ',' + this.comSearch.form[key][1]
                    }
                }
            } else if (fieldDataTemp.operator == 'RANGE' || fieldDataTemp.operator == 'NOT RANGE') {
                // 普通的范围筛选，公共搜索初始化时已准备好 start 和 end 字段
                if (!this.comSearch.form[key + '-start'] && !this.comSearch.form[key + '-end']) {
                    continue
                }
                val = this.comSearch.form[key + '-start'] + ',' + this.comSearch.form[key + '-end']
            } else if (this.comSearch.form[key]) {
                val = this.comSearch.form[key]
            }

            if (val === null) continue
            if (isArray(val) && !val.length) continue

            comSearchData.push({
                field: key,
                val: val,
                operator: fieldDataTemp.operator,
                render: fieldDataTemp.render,
            })
        }

        return comSearchData
    }

    /**
     * 设置 getData 请求时的过滤条件（搜索数据）
     * @param search 新的搜索数据
     * @param mode 模式:cover=覆盖到已有搜索数据,merge=合并到已有搜索数据
     */
    setFilterSearchData = (search: ComSearchData[], mode: 'cover' | 'merge' = 'merge') => {
        if (mode == 'cover' || !this.table.filter?.search) {
            this.table.filter!.search = search
        } else {
            const merged = this.table.filter!.search.concat(search)
            const fieldMap = new Map<string, ComSearchData>()

            merged.forEach((item) => {
                fieldMap.set(item.field, item)
            })

            this.table.filter!.search = Array.from(fieldMap.values())
        }
    }

    // 方法别名
    getIndex = this.getData
    requestEdit = this.getEditData
}
