import { reactive, shallowReactive, toRefs, ToRefs, UnwrapNestedRefs, ShallowReactive } from 'vue'

/**
 * getters 属性
 */
type Getters<T> = {
    [key in keyof T]: () => void;
}

/**
 * getters 计算属性
 */
type ComputedGetters<T extends Getters<T>> = { readonly [key in keyof T]: ReturnType<T[key]> };

/**
 * store 对象
 */
interface Store<S extends object, G extends Getters<G>, A extends object> {
    state: UnwrapNestedRefs<S>;
    getters: ComputedGetters<G>;
    actions: A;
    $setState: (callback: (state: UnwrapNestedRefs<S>) => void) => void;
    $mapState: () => ToRefs<UnwrapNestedRefs<S>>;
    $mapGetters: () => ToRefs<ShallowReactive<ComputedGetters<G>>>;
}

/**
 * store 配置项
 */
interface StoreOptions<S extends object, G extends Getters<G>, A extends object> {
    state: () => S;
    //created?: (this: Store<S, G, A>) => void;
    getters?: G & ThisType<Store<S, G, A>>;
    actions?: A & ThisType<Store<S, G, A>>;
}

/**
 * 创建 store 对象
 * @param options 
 * @returns 
 */
export function createStore<S extends object, G extends Getters<G>, A extends object>(options: StoreOptions<S, G, A>) {
    const state = reactive(options.state())
    const getters: ComputedGetters<G> = Object.create(null)
    const actions: A = Object.create(null)

    const store: Store<S, G, A> = {
        state,
        getters,
        actions,
        $setState: (callback) => {
            callback(state)
        },
        $mapState: () => {
            return toRefs(state)
        },
        $mapGetters: () => {
            const data = shallowReactive(store.getters)
            return toRefs(data)
        }
    }

    if (options.getters) {
        for (const key in options.getters) {
            const fn = options.getters[key]
            // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
            Object.defineProperty(getters, key, {
                get: () => fn.call(store),
                enumerable: true
            })
        }
    }

    if (options.actions) {
        for (const key in options.actions) {
            const fn = options.actions[key]
            if (fn instanceof Function) {
                actions[key] = fn.bind(store)
            } else {
                actions[key] = fn
            }
        }
    }

    // if (options.created) {
    //     options.created.call(store)
    // }

    return store
}