// From https://github.com/FormidableLabs/webpack-dashboard/blob/7f99b31c5f00a7818d8129cb8a8fc6eb1b71799c/plugin/index.js
// Modified by Guillaume Chau (Akryum)

/* eslint-disable max-params, max-statements */
'use strict'

const path = require('path')
const fs = require('fs-extra')
const webpack = require('webpack')
const { IpcMessenger } = require('@vue/cli-shared-utils')
const { analyzeBundle } = require('./analyzeBundle')

const ID = 'vue-cli-dashboard-plugin'
const ONE_SECOND = 1000
const FILENAME_QUERY_REGEXP = /\?.*$/

const ipc = new IpcMessenger()

function getTimeMessage (timer) {
  let time = Date.now() - timer

  if (time >= ONE_SECOND) {
    time /= ONE_SECOND
    time = Math.round(time)
    time += 's'
  } else {
    time += 'ms'
  }

  return ` (${time})`
}

class DashboardPlugin {
  constructor (options) {
    this.type = options.type
    if (this.type === 'build' && options.modernBuild) {
      this.type = 'build-modern'
    }
    this.watching = false
    this.autoDisconnect = !options.keepAlive
  }

  cleanup () {
    this.sendData = null
    if (this.autoDisconnect) ipc.disconnect()
  }

  apply (compiler) {
    let sendData = this.sendData
    let timer
    let inProgress = false

    let assetSources = new Map()

    if (!sendData) {
      sendData = data => ipc.send({
        webpackDashboardData: {
          type: this.type,
          value: data
        }
      })
    }

    // Progress status
    let progressTime = Date.now()
    const progressPlugin = new webpack.ProgressPlugin((percent, msg) => {
      // in webpack 5, progress plugin will continue sending progresses even after the done hook
      // for things like caching, causing the progress indicator stuck at 0.99
      // so we have to use a flag to stop sending such `compiling` progress data
      if (!inProgress) {
        return
      }

      // Debouncing
      const time = Date.now()
      if (time - progressTime > 300) {
        progressTime = time
        sendData([
          {
            type: 'status',
            value: 'Compiling'
          },
          {
            type: 'progress',
            value: percent
          },
          {
            type: 'operations',
            value: msg + getTimeMessage(timer)
          }
        ])
      }
    })
    progressPlugin.apply(compiler)

    compiler.hooks.watchRun.tap(ID, c => {
      this.watching = true
    })

    compiler.hooks.run.tap(ID, c => {
      this.watching = false
    })

    compiler.hooks.compile.tap(ID, () => {
      inProgress = true
      timer = Date.now()

      sendData([
        {
          type: 'status',
          value: 'Compiling'
        },
        {
          type: 'progress',
          value: 0
        }
      ])
    })

    compiler.hooks.invalid.tap(ID, () => {
      sendData([
        {
          type: 'status',
          value: 'Invalidated'
        },
        {
          type: 'progress',
          value: 0
        },
        {
          type: 'operations',
          value: 'idle'
        }
      ])
    })

    compiler.hooks.failed.tap(ID, () => {
      sendData([
        {
          type: 'status',
          value: 'Failed'
        },
        {
          type: 'operations',
          value: `idle${getTimeMessage(timer)}`
        }
      ])
      inProgress = false
    })

    compiler.hooks.afterEmit.tap(ID, compilation => {
      assetSources = new Map()
      for (const name in compilation.assets) {
        const asset = compilation.assets[name]
        const filename = name.replace(FILENAME_QUERY_REGEXP, '')
        try {
          assetSources.set(filename, asset.source())
        } catch (e) {
          const webpackFs = compiler.outputFileSystem
          const fullPath = (webpackFs.join || path.join)(compiler.options.output.path, filename)
          const buf = webpackFs.readFileSync(fullPath)
          assetSources.set(filename, buf.toString())
        }
      }
    })

    compiler.hooks.done.tap(ID, stats => {
      let statsData = stats.toJson()
      // Sometimes all the information is located in `children` array
      if ((!statsData.assets || !statsData.assets.length) && statsData.children && statsData.children.length) {
        statsData = statsData.children[0]
      }

      const outputPath = compiler.options.output.path
      statsData.assets.forEach(asset => {
        // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
        asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '')
        asset.fullPath = path.join(outputPath, asset.name)
      })
      // Analyze the assets and update sizes on assets and modules
      analyzeBundle(statsData, assetSources)

      const hasErrors = stats.hasErrors()

      sendData([
        {
          type: 'status',
          value: hasErrors ? 'Failed' : 'Success'
        },
        {
          type: 'progress',
          value: 1
        },
        {
          type: 'operations',
          value: `idle${getTimeMessage(timer)}`
        }
      ])
      inProgress = false

      const statsFile = path.resolve(process.cwd(), `./node_modules/.stats-${this.type}.json`)
      fs.writeJson(statsFile, {
        errors: hasErrors,
        warnings: stats.hasWarnings(),
        data: statsData
      }).then(() => {
        sendData([
          {
            type: 'stats'
          }
        ])

        if (!this.watching) {
          this.cleanup()
        }
      }).catch(error => {
        console.error(error)
      })
    })
  }
}

module.exports = DashboardPlugin
