const { dirname } = require('path')
const camelCase = require('lodash/camelCase')
const capitalize = require('lodash/capitalize')

module.exports = function(chain, options = {}) {
  const project = this
  const { cacheKey = options.cacheKey, hashObject } = this
  const { ignorePattern = /dist|lib|node_modules/, base = '', origin, pattern = '*.js', prefix = './' } = options

  return chain
    .invoke(`query`, `${base}${pattern}`, false, (r) => r.doc)
    .keyBy(doc => doc.baseRelativePath)
    .mapValues('path')
    .pickBy((absolutePath, baseRelative) => {
      if (absolutePath.match(ignorePattern)) {
        return false
      } else if (origin) {
        return baseRelative.startsWith(dirname(project.relative(origin)))
      } else {
        return true
      }
    })
    .mapKeys((v,k) => {
      return `${prefix}${!origin ? k : k.replace(dirname(project.relative(origin)), '')}`.replace(/\/\//,'/')     
    })
    .thru((moduleMap) => {
      return Object.assign((key) => __non_webpack_require__(moduleMap[key]), {
        id: options.id || `${cacheKey}:${hashObject(moduleMap)}`,
        keys() {
          return Object.keys(moduleMap)
        },
        getOptions() {
          return options
        },
        getModuleMap() {
          return moduleMap
        },
        resolve(key) {
          return moduleMap[key]
        },
      })
    })
    .thru((contextObject) => {
      if (typeof options.stringify === 'function') {
        return options.stringify.call(this, contextObject, options)
      } else if (options.stringify === true) {
        return stringify.call(this, contextObject, options)          
      } else {
        return contextObject
      }           
    })
}

function stringify(requireContext, options = {}) {
  let code = buildContext(requireContext, options)

  if (options.registry) {
    const registryModule = typeof options.registry === 'string'
      ? JSON.stringify(options.registry)
      : 'skypager-registry/lib/context'

    code = [
      `const RegistryClass = require(${registryModule}).default`,
      `${code}`,
      `module.exports = (name, options = {}, ...args) => (
         new RegistryClass(name, Object.assign({}, {context: ctx}, options), ...args)
       )
      `,
    ].map(l => l.trim()).join("\n") 

    return code
  } else {
    return [`${code}; module.exports = ctx;`, ` `].map(l => l.trim()).join("\n")
  }
}

function buildContext (requireContext, options = {}) {
  const keys = JSON.stringify(requireContext.keys())
  const moduleMap = JSON.stringify(requireContext.getModuleMap())
  const getOptions = JSON.stringify(requireContext.getOptions())

  const requireStatements = requireContext.keys().map(
    key => `ctx.__modules["${key}"] = ${buildRequireFunction(requireContext.resolve(key), Object.assign({},{key, chunkName: getChunkName(key, options)},options))}`
  )

  return `
  const ctx = function(key) {
    return ctx.__modules[key]()
  }

  Object.assign(ctx, {
    resolve(key) {
      return ctx.getModuleMap()[key]  
    },
    keys() {
      return ${keys}
    },
    getModuleMap() {
      return ${moduleMap}
    },
    getOptions() {
      return ${getOptions}
    },
  }); 

  Object.defineProperty(ctx, '__modules', {
    enumerable: false,
    configurable: false,
    value: {},
  })

  ${requireStatements.join("\n")}
  `
}

function buildRequireFunction(resourcePath, options = {}) {
  let { loader, query, chunkName = '' } = options

  loader = loader 
    ? loader.endsWith('!') ? loader : `${loader}!` 
    : '' 

  query = query 
    ? query.startsWith('?') ? query : `${query}` 
    : '' 

  chunkName = chunkName.length > 0 ? `,"${camelCase(capitalize(chunkName.replace(/\W+/g,'_')))}"` : ''
  
  let request = resourcePath 

  if (loader) {
    request = `${loader}${request}`
  }

  if (query) {
    request = `${request}${query}` 
  }

  request = JSON.stringify(request)
  
  switch (options.type) {
    case 'async':
      return `function (key, cb) {
        require.ensure([], function(require) {
          cb(null, require(${request}))
        }${chunkName})
      }`
    case 'promise':
      return `function (key) {
        new Promise((resolve, reject) => {
          require.ensure([], function(require) {
            try {
              resolve(require(${request}))
            } catch(error) {
              reject(error)
            }
          }${chunkName})
        })
      }`     
    case 'import':
      return `function (key) { return System.import(${request}) }`
    default:
      return `function (key) { return require(${request}) }`
  }
}

function getChunkName(key, options = {}) {
  return key.replace(options.prefix, '').replace(/\/?\w+\.\w+$/,'')
}