import { KEY_PREFIX, REHYDRATE } from './constants'
import createAsyncLocalStorage from './defaults/asyncLocalStorage'
import purgeStoredState from './purgeStoredState'
import getStoredState from './getStoredState'
import stringify from 'json-stringify-safe'

const garbagePrefix = '@garbage'

export default function createPersistor (store, config) {
  // defaults
  const serializer = config.serialize === false ? (data) => data : defaultSerializer
  const deserializer = config.serialize === false ? (data) => data : defaultDeserializer
  const blacklist = config.blacklist || []
  const whitelist = config.whitelist || false
  const transforms = config.transforms || []
  const debounce = config.debounce || false
  const keyPrefix = config.keyPrefix !== undefined ? config.keyPrefix : KEY_PREFIX
  const commonKeys = config.commonKeys || []
  const commonKeysPrefix = config.commonKeysPrefix || 'common'
  let dynPrefix = config.dynPrefix || garbagePrefix

  // pluggable state shape (e.g. immutablejs)
  const stateInit = config._stateInit || {}
  const stateIterator = config._stateIterator || defaultStateIterator
  const stateGetter = config._stateGetter || defaultStateGetter
  const stateSetter = config._stateSetter || defaultStateSetter

  // storage with keys -> getAllKeys for localForage support
  let storage = config.storage || createAsyncLocalStorage('local')
  if (storage.keys && !storage.getAllKeys) {
    storage.getAllKeys = storage.keys
  }

  // initialize stateful values
  let lastState = stateInit
  let paused = false
  let storesToProcess = []
  let timeIterator = null
  let dirty = true

  store.subscribe(() => {
    if (paused) return

    dirty = true

    let state = store.getState()

    stateIterator(state, (subState, key) => {
      if (!passWhitelistBlacklist(key)) return
      if (!passKeyFilter(key)) return
      if (stateGetter(lastState, key) === stateGetter(state, key)) return
      if (storesToProcess.indexOf(key) !== -1) return
      storesToProcess.push(key)
    })

    // time iterator (read: debounce)
    if (timeIterator === null) {
      timeIterator = setInterval(() => {
        if (storesToProcess.length === 0) {
          clearInterval(timeIterator)
          timeIterator = null
          return
        }

        let key = storesToProcess[0]
        let storageKey = createStorageKey(key)
        let endState = transforms.reduce((subState, transformer) => transformer.in(subState, key), stateGetter(store.getState(), key))
        if (typeof endState !== 'undefined') storage.setItem(storageKey, serializer(endState), warnIfSetError(key))
        storesToProcess.shift()

        dirty = false
      }, debounce)
    }

    lastState = state
  })

  function passWhitelistBlacklist (key) {
    if (whitelist && whitelist.indexOf(key) === -1) return false
    if (blacklist.indexOf(key) !== -1) return false
    return true
  }

  function passKeyFilter (key) {
    if(commonKeys.includes(key)) return true
    if(dynPrefix === garbagePrefix) return false
    return true
  }

  function adhocRehydrate (incoming, options = {}) {
    let state = {}
    if (options.serial) {
      stateIterator(incoming, (subState, key) => {
        try {
          let data = deserializer(subState)
          let value = transforms.reduceRight((interState, transformer) => {
            return transformer.out(interState, key)
          }, data)
          state = stateSetter(state, key, value)
        } catch (err) {
          if (process.env.NODE_ENV !== 'production') console.warn(`Error rehydrating data for key "${key}"`, subState, err)
        }
      })
    } else state = incoming

    store.dispatch(rehydrateAction(state))
    return state
  }

  function forceFlush () {
    const state = store.getState()
    let storesToProcess = []

    stateIterator(state, (subState, key) => {
      if (!passWhitelistBlacklist(key)) return
      if (!passKeyFilter(key)) return
      if (storesToProcess.indexOf(key) !== -1) return
      storesToProcess.push(key)
    })

    storesToProcess.forEach(key => {
      let storageKey = createStorageKey(key)
      let endState = transforms.reduce((subState, transformer) => transformer.in(subState, key), stateGetter(state, key))
      if (typeof endState !== 'undefined') storage.setItem(storageKey, serializer(endState), warnIfSetError(key))
    })

    dirty = false
  }

  function persistState(key, state) {
    let storageKey = createStorageKey(key)
    let endState = transforms.reduce((subState, transformer) => transformer.in(subState, key), state)
    if (typeof endState !== 'undefined') storage.setItem(storageKey, serializer(endState), warnIfSetError(key))
  }

  function changeDynPrefix (_dynPrefix = garbagePrefix) {
    const wasPaused = paused
    paused = true
    if(dirty) {
      if(timeIterator) {
        clearInterval(timeIterator)
      }
      forceFlush()
    }

    dynPrefix = _dynPrefix
    config.dynPrefix = _dynPrefix

    return getStoredState(config)
      .then(adhocRehydrate)
      .then(() => forceFlush())
      .then(() => paused = wasPaused)
  }

  function createStorageKey (key) {
    if(commonKeys.includes(key)) {
      return `${keyPrefix}${commonKeysPrefix}:${key}`
    } else {
      return `${keyPrefix}@${dynPrefix}:${key}`
    }
  }

  // return `persistor`
  return {
    rehydrate: adhocRehydrate,
    forceFlush: forceFlush,
    persistState: persistState,
    changeDynPrefix: changeDynPrefix,
    pause: () => { paused = true },
    resume: () => { paused = false },
    purge: (keys) => purgeStoredState({storage, keyPrefix}, keys)
  }
}

function warnIfSetError (key) {
  return function setError (err) {
    if (err && process.env.NODE_ENV !== 'production') { console.warn('Error storing data for key:', key, err) }
  }
}

function defaultSerializer (data) {
  return stringify(data, null, null, (k, v) => {
    if (process.env.NODE_ENV !== 'production') return null
    throw new Error(`
      redux-persist: cannot process cyclical state.
      Consider changing your state structure to have no cycles.
      Alternatively blacklist the corresponding reducer key.
      Cycle encounted at key "${k}" with value "${v}".
    `)
  })
}

function defaultDeserializer (serial) {
  return JSON.parse(serial)
}

function rehydrateAction (data) {
  return {
    type: REHYDRATE,
    payload: data
  }
}

function defaultStateIterator (collection, callback) {
  return Object.keys(collection).forEach((key) => callback(collection[key], key))
}

function defaultStateGetter (state, key) {
  return state[key]
}

function defaultStateSetter (state, key, value) {
  state[key] = value
  return state
}

@@ -4723,16 +4723,32 @@
 gePrefix
+, reducer = null
 ) %7B%0A    
@@ -4963,16 +4963,77 @@
 Prefix%0A%0A
+    if(reducer) %7B%0A      store.replaceReducer(reducer)%0A    %7D%0A%0A
     retu
@@ -5090,40 +5090,8 @@
 te)%0A
-      .then(() =%3E forceFlush())%0A
     

Allow to change the reducer at the same time as changing the prefix