import { reactive, isRef, isReactive, toRaw, ComputedRef, toRef, effectScope, unref } from 'vue'
import { v4 } from 'uuid'
import { Store, StoreState, StoreToRefs } from './types'

/**
 * 判断是否 ComputedRef 类型
 * @param obj 
 * @returns 
 */
function isComputed(obj: unknown): obj is ComputedRef {
    return !!(isRef(obj) && Reflect.has(obj, 'effect'))
}

/**
 * 定义一个 store 对象
 * @param createStore 
 * @param id 
 * @returns 
 */
export function defineStore<T extends object>(createStore: () => T, id = v4()) {
    const scope = effectScope()
    const store = scope.run(() => createStore()) as T

    // 将 store 中的响应式属性转化为 ref 属性
    const $toRefs = () => {
        const result = Object.create(null)

        for (const key in store) {
            const value = store[key]
            if (isRef(value)) {
                result[key] = value
            } else if (isReactive(value)) {
                result[key] = toRef(store, key)
            }
        }

        return result as StoreToRefs<T>
    }

    // 更新 state 状态
    const $setState = (callback: (state: StoreState<T>) => void) => {
        const state = Object.create(null)

        for (const key in store) {
            const value = store[key]
            if (!isComputed(value) && (isRef(value) || isReactive(value))) {
                state[key] = value
            }
        }

        callback(reactive(state))
    }

    // 将 state 重设为初始状态
    const $reset = () => {
        const newScope = effectScope()
        const newStore = scope.run(() => createStore()) as T

        for (const key in store) {
            const item = store[key]
            if (!isComputed(item) && isRef(item)) {
                item.value = unref(newStore[key])
            }
            if (isReactive(item)) {
                for (const prop in item) {
                    item[prop] = newStore[key][prop]
                }
            }
        }

        newScope.stop()
    }

    // 销毁当前的 store
    const $dispose = () => {
        scope.stop()
    }

    const $store: Store<T> = reactive({
        $id: id,
        $toRefs,
        $setState,
        $reset,
        $dispose,
        ...store
    })

    return () => $store
}

/**
 * 将 store 中的响应式属性转化为 ref 属性
 * @param store 
 * @returns 
 */
export function storeToRefs<T extends object>(store: Store<T>) {
    const raw = toRaw(store)
    const result = Object.create(null)

    for (const key in raw) {
        const value = raw[key]
        if (isRef(value)) {
            result[key] = value
        } else if (isReactive(value)) {
            result[key] = toRef(raw, key)
        }
    }

    return result as StoreToRefs<T>
}