const loaderUtils = require('loader-utils');

const WebWorkerTemplatePlugin = require('webpack/lib/webworker/WebWorkerTemplatePlugin');
const ExternalsPlugin = require('webpack/lib/ExternalsPlugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');

const COMPILATION_METADATA = Symbol('COMPILATION_METADATA');

module.exports.COMPILATION_METADATA = COMPILATION_METADATA;

module.exports.pitch = function pitch(remainingRequest) {
  const { target, plugins = [], output, emit } = loaderUtils.getOptions(this) || {};

  if (target !== 'worker') {
    throw new Error(`Unsupported compile target: ${JSON.stringify(target)}`);
  }

  this.cacheable(false);

  const { filename, options = {} } = getOutputFilename(output, { target });

  // eslint-disable-next-line no-underscore-dangle
  const currentCompilation = this._compilation;

  const outputFilename = loaderUtils.interpolateName(this, filename, {
    context: options.context || currentCompilation.options.context,
    regExp: options.regExp,
  });

  const outputOptions = {
    filename: outputFilename,
    chunkFilename: `${outputFilename}.[id]`,
    namedChunkFilename: null,
  };

  const compilerOptions = currentCompilation.compiler.options;
  const childCompiler = currentCompilation.createChildCompiler('worker', outputOptions, [
    // https://github.com/webpack/webpack/blob/master/lib/WebpackOptionsApply.js
    new WebWorkerTemplatePlugin(outputOptions),
    new LoaderTargetPlugin('webworker'),
    ...(this.target === 'web' || this.target === 'webworker' ? [] : [new NodeTargetPlugin()]),

    // https://github.com/webpack-contrib/worker-loader/issues/95#issuecomment-352856617
    ...(compilerOptions.externals ? [new ExternalsPlugin(compilerOptions.externals)] : []),

    ...plugins,

    new SingleEntryPlugin(this.context, `!!${remainingRequest}`, 'main'),
  ]);

  const subCache = `subcache ${__dirname} ${remainingRequest}`;

  childCompiler.plugin('compilation', (compilation) => {
    if (!compilation.cache) {
      return;
    }
    if (!(subCache in compilation.cache)) {
      Object.assign(compilation.cache, { [subCache]: {} });
    }
    Object.assign(compilation, { cache: compilation.cache[subCache] });
  });

  const callback = this.async();

  childCompiler.runAsChild((error, entries, compilation) => {
    if (error) {
      return callback(error);
    }
    if (entries.length === 0) {
      return callback(null, null);
    }
    const mainFilename = entries[0].files[0];
    if (emit === false) {
      delete currentCompilation.assets[mainFilename];
    }
    callback(null, compilation.assets[mainFilename].source(), null, {
      [COMPILATION_METADATA]: entries[0].files,
    });
  });
};

function getOutputFilename(options, { target }) {
  if (!options) {
    return { filename: `[hash].${target}.js`, options: undefined };
  }
  if (typeof options === 'string') {
    return { filename: options, options: undefined };
  }
  if (typeof options === 'object') {
    return {
      filename: options.filename,
      options: {
        context: options.context,
        regExp: options.regExp,
      },
    };
  }
  throw new Error(`Invalid compile output options: ${options}`);
}
