import * as ACTIONS from 'constants/action_types';
import { persistStore, persistReducer } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import createCompressor from 'redux-persist-transform-compress';
import { createFilter, createBlacklistFilter } from 'redux-persist-transform-filter';
import localForage from 'localforage';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { createMemoryHistory, createBrowserHistory } from 'history';
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './reducers';
import { Lbry, buildSharedStateMiddleware, ACTIONS as LBRY_REDUX_ACTIONS, SETTINGS } from 'lbry-redux';
import { LBRYINC_ACTIONS, doGetSync, selectUserVerifiedEmail } from 'lbryinc';
import { getSavedPassword, getAuthToken } from 'util/saved-passwords';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { generateInitialUrl } from 'util/url';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';

function isFunction(object) {
  return typeof object === 'function';
}

function isNotFunction(object) {
  return !isFunction(object);
}

function createBulkThunkMiddleware() {
  return ({ dispatch, getState }) => next => action => {
    if (action.type === 'BATCH_ACTIONS') {
      action.actions.filter(isFunction).map(actionFn => actionFn(dispatch, getState));
    }
    return next(action);
  };
}

function enableBatching(reducer) {
  return function batchingReducer(state, action) {
    switch (action.type) {
      case 'BATCH_ACTIONS':
        return action.actions.filter(isNotFunction).reduce(batchingReducer, state);
      default:
        return reducer(state, action);
    }
  };
}

const contentFilter = createFilter('content', ['positions', 'history']);
const fileInfoFilter = createFilter('fileInfo', [
  'fileListPublishedSort',
  'fileListDownloadedSort',
  'fileListSubscriptionSort',
]);
const appFilter = createFilter('app', [
  'hasClickedComment',
  'searchOptionsExpanded',
  'volume',
  'muted',
  'allowAnalytics',
  'welcomeVersion',
]);
// We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']);
const searchFilter = createFilter('search', ['options']);
const tagsFilter = createFilter('tags', ['followedTags']);
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
const blockedFilter = createFilter('blocked', ['blockedChannels']);
const settingsFilter = createBlacklistFilter('settings', ['loadedLanguages', 'language']);
const whiteListedReducers = [
  'fileInfo',
  'publish',
  'wallet',
  'tags',
  'content',
  'app',
  'search',
  'blocked',
  'settings',
  'subscriptions',
];

const transforms = [
  fileInfoFilter,
  walletFilter,
  blockedFilter,
  tagsFilter,
  appFilter,
  searchFilter,
  tagsFilter,
  contentFilter,
  subscriptionsFilter,
  settingsFilter,
  createCompressor(),
];

const persistOptions = {
  key: 'v0',
  storage: localForage,
  stateReconciler: autoMergeLevel2,
  whitelist: whiteListedReducers,
  // Order is important. Needs to be compressed last or other transforms can't
  // read the data
  transforms,
};

let history;
// @if TARGET='app'
history = createMemoryHistory({
  initialEntries: [generateInitialUrl(window.location.hash)],
  initialIndex: 0,
});
// @endif
// @if TARGET='web'
history = createBrowserHistory();
// @endif

const triggerSharedStateActions = [
  ACTIONS.CHANNEL_SUBSCRIBE,
  ACTIONS.CHANNEL_UNSUBSCRIBE,
  LBRY_REDUX_ACTIONS.TOGGLE_TAG_FOLLOW,
  LBRY_REDUX_ACTIONS.TOGGLE_BLOCK_CHANNEL,
  LBRY_REDUX_ACTIONS.CREATE_CHANNEL_COMPLETED,
  // Disabled until we can overwrite preferences
  // LBRY_REDUX_ACTIONS.SHARED_PREFERENCE_SET,
  // ACTIONS.SET_WELCOME_VERSION,
  // ACTIONS.SET_ALLOW_ANALYTICS,
];

/**
 * source: the reducer name
 * property: the property in the reducer-specific state
 * transform: optional method to modify the value to be stored
 *
 * See https://github.com/lbryio/lbry-redux/blob/master/src/redux/middleware/shared-state.js for the source
 * This is based off v0.1
 * If lbry-redux changes to another version, this code will need to be changed when upgrading
 */
const sharedStateFilters = {
  tags: { source: 'tags', property: 'followedTags' },
  subscriptions: {
    source: 'subscriptions',
    property: 'subscriptions',
    transform: function(value) {
      return value.map(({ uri }) => uri);
    },
  },
  blocked: { source: 'blocked', property: 'blockedChannels' },
  settings: { source: 'settings', property: 'sharedPreferences' },
  app_welcome_version: { source: 'app', property: 'welcomeVersion' },
  sharing_3P: { source: 'app', property: 'allowAnalytics' },
};

const sharedStateCb = ({ dispatch, getState }) => {
  const state = getState();
  const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
  const emailVerified = selectUserVerifiedEmail(state);
  if (syncEnabled && emailVerified) {
    getSavedPassword().then(savedPassword => {
      dispatch(doGetSync(savedPassword));
    });
  }
};

const populateAuthTokenHeader = () => {
  return next => action => {
    if (action.type === LBRYINC_ACTIONS.AUTHENTICATION_SUCCESS) {
      const authToken = getAuthToken();
      Lbry.setApiHeader(X_LBRY_AUTH_TOKEN, authToken);
    }

    return next(action);
  };
};

const sharedStateMiddleware = buildSharedStateMiddleware(triggerSharedStateActions, sharedStateFilters, sharedStateCb);
const rootReducer = createRootReducer(history);
const persistedReducer = persistReducer(persistOptions, rootReducer);
const bulkThunk = createBulkThunkMiddleware();
const middleware = [
  sharedStateMiddleware,
  // @if TARGET='web'
  populateAuthTokenHeader,
  // @endif
  routerMiddleware(history),
  thunk,
  bulkThunk,
];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  enableBatching(persistedReducer),
  {}, // initial state
  composeEnhancers(applyMiddleware(...middleware))
);

const persistor = persistStore(store);
window.persistor = persistor;

export { store, persistor, history, whiteListedReducers };