import type { ComputedRef, Ref } from 'vue'
import { computed, onMounted, reactive, ref, unref, watch, watchEffect } from 'vue'
import { cloneDeep, get, merge } from 'lodash-es'
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'
import type { PaginationProps } from '../types/pagination'
import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { buildUUID } from '/@/utils/uuid'
import { isBoolean, isFunction } from '/@/utils/is'
import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const'

interface ActionType {
    getPaginationInfo: ComputedRef<boolean | PaginationProps>
    setPagination: (info: Partial<PaginationProps>) => void
    setLoading: (loading: boolean) => void
    getFieldsValue: () => Recordable
    clearSelectedRowKeys: () => void
    tableData: Ref<Recordable[]>
}

interface SearchState {
    sortInfo: Recordable
    filterInfo: Record<string, string[]>
}
export function useDataSource(
    propsRef: ComputedRef<BasicTableProps>,
    { getPaginationInfo, setPagination, setLoading, getFieldsValue, clearSelectedRowKeys, tableData }: ActionType,
    emit: EmitType,
) {
    const searchState = reactive<SearchState>({
        sortInfo: {},
        filterInfo: {},
    })
    const dataSourceRef = ref<Recordable[]>([])
    const rawDataSourceRef = ref<Recordable>({})

    watchEffect(() => {
        tableData.value = unref(dataSourceRef)
    })

    watch(
        () => unref(propsRef).dataSource,
        () => {
            const { dataSource, api } = unref(propsRef)
            !api && dataSource && (dataSourceRef.value = dataSource)
        },
        {
            immediate: true,
        },
    )

    function handleTableChange(
        pagination: PaginationProps,
        filters: Partial<Recordable<string[]>>,
        sorter: SorterResult,
    ) {
        const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef)
        if (clearSelectOnPageChange) {
            clearSelectedRowKeys()
        }
        setPagination(pagination)

        const params: Recordable = {}
        if (sorter && isFunction(sortFn)) {
            const sortInfo = sortFn(sorter)
            searchState.sortInfo = sortInfo
            params.sortInfo = sortInfo
        }

        if (filters && isFunction(filterFn)) {
            const filterInfo = filterFn(filters)
            searchState.filterInfo = filterInfo
            params.filterInfo = filterInfo
        }
        fetch(params)
    }

    function setTableKey(items: any[]) {
        if (!items || !Array.isArray(items)) return
        items.forEach((item) => {
            if (!item[ROW_KEY]) {
                item[ROW_KEY] = buildUUID()
            }
            if (item.children && item.children.length) {
                setTableKey(item.children)
            }
        })
    }

    const getAutoCreateKey = computed(() => {
        return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey
    })

    const getRowKey = computed(() => {
        const { rowKey } = unref(propsRef)
        return unref(getAutoCreateKey) ? ROW_KEY : rowKey
    })

    const getDataSourceRef = computed(() => {
        const dataSource = unref(dataSourceRef)
        if (!dataSource || dataSource.length === 0) {
            return unref(dataSourceRef)
        }
        if (unref(getAutoCreateKey)) {
            const firstItem = dataSource[0]
            const lastItem = dataSource[dataSource.length - 1]

            if (firstItem && lastItem) {
                if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
                    const data = cloneDeep(unref(dataSourceRef))
                    data.forEach((item) => {
                        if (!item[ROW_KEY]) {
                            item[ROW_KEY] = buildUUID()
                        }
                        if (item.children && item.children.length) {
                            setTableKey(item.children)
                        }
                    })
                    dataSourceRef.value = data
                }
            }
        }
        return unref(dataSourceRef)
    })

    async function updateTableData(index: number, key: string, value: any) {
        const record = dataSourceRef.value[index]
        if (record) {
            dataSourceRef.value[index][key] = value
        }
        return dataSourceRef.value[index]
    }

    function updateTableDataRecord(rowKey: string | number, record: Recordable): Recordable | undefined {
        const row = findTableDataRecord(rowKey)

        if (row) {
            for (const field in row) {
                if (Reflect.has(record, field)) row[field] = record[field]
            }
            return row
        }
    }

    function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
        if (!dataSourceRef.value || dataSourceRef.value.length === 0) return
        const rowKeyName = unref(getRowKey)
        if (!rowKeyName) return
        const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey
        for (const key of rowKeys) {
            let index: number | undefined = dataSourceRef.value.findIndex((row) => {
                let targetKeyName: string
                if (typeof rowKeyName === 'function') {
                    targetKeyName = rowKeyName(row)
                } else {
                    targetKeyName = rowKeyName as string
                }
                return row[targetKeyName] === key
            })
            if (index >= 0) {
                dataSourceRef.value.splice(index, 1)
            }
            index = unref(propsRef).dataSource?.findIndex((row) => {
                let targetKeyName: string
                if (typeof rowKeyName === 'function') {
                    targetKeyName = rowKeyName(row)
                } else {
                    targetKeyName = rowKeyName as string
                }
                return row[targetKeyName] === key
            })
            if (typeof index !== 'undefined' && index !== -1) unref(propsRef).dataSource?.splice(index, 1)
        }
        setPagination({
            total: unref(propsRef).dataSource?.length,
        })
    }

    function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
        // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
        index = index ?? dataSourceRef.value?.length
        unref(dataSourceRef).splice(index, 0, record)
        return unref(dataSourceRef)
    }

    function findTableDataRecord(rowKey: string | number) {
        if (!dataSourceRef.value || dataSourceRef.value.length === 0) return

        const rowKeyName = unref(getRowKey)
        if (!rowKeyName) return

        const { childrenColumnName = 'children' } = unref(propsRef)

        const findRow = (array: any[]) => {
            let ret
            array.some(function iter(r) {
                if (typeof rowKeyName === 'function') {
                    if ((rowKeyName(r) as string) === rowKey) {
                        ret = r
                        return true
                    }
                } else {
                    if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
                        ret = r
                        return true
                    }
                }
                return r[childrenColumnName] && r[childrenColumnName].some(iter)
            })
            return ret
        }

        // const row = dataSourceRef.value.find(r => {
        //   if (typeof rowKeyName === 'function') {
        //     return (rowKeyName(r) as string) === rowKey
        //   } else {
        //     return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
        //   }
        // })
        return findRow(dataSourceRef.value)
    }

    async function fetch(opt?: FetchParams) {
        const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } =
            unref(propsRef)
        if (!api || !isFunction(api)) return
        try {
            setLoading(true)
            const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting)
            let pageParams: Recordable = {}

            const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps

            if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
                pageParams = {}
            } else {
                pageParams[pageField] = (opt && opt.page) || current
                pageParams[sizeField] = pageSize
            }

            const { sortInfo = {}, filterInfo } = searchState

            let params: Recordable = merge(
                pageParams,
                useSearchForm ? getFieldsValue() : {},
                searchInfo,
                opt?.searchInfo ?? {},
                defSort,
                sortInfo,
                filterInfo,
                opt?.sortInfo ?? {},
                opt?.filterInfo ?? {},
            )
            if (beforeFetch && isFunction(beforeFetch)) {
                params = (await beforeFetch(params)) || params
            }

            const res = await api(params)
            rawDataSourceRef.value = res

            const isArrayResult = Array.isArray(res)

            let resultItems: Recordable[] = isArrayResult ? res : get(res, listField)
            const resultTotal: number = isArrayResult ? res.length : get(res, totalField)

            // 假如数据变少，导致总页数变少并小于当前选中页码，通过getPaginationRef获取到的页码是不正确的，需获取正确的页码再次执行
            if (resultTotal) {
                const currentTotalPage = Math.ceil(resultTotal / pageSize)
                if (current > currentTotalPage) {
                    setPagination({
                        current: currentTotalPage,
                    })
                    return await fetch(opt)
                }
            }

            if (afterFetch && isFunction(afterFetch)) {
                resultItems = (await afterFetch(resultItems)) || resultItems
            }
            dataSourceRef.value = resultItems
            setPagination({
                total: resultTotal || 0,
            })
            if (opt && opt.page) {
                setPagination({
                    current: opt.page || 1,
                })
            }
            emit('fetch-success', {
                items: unref(resultItems),
                total: resultTotal,
            })
            return resultItems
        } catch (error) {
            emit('fetch-error', error)
            dataSourceRef.value = []
            setPagination({
                total: 0,
            })
        } finally {
            setLoading(false)
        }
    }

    function setTableData<T = Recordable>(values: T[]) {
        dataSourceRef.value = values
    }

    function getDataSource<T = Recordable>() {
        return getDataSourceRef.value as T[]
    }

    function getRawDataSource<T = Recordable>() {
        return rawDataSourceRef.value as T
    }

    async function reload(opt?: FetchParams) {
        return await fetch(opt)
    }

    onMounted(() => {
        useTimeoutFn(() => {
            unref(propsRef).immediate && fetch()
        }, 16)
    })

    return {
        getDataSourceRef,
        getDataSource,
        getRawDataSource,
        getRowKey,
        setTableData,
        getAutoCreateKey,
        fetch,
        reload,
        updateTableData,
        updateTableDataRecord,
        deleteTableDataRecord,
        insertTableDataRecord,
        findTableDataRecord,
        handleTableChange,
    }
}
