Source: app/selectors/userProfileSelectors.js

import { createSelector } from 'reselect';
import orderBy from 'lodash/orderBy';
import { PROFILE, PRIVACY_SETTINGS, USER_WEIGH_IN_HISTORY } from '../data/entityTypes';
import { userIdSelector } from './userAccountSelectors';
import { userWeighInsCollectionId } from '../data/collectionIds';
import { makeCollectionSelector } from './collectionSelector';

/**
 * Selector that receives the root redux state and returns an object with user profile.
 * If the user is not logged in, or if the user is logged in but the profile entity is
 * not loaded from the API, this selector returns null.
 */
export const userProfileSelector = createSelector(
  userIdSelector,
  state => state.entities[PROFILE] || {},
  (userId, profileEntities) => {
    if (userId && profileEntities[userId]) {
      return profileEntities[userId];
    }
    return null;
  },
);

/**
 * TEMP helper function, remove this once we implement correct profile > weighins normalisation
 * @param state
 */
export const makeWeighInsSelector = () => {
  const collectionSelector = makeCollectionSelector();
  return createSelector(
    (state, props) => {
      const userId = userIdSelector(state, props);

      return collectionSelector(state, { collectionId: userWeighInsCollectionId({ userId }) })
        .entities;
    },
    userProfileSelector,
    (weighInEntities, profile) => {
      if (weighInEntities) {
        return weighInEntities;
      }
      if (profile) {
        return profile.weighIns;
      }
      return [];
    },
  );
};

const weighInSelector = makeWeighInsSelector();

/**
 * Instance of weighInSelector that does not pass props, and thus will always get the weighins
 * for the current user.
 * @param state
 * @returns {*}
 */
export const userWeighInsSelector = state => weighInSelector(state);

export const getPrivacySelector = createSelector(
  state => state.entities[PRIVACY_SETTINGS] || null,
  userIdSelector,
  (privacyEntity, userId) => (privacyEntity ? privacyEntity[userId] : null),
);

export const getJourneyStartWeightSelector = createSelector(
  userProfileSelector,
  profile => profile && profile.currentJourney && profile.currentJourney.startWeight,
);

export const getJourneyIdSelector = createSelector(
  userProfileSelector,
  profile => profile?.currentJourney?.id,
);

/**
 * Selector that gets the current weight of the user. It first checks the most recent weighIn,
 * and will fall back to the startWeight when there are no weighIns yet.
 */
export const currentWeightSelector = createSelector(
  userProfileSelector,
  profile => profile && profile.currentJourney && profile.currentJourney.currentWeight,
);

export const userProfileHeightSelector = createSelector(
  userProfileSelector,
  profile => profile?.height,
);

export const userProfileCurrentWeightSelector = createSelector(
  userProfileSelector,
  profile => profile?.currentWeight,
);

/**
 * Selector for selected weigh-in by id
 */
const weighInByIdSelector = createSelector(
  state => state.weighIn.weighInById,
  weighInById => {
    if (weighInById) {
      return weighInById;
    }
    return null;
  },
);

/**
 * Selector for weigh-in to add
 */
export const weighInToAddSelector = createSelector(
  state => state.weighIn.weighInToAdd,
  weighInToAdd => {
    if (weighInToAdd) {
      return weighInToAdd;
    }
    return null;
  },
);

/**
 * Select weigh-in history
 */
export const weighInHistorySelector = createSelector(
  state => state.entities[USER_WEIGH_IN_HISTORY],
  weighInHistory => {
    if (weighInHistory) {
      return Object.values(weighInHistory);
    }
    return [];
  },
);

/**
 * Selector that gets the previous weight of the after having done a new weighin.
 * It first checks the second most recent weighIn, and will fall back to the initialWeight when
 * there are no weighIns yet.
 *
 */
export const previousWeightSelector = createSelector(
  userWeighInsSelector,
  getJourneyStartWeightSelector,
  weighInHistorySelector,
  weighInByIdSelector,
  weighInToAddSelector,
  (weighIns, journeyStartWeight, weighInHistory, weighInById, weighInToAdd) => {
    const weighInWeights = weighIns && weighIns.filter(weighIn => !weighIn.skippingReason);

    // Do a correct filter to get the correct previous weight, based on the weight you are editing or adding
    const weighInToCheck = weighInById || weighInToAdd;

    if (weighInToCheck && weighInHistory && weighInHistory.length > 1) {
      const filteredWeighInHistory = weighInHistory.filter(weighIn => !weighIn.skippingReason);
      const orderByDateWeighIns = orderBy(
        filteredWeighInHistory,
        weighIn => weighIn.startOfUserWeekUtc,
        ['desc'],
      );

      // Find the weigh-in index from the orderByDateWeighIns
      const index = orderByDateWeighIns.findIndex(fd => weighInToCheck.id === fd.id);
      const previousWeighIn = orderByDateWeighIns
        .slice(index)
        .find(
          validWeighIn => validWeighIn.weighingDateUTC && validWeighIn.id !== weighInToCheck.id,
        );

      if (previousWeighIn) {
        return previousWeighIn.weight;
      }

      return journeyStartWeight;
    } else if (weighInWeights && weighInWeights.length > 1) {
      return weighInWeights[1].weight;
    }

    return journeyStartWeight;
  },
);

/**
 * This combined selector is used for showing the correct current week number and mood on
 * the progress tile
 */
export const lastWeighInSelector = createSelector(
  userWeighInsSelector,
  weighIns => weighIns?.[0] || null,
);

/**
 * This selector finds if a user has completed a first weigh in yet
 */
export const isFirstWeighInSelector = createSelector(
  userWeighInsSelector,
  weighIns => weighIns && weighIns.length === 0,
);

/**
 * Selector that determines if a member is at target using their currentWeight
 * and targetWeight out of their current journey, returns false by default if member
 * doesn't have a target weight.
 */
export const isAtTargetSelector = createSelector(userProfileSelector, profile => {
  if (profile?.currentJourney?.targetWeight) {
    return profile.currentJourney.currentWeight <= profile.currentJourney.targetWeight;
  }

  return false;
});