import Vue from 'vue';
import Vuex, { Module, ModuleTree } from 'vuex';

import { camelCase, intersection } from 'lodash';

// Recursively get the namespace of a Vuex module, even if nested.
function getNamespace(modules: Record<string, any>, path: any[]): any {
  if (path.length === 1) return modules;
  const namespace = path.shift();
  modules[namespace] = {
    modules: {},
    namespaced: true,
    ...modules[namespace],
  };
  return getNamespace(modules[namespace], path);
}

Vue.use(Vuex);

function loadModules(context: any) {
  // const storeData = { modules: {} };

  const modules = context
    .keys()
    .reduce((modules: ModuleTree<string>, key: string) => {
      const module: Module<string, any> = context(key) || context(key).default;

      const modulePath: string[] = key
        // Remove the "./" from the beginning.
        .replace(/^\.\//, '')
        // Remove the file extension from the end.
        .replace(/\.\w+$/, '')
        // Split nested modules into an array path.
        .split(/\//)
        // camelCase all module namespaces and names.
        .map(camelCase);

      const { modules: subMoldules } = getNamespace(modules, modulePath);
      const modulePathEnd = modulePath.pop();

      // todo refactor nicer nested/root modules setting & getNamespace fn
      if (modulePathEnd && subMoldules) {
        subMoldules[modulePathEnd] = {
          namespaced: true,
          ...module,
        };
      } else if (modulePathEnd) {
        modules[modulePathEnd] = {
          namespaced: true,
          ...module,
        };
      }

      return modules;
    }, {});

  return { context, modules };
}

const loadedModules = [
  require.context('./modules', true, /([a-z_]+)\.[tj]s$/i),
  require.context('@/recruits/state', true, /([a-z_]+)\.[tj]s$/i),
].map(c => loadModules(c));

const mergeModules = (m: any) => {
  const intersections: Array<string> = [];
  // @ts-ignore
  const mergedModules = m.reduce((acc, { modules }) => {
    intersections.push(...intersection(Object.keys(acc), Object.keys(modules)));
    return {
      ...acc,
      ...modules
    };
  }, {});
  if (intersections.length) {
    // Ошибка означает что какие-то модули которые подгружались из разных каталогов имеют совпадающие
    // имена, что будет означать что последний заслонит собой того что был загружен ранее, что явно
    // приведёт к нежелаемому результату. Посмотрите внимательно на содержимое каталогов перечисленных
    // при инициализации массива 'loadedModules'
    console.error("Vuex State имеет конфликт наименований модулей", [...new Set(intersections)])
  }
  return mergedModules;
};

const store = new Vuex.Store({
  modules: mergeModules(loadedModules),
  // Enable strict mode in development to get a warning
  // when mutating store outside of a mutation.
  // https://vuex.vuejs.org/guide/strict.html
  strict: process.env.NODE_ENV !== 'production',
});

if (module.hot) {
  // Hot reload whenever any module changes.
  loadedModules.forEach(({ context }) => {
    module!.hot!.accept(context.id, () => {
      const { modules }: any = loadModules(context);
      store.hotUpdate({
        modules: {
          ...mergeModules(loadedModules),
          ...modules,   
        },
      });
    });
  });
}

export default store;
