import { createSelector } from 'reselect';

import { buildKey } from './utils';

const selectSnowbox = state => state.snowbox;

export const selectCollections = createSelector(
  selectSnowbox,
  snowbox => snowbox.collections,
);

export const selectEntities = createSelector(
  selectSnowbox,
  state => state.entities
);

export const selectSingletons = createSelector(
  selectSnowbox,
  state => state.singletons
);

const selectEntityCollection = entity => (state, filters) => {
  const collections = state?.snowbox?.collections?.[entity.key];

  return collections?.[buildKey(filters)];
};

const applyHydration = (entity, levels = 0) => entities => data => {
  if (!data || !levels) {
    return data;
  }

  if (Array.isArray(data)) {
    return data.map(item => applyHydration(entity, levels)(entities)(item));
  }

  const newData = { ...data };

  for (const key in entity.schema) {
    const childEntity = Array.isArray(entity.schema[key]) ?
      entity.schema[key][0] :
      entity.schema[key];

    newData[key] = applyHydration(childEntity, levels - 1)(entities)(
      Array.isArray(data[key]) ?
        data[key].map(id => entities[childEntity.key][id]) :
        entities[childEntity.key][data[key]]
    );
  }

  return newData;
};

export const selectOne = (entity, hydrationLevels = 0, idAttribute) => {
  if (entity.singleton) {
    return createSelector(
      selectSingletons,
      singletons => singletons[entity.key]
    );
  }

  const hydrate = applyHydration(entity, hydrationLevels);

  return createSelector(
    selectEntities,
    (state, props) => props[idAttribute || entity.idField],
    (entities, id) => entities[entity.key] ?
      hydrate(entities)(entities[entity.key][id]) :
      null
  );
};

const emptyArray = [];

export const selectCollection = (entity, hydrationLevels = 0) => {
  const hydrate = applyHydration(entity, hydrationLevels);

  return createSelector(
    selectEntities,
    selectEntityCollection(entity),
    (allEntities, collection) => {
      if (!allEntities[entity.key] || !collection || !collection.result) {
        return emptyArray;
      }

      const selectedEntities = collection.result
        .map(id => allEntities[entity.key][id])
        .filter(obj => obj);

      return hydrate(allEntities)(selectedEntities);
    }
  );
};

export const selectAll = (entity, hydrationLevels = 0) =>{
  const hydrate = applyHydration(entity, hydrationLevels);

  return createSelector(
    selectEntities,
    (allEntities) => (
      hydrate(allEntities)(Object.values(allEntities[entity.key] || {}))
    )
  );
};

export const selectMeta = entity => createSelector(
  selectEntityCollection(entity),
  collection => collection?.meta
);