import type {
    DrawerInstance,
    DrawerProps,
    ReturnMethods,
    UseDrawerInnerReturnType,
    UseDrawerReturnType,
} from './typing'
import { computed, getCurrentInstance, nextTick, reactive, ref, toRaw, unref, watchEffect } from 'vue'
import { isProdMode } from '/@/utils/env'
import { isFunction } from '/@/utils/is'
import { tryOnUnmounted } from '@vueuse/core'
import { isEqual } from 'lodash-es'
import { error } from '/@/utils/log'

const dataTransferRef = reactive<any>({})

const visibleData = reactive<{ [key: number]: boolean }>({})

/**
 * @description: Applicable to separate drawer and call outside
 */
export function useDrawer(): UseDrawerReturnType {
    if (!getCurrentInstance()) {
        throw new Error('useDrawer() can only be used inside setup() or functional components!')
    }
    const drawer = ref<DrawerInstance | null>(null)
    const loaded = ref<Nullable<boolean>>(false)
    const uid = ref<string>('')

    function register(drawerInstance: DrawerInstance, uuid: string) {
        isProdMode() &&
            tryOnUnmounted(() => {
                drawer.value = null
                loaded.value = null
                dataTransferRef[unref(uid)] = null
            })

        if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
            return
        }
        uid.value = uuid
        drawer.value = drawerInstance
        loaded.value = true

        drawerInstance.emitVisible = (visible: boolean, uid: number) => {
            visibleData[uid] = visible
        }
    }

    const getInstance = () => {
        const instance = unref(drawer)
        if (!instance) {
            error('useDrawer instance is undefined!')
        }
        return instance
    }

    const methods: ReturnMethods = {
        setDrawerProps: (props: Partial<DrawerProps>): void => {
            getInstance()?.setDrawerProps(props)
        },

        getVisible: computed((): boolean => {
            return visibleData[~~unref(uid)]
        }),

        openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
            getInstance()?.setDrawerProps({
                visible,
            })
            if (!data) return

            if (openOnSet) {
                dataTransferRef[unref(uid)] = null
                dataTransferRef[unref(uid)] = toRaw(data)
                return
            }
            const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data))
            if (!equal) {
                dataTransferRef[unref(uid)] = toRaw(data)
            }
        },
        closeDrawer: () => {
            getInstance()?.setDrawerProps({ visible: false })
        },
    }

    return [register, methods]
}

export function useDrawerInner(callbackFn?: Fn): UseDrawerInnerReturnType {
    const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null)
    const currentInstance = getCurrentInstance()
    const uidRef = ref<string>('')

    if (!getCurrentInstance()) {
        throw new Error('useDrawerInner() can only be used inside setup() or functional components!')
    }

    const getInstance = () => {
        const instance = unref(drawerInstanceRef)
        if (!instance) {
            error('useDrawerInner instance is undefined!')
            return
        }
        return instance
    }

    const register = (modalInstance: DrawerInstance, uuid: string) => {
        isProdMode() &&
            tryOnUnmounted(() => {
                drawerInstanceRef.value = null
            })

        uidRef.value = uuid
        drawerInstanceRef.value = modalInstance
        currentInstance?.emit('register', modalInstance, uuid)
    }

    watchEffect(() => {
        const data = dataTransferRef[unref(uidRef)]
        if (!data) return
        if (!callbackFn || !isFunction(callbackFn)) return
        nextTick(() => {
            callbackFn(data)
        })
    })

    return [
        register,
        {
            changeLoading: (loading = true) => {
                getInstance()?.setDrawerProps({ loading })
            },

            changeOkLoading: (loading = true) => {
                getInstance()?.setDrawerProps({ confirmLoading: loading })
            },
            getVisible: computed((): boolean => {
                return visibleData[~~unref(uidRef)]
            }),

            closeDrawer: () => {
                getInstance()?.setDrawerProps({ visible: false })
            },

            setDrawerProps: (props: Partial<DrawerProps>) => {
                getInstance()?.setDrawerProps(props)
            },
        },
    ]
}
