import { computed, getCurrentInstance, nextTick, onUnmounted, reactive, ref, toRaw, unref, watchEffect } from 'vue'
import { isProdMode } from '/@/utils/env'
import { isFunction } from '/@/utils/is'
import { isEqual } from 'lodash-es'
import { tryOnUnmounted } from '@vueuse/core'
import { error } from '/@/utils/log'
import type { ModalMethods, ModalProps, ReturnMethods, UseModalInnerReturnType, UseModalReturnType } from '../typing'

const dataTransfer = reactive<any>({})

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

/**
 * @description: Applicable to independent modal and call outside
 */
export function useModal(): UseModalReturnType {
    const modal = ref<Nullable<ModalMethods>>(null)
    const loaded = ref<Nullable<boolean>>(false)
    const uid = ref<string>('')

    function register(modalMethod: ModalMethods, uuid: string) {
        if (!getCurrentInstance()) {
            throw new Error('useModal() can only be used inside setup() or functional components!')
        }
        uid.value = uuid
        isProdMode() &&
            onUnmounted(() => {
                modal.value = null
                loaded.value = false
                dataTransfer[unref(uid)] = null
            })
        if (unref(loaded) && isProdMode() && modalMethod === unref(modal)) return

        modal.value = modalMethod
        loaded.value = true
        modalMethod.emitVisible = (visible: boolean, uid: number) => {
            visibleData[uid] = visible
        }
    }

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

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

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

        redoModalHeight: () => {
            getInstance()?.redoModalHeight?.()
        },

        openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => {
            getInstance()?.setModalProps({
                visible,
            })

            if (!data) return
            const id = unref(uid)
            if (openOnSet) {
                dataTransfer[id] = null
                dataTransfer[id] = toRaw(data)
                return
            }
            const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data))
            if (!equal) {
                dataTransfer[id] = toRaw(data)
            }
        },

        closeModal: () => {
            getInstance()?.setModalProps({ visible: false })
        },
    }
    return [register, methods]
}

export function useModalInner(callbackFn?: Fn): UseModalInnerReturnType {
    const modalInstanceRef = ref<Nullable<ModalMethods>>(null)
    const currentInstance = getCurrentInstance()
    const uidRef = ref<string>('')

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

    const register = (modalInstance: ModalMethods, uuid: string) => {
        isProdMode() &&
            tryOnUnmounted(() => {
                modalInstanceRef.value = null
            })
        uidRef.value = uuid
        modalInstanceRef.value = modalInstance
        currentInstance?.emit('register', modalInstance, uuid)
    }

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

    return [
        register,
        {
            changeLoading: (loading = true) => {
                getInstance()?.setModalProps({ loading })
            },
            getVisible: computed((): boolean => {
                return visibleData[~~unref(uidRef)]
            }),

            changeOkLoading: (loading = true) => {
                getInstance()?.setModalProps({ confirmLoading: loading })
            },

            closeModal: () => {
                getInstance()?.setModalProps({ visible: false })
            },

            setModalProps: (props: Partial<ModalProps>) => {
                getInstance()?.setModalProps(props)
            },

            redoModalHeight: () => {
                const callRedo = getInstance()?.redoModalHeight
                callRedo && callRedo()
            },
        },
    ]
}
