// like manifest webpack plugin, but fit multiple configs environment of webpack
// generate entries of each config, and collect them into a manifest json file

const path = require('path')
const fse = require('fs-extra')

module.exports = class EntriesGenerationWebpackPlugin {
  constructor(opts) {
    this.opts = Object.assign(
      {
        fileName: 'manifest.json',
        appendMode: true,
        transformExtensions: /^(gz|map)$/i,
      },
      opts
    )
  }

  getFileType(str) {
    str = str.replace(/\?.*/, '')
    const split = str.split('.')
    let ext = split.pop()
    if (this.opts.transformExtensions.test(ext)) {
      ext = `${split.pop()}.${ext}`
    }
    return ext
  }

  apply(compiler) {
    compiler.plugin('compilation', compilation => {
      compiler.plugin('after-emit', (compilation, compileCallback) => {
        const publicPath =
          this.opts.publicPath || compilation.options.output.publicPath
        let files = compilation.chunks.reduce(
          (files, chunk) =>
            chunk.files.reduce((files, path) => {
              let name
              if (chunk.name) {
                name = this.opts.setEntryName
                  ? this.opts.setEntryName(chunk.name)
                  : chunk.name
                name = `${name}.${this.getFileType(path)}`
              } else {
                name = path
              }
              return files.concat({
                path,
                chunk,
                name,
                isInitial: chunk.isInitial ? chunk.isInitial() : chunk.initial,
                isChunk: true,
                isAsset: false,
                isModuleAsset: false,
              })
            }, files),
          []
        )

        if (publicPath) {
          files = files
            .filter(file => file.path.indexOf('hot-update') === -1)
            .map(file => {
              file.path = publicPath + file.path
              return file
            })
        }

        const manifest = files.reduce((manifest, file) => {
          manifest[file.name] = file.path
          return manifest
        }, {})
        let json = JSON.stringify(manifest, null, 2)
        const outputFolder = compilation.options.output.path
        const outputFile = path.resolve(
          compilation.options.output.path,
          this.opts.fileName
        )
        const outputName = path.relative(outputFolder, outputFile)

        compilation.assets[outputName] = {
          source() {
            return json
          },
          size() {
            return json.length
          },
        }
        if (this.opts.appendMode && fse.existsSync(outputFile)) {
          const previousJson = JSON.parse(
            fse.readFileSync(outputFile).toString() || '{}'
          )
          json = JSON.stringify(Object.assign(previousJson, manifest), null, 2)
        }
        fse.outputFileSync(outputFile, json)
        compileCallback()
      })
    })
  }
}
