text: import { reactive, watch } from 'kdu'
  import { forEachValue, isObject, isPromise, assert, partial } from './util'
  
  export function genericSubscribe (fn, subs, options) {
    if (subs.indexOf(fn) < 0) {
      options && options.prepend
        ? subs.unshift(fn)
        : subs.push(fn)
    }
    return () => {
      const i = subs.indexOf(fn)
      if (i > -1) {
        subs.splice(i, 1)
      }
    }
  }
  
  export function resetStore (store, hot) {
    store._actions = Object.create(null)
    store._mutations = Object.create(null)
    store._wrappedGetters = Object.create(null)
    store._modulesNamespaceMap = Object.create(null)
    const state = store.state
    // init all modules
    installModule(store, state, [], store._modules.root, true)
    // reset state
    resetStoreState(store, state, hot)
  }
  
  export function resetStoreState (store, state, hot) {
    const oldState = store._state
  
    // bind store public getters
    store.getters = {}
    // reset local getters cache
    store._makeLocalGettersCache = Object.create(null)
    const wrappedGetters = store._wrappedGetters
    const computedObj = {}
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      // direct inline function use will lead to closure preserving oldState.
      // using partial to return function with only arguments preserved in closure environment.
      computedObj[key] = partial(fn, store)
      Object.defineProperty(store.getters, key, {
        // TODO: use `computed` when it's possible. at the moment we can't due to
        get: () => computedObj[key](),
        enumerable: true // for local getters
      })
    })
  
    store._state = reactive({
      data: state
    })
  
    // enable strict mode for new state
    if (store.strict) {
      enableStrictMode(store)
    }
  
    if (oldState) {
      if (hot) {
        // dispatch changes in all subscribed watchers
        // to force getter re-evaluation for hot reloading.
        store._withCommit(() => {
          oldState.data = null
        })
      }
    }
  }
  
  export function installModule (store, rootState, path, module, hot) {
    const isRoot = !path.length
    const namespace = store._modules.getNamespace(path)
  
    // register in namespace map
    if (module.namespaced) {
      if (store._modulesNamespaceMap[namespace] && __DEV__) {
        console.error(`[kduxs] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
      }
      store._modulesNamespaceMap[namespace] = module
    }
  
    // set state
    if (!isRoot && !hot) {
      const parentState = getNestedState(rootState, path.slice(0, -1))
      const moduleName = path[path.length - 1]
      store._withCommit(() => {
        if (__DEV__) {
          if (moduleName in parentState) {
            console.warn(
              `[kduxs] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
            )
          }
        }
        parentState[moduleName] = module.state
      })
    }
  
    const local = module.context = makeLocalContext(store, namespace, path)
  
    module.forEachMutation((mutation, key) => {
      const namespacedType = namespace + key
      registerMutation(store, namespacedType, mutation, local)
    })
  
    module.forEachAction((action, key) => {
      const type = action.root ? key : namespace + key
      const handler = action.handler || action
      registerAction(store, type, handler, local)
    })
  
    module.forEachGetter((getter, key) => {
      const namespacedType = namespace + key
      registerGetter(store, namespacedType, getter, local)
    })
  
    module.forEachChild((child, key) => {
      installModule(store, rootState, path.concat(key), child, hot)
    })
  }
  
  /**
   * make localized dispatch, commit, getters and state
   * if there is no namespace, just use root ones
   */
  function makeLocalContext (store, namespace, path) {
    const noNamespace = namespace === ''
  
    const local = {
      dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
        const args = unifyObjectStyle(_type, _payload, _options)
        const { payload, options } = args
        let { type } = args
  
        if (!options || !options.root) {
          type = namespace + type
          if (__DEV__ && !store._actions[type]) {
            console.error(`[kduxs] unknown local action type: ${args.type}, global type: ${type}`)
            return
          }
        }
  
        return store.dispatch(type, payload)
      },
  
      commit: noNamespace ? store.commit : (_type, _payload, _options) => {
        const args = unifyObjectStyle(_type, _payload, _options)
        const { payload, options } = args
        let { type } = args
  
        if (!options || !options.root) {
          type = namespace + type
          if (__DEV__ && !store._mutations[type]) {
            console.error(`[kduxs] unknown local mutation type: ${args.type}, global type: ${type}`)
            return
          }
        }
  
        store.commit(type, payload, options)
      }
    }
  
    // getters and state object must be gotten lazily
    // because they will be changed by state update
    Object.defineProperties(local, {
      getters: {
        get: noNamespace
          ? () => store.getters
          : () => makeLocalGetters(store, namespace)
      },
      state: {
        get: () => getNestedState(store.state, path)
      }
    })
  
    return local
  }
  
  export function makeLocalGetters (store, namespace) {
    if (!store._makeLocalGettersCache[namespace]) {
      const gettersProxy = {}
      const splitPos = namespace.length
      Object.keys(store.getters).forEach(type => {
        // skip if the target getter is not match this namespace
        if (type.slice(0, splitPos) !== namespace) return
  
        // extract local getter type
        const localType = type.slice(splitPos)
  
        // Add a port to the getters proxy.
        // Define as getter property because
        // we do not want to evaluate the getters in this time.
        Object.defineProperty(gettersProxy, localType, {
          get: () => store.getters[type],
          enumerable: true
        })
      })
      store._makeLocalGettersCache[namespace] = gettersProxy
    }
  
    return store._makeLocalGettersCache[namespace]
  }
  
  function registerMutation (store, type, handler, local) {
    const entry = store._mutations[type] || (store._mutations[type] = [])
    entry.push(function wrappedMutationHandler (payload) {
      handler.call(store, local.state, payload)
    })
  }
  
  function registerAction (store, type, handler, local) {
    const entry = store._actions[type] || (store._actions[type] = [])
    entry.push(function wrappedActionHandler (payload) {
      let res = handler.call(store, {
        dispatch: local.dispatch,
        commit: local.commit,
        getters: local.getters,
        state: local.state,
        rootGetters: store.getters,
        rootState: store.state
      }, payload)
      if (!isPromise(res)) {
        res = Promise.resolve(res)
      }
      if (store._devtoolHook) {
        return res.catch(err => {
          store._devtoolHook.emit('kduxs:error', err)
          throw err
        })
      } else {
        return res
      }
    })
  }
  
  function registerGetter (store, type, rawGetter, local) {
    if (store._wrappedGetters[type]) {
      if (__DEV__) {
        console.error(`[kduxs] duplicate getter key: ${type}`)
      }
      return
    }
    store._wrappedGetters[type] = function wrappedGetter (store) {
      return rawGetter(
        local.state, // local state
        local.getters, // local getters
        store.state, // root state
        store.getters // root getters
      )
    }
  }
  
  function enableStrictMode (store) {
    watch(() => store._state.data, () => {
      if (__DEV__) {
        assert(store._committing, `do not mutate kduxs store state outside mutation handlers.`)
      }
    }, { deep: true, flush: 'sync' })
  }
  
  export function getNestedState (state, path) {
    return path.reduce((state, key) => state[key], state)
  }
  
  export function unifyObjectStyle (type, payload, options) {
    if (isObject(type) && type.type) {
      options = payload
      payload = type
      type = type.type
    }
  
    if (__DEV__) {
      assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
    }
  
    return { type, payload, options }
  }
  