import {
  MessageKind,
  validateConfig
} from '@tarojs/plugin-doctor'

import { extractCompileEntry } from '../../util/appConfig'
import * as hooks from '../constant'

import type { IPluginContext } from '@tarojs/service'

export default (ctx: IPluginContext) => {
  ctx.registerCommand({
    name: 'build',
    optionsMap: {
      '--type [typeName]': 'Build type, weapp/swan/alipay/tt/qq/jd/h5/rn',
      '--watch': 'Watch mode',
      '--env [env]': 'Value for process.env.NODE_ENV',
      '--pages': 'Specify the pages to be compiled, separate multiple by comma',
      '--components': 'Specify the components to be compiled, separate multiple by comma',
      '--mode [mode]': 'Value of dotenv extname',
      '-p, --port [port]': 'Specified port',
      '--no-build': 'Do not build project',
      '--platform': '[rn] Specific React-Native build target: android / ios, android is default value',
      '--reset-cache': '[rn] Clear transform cache',
      '--public-path': '[rn] Assets public path',
      '--bundle-output': '[rn] File name where to store the resulting bundle',
      '--sourcemap-output': '[rn] File name where to store the sourcemap file for resulting bundle',
      '--sourcemap-use-absolute-path': '[rn]  Report SourceMapURL using its full path',
      '--sourcemap-sources-root': '[rn] Path to make sourcemaps sources entries relative to',
      '--assets-dest': '[rn] Directory name where to store assets referenced in the bundle',
      '--qr': '[rn] Print qrcode of React-Native bundle server',
      '--blended': 'Blended Taro project in an original MiniApp project',
      '--new-blended': 'Blended Taro project in an original MiniApp project while supporting building components independently',
      '--plugin [typeName]': 'Build Taro plugin project, weapp',
      '--env-prefix [envPrefix]': "Provide the dotEnv varables's prefix",
      '--no-inject-global-style': '[H5] Do not inject global style',
      '--no-check': 'Do not check config is valid or not',
    },
    synopsisList: [
      'taro build --type weapp',
      'taro build --type weapp --watch',
      'taro build --type weapp --watch --pages pages/index/index',
      'taro build --type weapp --env production',
      'taro build --type weapp --blended',
      'taro build --type weapp --no-build',
      'taro build native-components --type weapp',
      'taro build --type weapp --new-blended',
      'taro build --plugin weapp --watch',
      'taro build --plugin weapp',
      'taro build --type weapp --mode prepare --env-prefix TARO_APP_',
    ],
    async fn(opts) {
      const { options, config, _ } = opts
      const { platform, isWatch, blended, newBlended, withoutBuild, noInjectGlobalStyle, noCheck } = options
      const { fs, chalk, PROJECT_CONFIG } = ctx.helper
      const { outputPath, configPath } = ctx.paths
      const { args } = options

      if (!configPath || !fs.existsSync(configPath)) {
        console.log(chalk.red(`找不到项目配置文件${PROJECT_CONFIG}，请确定当前目录是 Taro 项目根目录!`))
        process.exit(1)
      }

      if (typeof platform !== 'string') {
        console.log(chalk.red('请传入正确的编译类型！'))
        process.exit(0)
      }

      // 校验 Taro 项目配置
      if (!noCheck) {
        const checkResult = await checkConfig({
          projectConfig: ctx.initialConfig,
          helper: ctx.helper
        })
        if (!checkResult.isValid) {
          const ERROR = chalk.red('[✗] ')
          const WARNING = chalk.yellow('[!] ')
          const SUCCESS = chalk.green('[✓] ')

          const lineChalk = chalk.hex('#fff')
          const errorChalk = chalk.hex('#f00')
          console.log(errorChalk(`Taro 配置有误，请检查！ (${configPath})`))
          checkResult.messages.forEach((message) => {
            switch (message.kind) {
              case MessageKind.Error:
                console.log('  ' + ERROR + lineChalk(message.content))
                break
              case MessageKind.Success:
                console.log('  ' + SUCCESS + lineChalk(message.content))
                break
              case MessageKind.Warning:
                console.log('  ' + WARNING + lineChalk(message.content))
                break
              case MessageKind.Manual:
                console.log('  ' + lineChalk(message.content))
                break
              default:
                break
            }
          })
          console.log('')
          process.exit(0)
        }
      }

      const isProduction = process.env.NODE_ENV === 'production' || !isWatch

      // dist folder
      fs.ensureDirSync(outputPath)

      // is build native components mode?
      const isBuildNativeComp = _[1] === 'native-components'

      await ctx.applyPlugins(hooks.ON_BUILD_START)
      await ctx.applyPlugins({
        name: platform,
        opts: {
          config: {
            ...config,
            isWatch,
            mode: isProduction ? 'production' : 'development',
            blended,
            isBuildNativeComp,
            withoutBuild,
            newBlended,
            noInjectGlobalStyle,
            async modifyAppConfig (appConfig) {
              extractCompileEntry(appConfig, args, ctx)

              await ctx.applyPlugins({
                name: hooks.MODIFY_APP_CONFIG,
                opts: {
                  appConfig
                }
              })
            },
            async modifyWebpackChain (chain, webpack, data) {
              await ctx.applyPlugins({
                name: hooks.MODIFY_WEBPACK_CHAIN,
                initialVal: chain,
                opts: {
                  chain,
                  webpack,
                  data,
                },
              })
            },
            async modifyViteConfig(viteConfig, data, viteCompilerContext) {
              await ctx.applyPlugins({
                name: hooks.MODIFY_VITE_CONFIG,
                initialVal: viteConfig,
                opts: {
                  viteConfig,
                  data,
                  viteCompilerContext
                },
              })
            },
            async modifyBuildAssets(assets, miniPlugin) {
              await ctx.applyPlugins({
                name: hooks.MODIFY_BUILD_ASSETS,
                initialVal: assets,
                opts: {
                  assets,
                  miniPlugin,
                },
              })
            },
            async modifyMiniConfigs(configMap) {
              await ctx.applyPlugins({
                name: hooks.MODIFY_MINI_CONFIGS,
                initialVal: configMap,
                opts: {
                  configMap,
                },
              })
            },
            async modifyComponentConfig(componentConfig, config) {
              await ctx.applyPlugins({
                name: hooks.MODIFY_COMPONENT_CONFIG,
                opts: {
                  componentConfig,
                  config,
                },
              })
            },
            async onCompilerMake(compilation, compiler, plugin) {
              await ctx.applyPlugins({
                name: hooks.ON_COMPILER_MAKE,
                opts: {
                  compilation,
                  compiler,
                  plugin,
                },
              })
            },
            async onParseCreateElement(nodeName, componentConfig) {
              await ctx.applyPlugins({
                name: hooks.ON_PARSE_CREATE_ELEMENT,
                opts: {
                  nodeName,
                  componentConfig,
                },
              })
            },
            async onBuildFinish({ error, stats, isWatch }) {
              await ctx.applyPlugins({
                name: hooks.ON_BUILD_FINISH,
                opts: {
                  error,
                  stats,
                  isWatch,
                },
              })
            },
          },
        },
      })
      await ctx.applyPlugins(hooks.ON_BUILD_COMPLETE)
    },
  })
}

async function checkConfig ({ projectConfig, helper }) {
  const result = await validateConfig(projectConfig, helper)
  return result
}
