const path = require('path')
const fs = require('fs-extra')
const { processStats } = require('./utils/stats')

/** @typedef {import('../apollo-server/api/PluginApi')} PluginApi */

/**
 * @param {PluginApi} api
 */
module.exports = api => {
  const { getSharedData, setSharedData, removeSharedData } = api.namespace('org.vue.webpack.')

  let firstRun = true
  let hadFailed = false

  // Specific to each modes (serve, build, ...)
  const fields = {
    status: null,
    progress: {},
    operations: null,
    sizes: null,
    problems: null,
    url: null
  }

  // Common fields for all mode
  const commonFields = {
    'modern-mode': false
  }

  // Init data
  api.onProjectOpen(setup)
  api.onPluginReload(setup)

  function setup () {
    for (const key of ['serve', 'build', 'build-modern']) {
      setupSharedData(key)
    }
    setupCommonData()
  }

  // Called when opening a project
  function setupSharedData (mode) {
    resetSharedData(mode)
  }

  // Called when opening a project
  function setupCommonData () {
    for (const field in commonFields) {
      setSharedData(field, getSharedDataInitialValue(field, commonFields[field]))
    }
  }

  function resetSharedData (mode, clear = false) {
    for (const field in fields) {
      const id = `${mode}-${field}`
      setSharedData(id, getSharedDataInitialValue(id, fields[field], clear))
    }
  }

  function getSharedDataInitialValue (id, defaultValue, clear) {
    if (!clear) {
      const data = getSharedData(id)
      if (data != null) return data.value
    }
    return defaultValue
  }

  async function onWebpackMessage ({ data: message }) {
    if (message.webpackDashboardData) {
      const modernMode = getSharedData('modern-mode').value
      const type = message.webpackDashboardData.type

      for (const data of message.webpackDashboardData.value) {
        const id = `${type}-${data.type}`

        if (data.type === 'stats') {
          // Stats are read from a file
          const statsFile = path.resolve(api.getCwd(), `./node_modules/.stats-${type}.json`)
          const value = await fs.readJson(statsFile)
          const { stats, analyzer } = processStats(value)
          setSharedData(id, stats, { disk: true })
          setSharedData(`${id}-analyzer`, analyzer, { disk: true })
          await fs.remove(statsFile)
        } else if (data.type === 'progress') {
          if (type === 'serve' || !modernMode) {
            setSharedData(id, {
              [type]: data.value
            })
          } else {
            // Display two progress bars
            const progress = getSharedData(id).value
            progress[type] = data.value
            for (const t of ['build', 'build-modern']) {
              setSharedData(`${t}-${data.type}`, {
                build: progress.build || 0,
                'build-modern': progress['build-modern'] || 0
              })
            }
          }
        } else {
          // Don't display success until both build and build-modern are done
          if (type !== 'serve' && modernMode && data.type === 'status' && data.value === 'Success') {
            if (type === 'build-modern') {
              for (const t of ['build', 'build-modern']) {
                setSharedData(`${t}-status`, data.value)
              }
            }
          } else {
            setSharedData(id, data.value)
          }

          // Notifications
          if (type === 'serve' && data.type === 'status') {
            if (data.value === 'Failed') {
              api.notify({
                title: 'Build failed',
                message: 'The build has errors.',
                icon: 'error'
              })
              hadFailed = true
            } else if (data.value === 'Success') {
              if (hadFailed) {
                api.notify({
                  title: 'Build fixed',
                  message: 'The build succeeded.',
                  icon: 'done'
                })
                hadFailed = false
              } else if (firstRun) {
                api.notify({
                  title: 'App ready',
                  message: 'The build succeeded.',
                  icon: 'done'
                })
                firstRun = false
              }
            }
          }
        }
      }
    }
  }

  // Tasks
  const views = {
    views: [
      {
        id: 'org.vue.webpack.views.dashboard',
        label: 'org.vue.vue-webpack.dashboard.title',
        icon: 'dashboard',
        component: 'org.vue.webpack.components.dashboard'
      },
      {
        id: 'org.vue.webpack.views.analyzer',
        label: 'org.vue.vue-webpack.analyzer.title',
        icon: 'donut_large',
        component: 'org.vue.webpack.components.analyzer'
      }
    ],
    defaultView: 'org.vue.webpack.views.dashboard'
  }
  api.describeTask({
    match: /vue-cli-service serve(\s+--\S+(\s+\S+)?)*$/,
    description: 'org.vue.vue-webpack.tasks.serve.description',
    link: 'https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-serve',
    icon: '/public/webpack-logo.png',
    prompts: [
      {
        name: 'open',
        type: 'confirm',
        default: false,
        description: 'org.vue.vue-webpack.tasks.serve.open'
      },
      {
        name: 'mode',
        type: 'list',
        default: 'development',
        choices: [
          {
            name: 'development',
            value: 'development'
          },
          {
            name: 'production',
            value: 'production'
          },
          {
            name: 'test',
            value: 'test'
          },
          {
            name: '(unset)',
            value: ''
          }
        ],
        description: 'org.vue.vue-webpack.tasks.serve.mode'
      },
      {
        name: 'host',
        type: 'input',
        default: '',
        description: 'org.vue.vue-webpack.tasks.serve.host'
      },
      {
        name: 'port',
        type: 'input',
        default: undefined,
        description: 'org.vue.vue-webpack.tasks.serve.port'
      },
      {
        name: 'https',
        type: 'confirm',
        default: false,
        description: 'org.vue.vue-webpack.tasks.serve.https'
      }
    ],
    onBeforeRun: ({ answers, args }) => {
      // Args
      if (answers.open) args.push('--open')
      if (answers.mode) args.push('--mode', answers.mode)
      if (answers.host) args.push('--host', answers.host)
      if (answers.port) args.push('--port', answers.port)
      if (answers.https) args.push('--https')
      args.push('--dashboard')

      // Data
      resetSharedData('serve', true)
      firstRun = true
      hadFailed = false
    },
    onRun: () => {
      api.ipcOn(onWebpackMessage)
    },
    onExit: () => {
      api.ipcOff(onWebpackMessage)
      removeSharedData('serve-url')
    },
    ...views
  })
  api.describeTask({
    match: /vue-cli-service build(\s+--\S+(\s+\S+)?)*$/,
    description: 'org.vue.vue-webpack.tasks.build.description',
    link: 'https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-build',
    icon: '/public/webpack-logo.png',
    prompts: [
      {
        name: 'modern',
        type: 'confirm',
        default: false,
        message: 'org.vue.vue-webpack.tasks.build.modern.label',
        description: 'org.vue.vue-webpack.tasks.build.modern.description',
        link: 'https://cli.vuejs.org/guide/browser-compatibility.html#modern-mode'
      },
      {
        name: 'mode',
        type: 'list',
        default: 'production',
        choices: [
          {
            name: 'development',
            value: 'development'
          },
          {
            name: 'production',
            value: 'production'
          },
          {
            name: 'test',
            value: 'test'
          },
          {
            name: '(unset)',
            value: ''
          }
        ],
        description: 'org.vue.vue-webpack.tasks.build.mode'
      },
      {
        name: 'dest',
        type: 'input',
        default: '',
        description: 'org.vue.vue-webpack.tasks.build.dest'
      },
      {
        name: 'target',
        type: 'list',
        default: 'app',
        choices: [
          {
            name: 'org.vue.vue-webpack.tasks.build.target.app',
            value: 'app'
          },
          {
            name: 'org.vue.vue-webpack.tasks.build.target.lib',
            value: 'lib'
          },
          {
            name: 'org.vue.vue-webpack.tasks.build.target.wc',
            value: 'wc'
          },
          {
            name: 'org.vue.vue-webpack.tasks.build.target.wc-async',
            value: 'wc-async'
          }
        ],
        description: 'org.vue.vue-webpack.tasks.build.target.description'
      },
      {
        name: 'name',
        type: 'input',
        default: '',
        description: 'org.vue.vue-webpack.tasks.build.name'
      },
      {
        name: 'watch',
        type: 'confirm',
        default: false,
        description: 'org.vue.vue-webpack.tasks.build.watch'
      }
    ],
    onBeforeRun: ({ answers, args }) => {
      // Args
      if (answers.mode) args.push('--mode', answers.mode)
      if (answers.dest) args.push('--dest', answers.dest)
      if (answers.target) args.push('--target', answers.target)
      if (answers.name) args.push('--name', answers.name)
      if (answers.watch) args.push('--watch')
      if (answers.modern) args.push('--modern')
      setSharedData('modern-mode', !!answers.modern)
      args.push('--dashboard')

      // Data
      resetSharedData('build', true)
      resetSharedData('build-modern', true)
    },
    onRun: () => {
      api.ipcOn(onWebpackMessage)
    },
    onExit: () => {
      api.ipcOff(onWebpackMessage)
    },
    ...views
  })
  // vue inspect
  api.addTask({
    name: 'inspect',
    command: 'vue-cli-service inspect',
    description: 'org.vue.vue-webpack.tasks.inspect.description',
    link: 'https://cli.vuejs.org/guide/webpack.html#inspecting-the-project-s-webpack-config',
    icon: '/public/webpack-inspect-logo.png',
    prompts: [
      {
        name: 'mode',
        type: 'list',
        default: 'production',
        choices: [
          {
            name: 'development',
            value: 'development'
          },
          {
            name: 'production',
            value: 'production'
          },
          {
            name: 'test',
            value: 'test'
          },
          {
            name: '(unset)',
            value: ''
          }
        ],
        description: 'org.vue.vue-webpack.tasks.inspect.mode'
      },
      {
        name: 'verbose',
        type: 'confirm',
        default: false,
        description: 'org.vue.vue-webpack.tasks.inspect.verbose'
      }
    ],
    onBeforeRun: ({ answers, args }) => {
      if (answers.mode) args.push('--mode', answers.mode)
      if (answers.verbose) args.push('--verbose')
    }
  })

  if (process.env.VUE_APP_CLI_UI_DEV) {
    // Add dynamic components in dev mode (webpack dashboard & analyzer)
    api.addClientAddon({
      id: 'org.vue.webpack.client-addon.dev',
      url: 'http://localhost:8096/index.js'
    })
  } else {
    // Webpack dashboard
    api.addClientAddon({
      id: 'org.vue.webpack.client-addon',
      path: '@vue/cli-ui-addon-webpack/dist'
    })
  }

  // Open app button
  api.ipcOn(({ data }) => {
    if (data.vueServe) {
      setSharedData('serve-url', data.vueServe.url)
    }
  })
}
