import stringify from "./stringify";
import { pluralize } from 'skypager-util/lib/string'
import mapKeys from 'lodash/mapKeys'
import mapValues from 'lodash/mapValues'
import reduce from 'lodash/reduce'
import isUndefined from 'lodash/isUndefined'

const featuresReq = require.context('./features', false, /\.js$/)
const reducersReq = require.context('./reducers', false, /\.js$/)
const es6 = (mod) => mod.default ? mod.default : mod

export const defaultFeatures = featuresReq
  .keys()
  .reduce((memo, key) => ({
    ...memo,
    [key.replace(/\.\//,'').replace(/\.js$/,'')]: es6(featuresReq(key)),
  }), {})

export const defaultReducers = reducersReq
  .keys()
  .reduce((memo, key) => ({
    ...memo,
    [key.replace(/\.\//,'').replace(/\.js$/,'')]: es6(reducersReq(key)),
  }), {})


export const defaultInitialState = () => ({})

export function api(options = {}) {
  let { history = [], scope = this, features = {}, reducers = {}, keyFn = pluralize } = options

  features = mapValues({ ...defaultFeatures, ...features }, (fn) => fn.bind ? fn.bind(scope) : fn);
  reducers = mapValues({ ...defaultReducers, ...reducers }, (fn) => fn.bind ? fn.bind(scope) : fn);

  const getState = () => {
    const initialState = reduce(features, (acc, feature, name) => {
      const defaultValue = feature();

      if (isUndefined(defaultValue)) {
        return acc;
      }

      return {
        ...acc,
        [name]: defaultValue,
      };
    }, defaultInitialState());

    const state = history.reduce((acc, { name, args }) => {
      const featureState = acc[name];
      const feature = features[name];

      return {
        ...acc,
        [name]: feature(featureState, ...args),
      };
    }, initialState);

    Object.defineProperty(state, "toString", {
      value: () => stringify(state),
    });

    return state;
  };

  const getConfig = (tap = options.tap) => {
    const state = getState();
    let config = reduce(reducers, (config, reducer, name) => {
      const reduced = reducer(state);

      if (isUndefined(reduced)) {
        return config;
      }

      return {
        ...config,
        [name]: reducer(state),
      };
    }, {});

    if (typeof tap === 'function') {
      config = tap.call(scope, config, options)
    }

    Object.defineProperty(config, "toString", {
      value: () => stringify(config),
    });

    return config;
  };

  const when = (env, configure) => {
    const envs = Array.isArray(env) ? env : [env];
    const { NODE_ENV = "development" } = process.env;

    if (env === true || envs.indexOf(NODE_ENV) !== -1) {
      return configure(api({features,reducers,history}));
    } else if (typeof env === 'function' && env(getState(), getConfig)) {
      return configure({features, reducers, history})
    }

    return api({features, reducers, history});
  };

  return reduce(features, (acc, feature, name) => ({
    ...acc,
    [name]: (...args) => api({
      features,
      reducers,
      history: history.concat({ args, name }),
    }),
  }), {
    getConfig: (...args) => mapKeys(getConfig(...args),(v,k) => keyFn(k)),
    getState,
    history,
    when,
  });
}

export default api
