'use strict';

const nexus = require('nexus');
const { merge } = require('lodash/fp');

const createShadowCRUDManager = require('./shadow-crud-manager');

/**
 * @typedef StrapiGraphQLExtensionConfiguration
 * @property {NexusGen[]} types - A collection of Nexus types
 * @property {string} typeDefs - Type definitions (SDL format)
 * @property {object} resolvers - A resolver map
 * @property {object} resolversConfig - An object that bind a configuration to a resolver based on an absolute path (the key)
 * @property {NexusPlugin[]} plugins - A collection of Nexus plugins
 */

/**
 * @typedef {function({ strapi: object, nexus: object, typeRegistry: object }): StrapiGraphQLExtensionConfiguration} StrapiGraphQLExtensionConfigurationFactory
 */

const getDefaultState = () => ({
  types: [],
  typeDefs: [],
  resolvers: {},
  resolversConfig: {},
  plugins: [],
});

const createExtension = ({ strapi } = {}) => {
  const configs = [];

  return {
    shadowCRUD: createShadowCRUDManager({ strapi }),

    /**
     * Register a new extension configuration
     * @param {StrapiGraphQLExtensionConfiguration | StrapiGraphQLExtensionConfigurationFactory} configuration
     * @return {this}
     */
    use(configuration) {
      configs.push(configuration);

      return this;
    },

    /**
     * Convert the registered configuration into a single extension object & return it
     * @param {object} options
     * @param {object} options.typeRegistry
     * @return {object}
     */
    generate({ typeRegistry }) {
      const resolveConfig = config => {
        return typeof config === 'function' ? config({ strapi, nexus, typeRegistry }) : config;
      };

      // Evaluate & merge every registered configuration object, then return the result
      return configs.reduce((acc, configuration) => {
        const { types, typeDefs, resolvers, resolversConfig, plugins } = resolveConfig(
          configuration
        );

        // Register type definitions
        if (typeof typeDefs === 'string') {
          acc.typeDefs.push(typeDefs);
        }

        // Register nexus types
        if (Array.isArray(types)) {
          acc.types.push(...types);
        }

        // Register nexus plugins
        if (Array.isArray(plugins)) {
          acc.plugins.push(...plugins);
        }

        // Register resolvers
        if (typeof resolvers === 'object') {
          acc.resolvers = merge(acc.resolvers, resolvers);
        }

        // Register resolvers configuration
        if (typeof resolversConfig === 'object') {
          // TODO: smarter merge for auth, middlewares & policies
          acc.resolversConfig = merge(resolversConfig, acc.resolversConfig);
        }

        return acc;
      }, getDefaultState());
    },
  };
};

module.exports = createExtension;
