text: import { findIndex, property, merge, union } from 'lodash';
  import uuid from 'uuid/v4';
  import {
    combineActions,
    composeReducers,
    createAction,
    createTypes,
    handleActions
  } from 'berkeleys-redux-utils';
  
  import { themes } from '../../utils/themes';
  import { usernameSelector, types as app } from '../redux';
  import { types as challenges } from '../routes/Challenges/redux';
  import { types as map } from '../Map/redux';
  import legacyProjects from '../../utils/legacyProjectData';
  
  export const ns = 'entities';
  export const getNS = state => state[ns];
  export const entitiesSelector = getNS;
  export const types = createTypes([
    'addPortfolioItem',
    'optoUpdatePortfolio',
    'regresPortfolio',
    'updateMultipleUserFlags',
    'updateTheme',
    'updateUserFlag',
    'updateUserEmail',
    'updateUserLang',
    'updateUserCurrentChallenge'
  ], ns);
  
  // addPortfolioItem(...PortfolioItem) => Action
  export const addPortfolioItem = createAction(types.addPortfolioItem);
  // optoUpdatePortfolio(...PortfolioItem) => Action
  export const optoUpdatePortfolio = createAction(types.optoUpdatePortfolio);
  // regresPortfolio(id: String) => Action
  export const regresPortfolio = createAction(types.regresPortfolio);
  
  // updateMultipleUserFlags({ username: String, flags: { String }) => Action
  export const updateMultipleUserFlags = createAction(
    types.updateMultipleUserFlags
  );
  
  // updateUserFlag(username: String, flag: String) => Action
  export const updateUserFlag = createAction(
    types.updateUserFlag,
    (username, flag) => ({ username, flag })
  );
  // updateUserEmail(username: String, email: String) => Action
  export const updateUserEmail = createAction(
    types.updateUserEmail,
    (username, email) => ({ username, email })
  );
  // updateUserLang(username: String, lang: String) => Action
  export const updateUserLang = createAction(
    types.updateUserLang,
    (username, lang) => ({ username, languageTag: lang })
  );
  
  export const updateUserCurrentChallenge = createAction(
    types.updateUserCurrentChallenge
  );
  
  // entity meta creators
  const getEntityAction = property('meta.entitiesAction');
  
  export const updateThemeMetacreator = (username, theme) => ({
    entitiesAction: {
      type: types.updateTheme,
      payload: {
        username,
        theme: !theme || theme === themes.default ? themes.default : themes.night
      }
    }
  });
  
  export function emptyPortfolio() {
    return {
    id: uuid(),
    title: '',
    description: '',
    url: '',
    image: ''
    };
  }
  
  const defaultState = {
    superBlock: {},
    block: {},
    challenge: {},
    user: {},
    fullBlocks: []
  };
  
  export function portfolioSelector(state, props) {
    const username = usernameSelector(state);
    const { portfolio } = getNS(state).user[username];
    const pIndex = findIndex(portfolio, p => p.id === props.id);
    return portfolio[pIndex];
  }
  
  export function projectsSelector(state) {
    const {
      block: blocks,
      challenge: challengeMap
    } = getNS(state);
    const idToNameMap = challengeIdToNameMapSelector(state);
    const legacyWithDashedNames = legacyProjects
      .reduce((list, current) => ([
        ...list,
        {
          ...current,
          challenges: current.challenges.map(id => idToNameMap[id])
        }
      ]),
      []
    );
    return Object.keys(blocks)
      .filter(key =>
        key.includes('projects') && !key.includes('coding-interview')
      )
      .map(key => blocks[key])
      .concat(legacyWithDashedNames)
      .map(({ title, challenges, superBlock }) => {
        const projectChallengeDashNames = challenges
          // challengeIdToName is not available on appMount
          .filter(Boolean)
          // remove any project intros
          .filter(chal => !chal.includes('get-set-for'));
        const projectChallenges = projectChallengeDashNames
          .map(dashedName => {
            const { id, title } = challengeMap[dashedName];
            return { id, title, dashedName };
          });
        return {
          projectBlockName: title,
          superBlock,
          challenges: projectChallenges
        };
      });
  }
  
  export function challengeIdToNameMapSelector(state) {
    return getNS(state).challengeIdToName || {};
  }
  
  export const challengeMapSelector = state => getNS(state).challenge || {};
  
  export function makeBlockSelector(block) {
    return state => {
      const blockMap = getNS(state).block || {};
      return blockMap[block] || {};
    };
  }
  
  export function makeSuperBlockSelector(name) {
    return state => {
      const superBlock = getNS(state).superBlock || {};
      return superBlock[name] || {};
    };
  }
  
  export const isChallengeLoaded = (state, { dashedName }) =>
    !!challengeMapSelector(state)[dashedName];
  
  export const fullBlocksSelector = state => getNS(state).fullBlocks;
  
  export default composeReducers(
    ns,
    function metaReducer(state = defaultState, action) {
      const { meta } = action;
      if (meta && meta.entities) {
        if (meta.entities.user) {
          return {
            ...state,
            user: {
              ...state.user,
              ...meta.entities.user
            }
          };
        }
        return {
          ...merge(state, action.meta.entities)
        };
      }
      return state;
    },
    function entitiesReducer(state = defaultState, action) {
      if (getEntityAction(action)) {
        const { payload: { username, theme } } = getEntityAction(action);
        return {
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              theme
            }
          }
        };
      }
      return state;
    },
    handleActions(
      () => ({
        [
          combineActions(
            app.fetchChallenges.complete,
            map.fetchMapUi.complete
          )
        ]: (state, { payload }) => {
          const {entities: { block } } = payload;
          return {
            ...merge(state, payload.entities),
            fullBlocks: union(state.fullBlocks, [ Object.keys(block)[0] ])
          };
        },
        [
          challenges.submitChallenge.complete
        ]: (state, { payload: { username, points, challengeInfo } }) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              points,
              challengeMap: {
                ...state.user[username].challengeMap,
                [challengeInfo.id]: challengeInfo
              }
            }
          }
        }),
        [types.addPortfolioItem]: (state, { payload: username }) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              portfolio: [
                ...state.user[username].portfolio,
                emptyPortfolio()
              ]
            }
          }
        }),
        [types.optoUpdatePortfolio]: (
          state,
          { payload: { username, portfolio }}
        ) => {
          const currentPortfolio = state.user[username].portfolio.slice(0);
          const pIndex = findIndex(currentPortfolio, p => p.id === portfolio.id);
          const updatedPortfolio = currentPortfolio;
          updatedPortfolio[pIndex] = portfolio;
          return {
            ...state,
            user: {
              ...state.user,
              [username]: {
                ...state.user[username],
                portfolio: updatedPortfolio
              }
            }
          };
        },
        [types.regresPortfolio]: (state, { payload: { username, id } }) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              portfolio: state.user[username].portfolio.filter(p => p.id !== id)
            }
          }
        }),
        [types.updateMultipleUserFlags]: (
          state,
          { payload: { username, flags }}
        ) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              ...flags
            }
          }
        }),
        [types.updateUserFlag]: (state, { payload: { username, flag } }) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              [flag]: !state.user[username][flag]
            }
          }
        }),
        [types.updateUserEmail]: (state, { payload: { username, email } }) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              email
            }
          }
        }),
        [types.updateUserLang]:
        (
          state,
          {
            payload: { username, languageTag }
          }
        ) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              languageTag
            }
          }
        }),
        [types.updateUserCurrentChallenge]:
        (
          state,
          {
            payload: { username, currentChallengeId }
          }
        ) => ({
          ...state,
          user: {
            ...state.user,
            [username]: {
              ...state.user[username],
              currentChallengeId
            }
          }
        })
      }),
      defaultState
    )
  );
  