// const path = require('path')
const chalk = require('chalk')
const debug = require('debug')
const execa = require('execa')
const inquirer = require('inquirer')
const EventEmitter = require('events')
const path = require('path')
const generate = require('./generate')
const getGitUser = require('./util/git-user')
const { installDeps, installComposerDeps, injectedScripts } = require('./util/installDeps')
const { clearConsole } = require('./util/clearConsole')

const {
  saveOptions,
  loadOptions
} = require('./options')

const {
  log,
  warn,
  hasGit,
  hasProjectGit,
  hasYarn,
  hasPnpm3OrLater,
  logWithSpinner,
  stopSpinner
} = require('@vue/cli-shared-utils')

module.exports = class Creator extends EventEmitter {
  constructor (name, context) {
    super()

    this.name = name
    this.context = process.env.VUE_CLI_CONTEXT = context
    const { presetPrompt } = this.resolveIntroPrompts()
    this.presetPrompt = presetPrompt
    this.outroPrompts = this.resolveOutroPrompts()
    this.injectedPrompts = []
    this.createCompleteCbs = []

    this.run = this.run.bind(this)

    // const promptAPI = new PromptModuleAPI(this)
    // promptModules.forEach(m => m(promptAPI))
  }

  async create (cliOptions = {}, preset = null) {
    const isTestOrDebug = process.env.CONTROLLA_CLI_TEST || process.env.CONTROLLA_CLI_DEBUG
    const { run, name, context, createCompleteCbs } = this

    if (!preset) {
      if (cliOptions.preset) {
        preset = cliOptions.preset
      } else {
        preset = await this.resolvePrompts()
      }
    }

    const packageManager = (
      cliOptions.packageManager ||
      loadOptions().packageManager ||
      (hasYarn() ? 'yarn' : null) ||
      (hasPnpm3OrLater() ? 'pnpm' : 'npm')
    )

    const description = loadOptions().description

    const author = getGitUser()

    const shouldBackend = await this.shouldBackend(preset)

    await clearConsole()
    logWithSpinner(`✨`, `Creating project in ${chalk.yellow(context)}.`)
    this.emit('creation', { event: 'creating' })
    const src = path.join(path.dirname(require.resolve('../templates')), preset)
    await generate(name, description, author, src, context, shouldBackend, err => {
      if (err) console.log(err)
      stopSpinner()
    })

    // intilaize git repository before installing deps
    // so that vue-cli-service can setup git hooks.
    const shouldInitGit = this.shouldInitGit(cliOptions)
    if (shouldInitGit) {
      logWithSpinner(`🗃`, `Initializing git repository...`)
      this.emit('creation', { event: 'git-init' })
      await run('git init')
    }

    const frontendPath = shouldBackend ? `${context}/frontend` : context
    // install plugins
    stopSpinner()
    log(`🚀  Installing CLI plugins. This might take a while...`)
    log()
    this.emit('creation', { event: 'plugins-install' })
    if (isTestOrDebug) {
      // in development, avoid installation process
      await require('./util/setupDevProject')(context)
    } else {
      await installDeps(frontendPath, packageManager, cliOptions.registry)
    }

    // install composer
    if (shouldBackend) {
      log()
      log(`🚀  Installing Composer dependencies...`)
      this.emit('creation', { event: 'composer-install' })
      log()
      await installComposerDeps(context, 'composer')
    }

    // Injected additional scripts
    if (shouldBackend) {
      log()
      log(`📦  Injected additional scripts...`)
      this.emit('creation', { event: 'injected-scripts' })
      log()
      await injectedScripts(context, 'composer')
    }

    // run complete cbs if any (injected by generators)
    log()
    logWithSpinner('⚓', `Running completion hooks...`)
    this.emit('creation', { event: 'completion-hooks' })
    for (const cb of createCompleteCbs) {
      await cb()
    }

    // generate README.md
    stopSpinner()
    log()
    logWithSpinner('📄', 'Generating README.md...')
    stopSpinner()

    // commit initial state
    let gitCommitFailed = false
    if (shouldInitGit) {
      await run('git add -A')
      if (isTestOrDebug) {
        await run('git', ['config', 'user.name', 'test'])
        await run('git', ['config', 'user.email', 'test@test.com'])
      }
      const msg = typeof cliOptions.git === 'string' ? cliOptions.git : 'init'
      try {
        await run('git', ['commit', '-m', msg])
      } catch (e) {
        gitCommitFailed = true
      }
    }

    // log instructions
    stopSpinner()
    log()
    log(`🎉  Successfully created project ${chalk.yellow(name)}.`)
    if (!cliOptions.skipGetStarted) {
      log(
        `👉  Get started with the following commands:\n\n` +
        (this.context === process.cwd() ? `` : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) +
        chalk.cyan(` ${chalk.gray('$')} ${packageManager === 'yarn' ? 'yarn serve' : packageManager === 'pnpm' ? 'pnpm run serve' : 'npm run serve'}`)
      )
    }
    log()
    this.emit('creation', { event: 'done' })

    if (gitCommitFailed) {
      warn(
        `Skipped git commit due to missing username and email in git config.\n` +
        `You will need to perform the initial commit yourself.\n`
      )
    }

    // generator.printExitLogs()
  }

  run (command, args) {
    if (!args) { [command, ...args] = command.split(/\s+/) }
    return execa(command, args, { cwd: this.context })
  }

  async resolvePrompts (answers = null) {
    // prompt
    if (!answers) {
      await clearConsole(true)
      answers = await inquirer.prompt(this.resolveFinalPrompts())
    }
    debug('vue-cli:answers')(answers)

    if (answers.packageManager) {
      saveOptions({
        packageManager: answers.packageManager
      })
    }

    saveOptions({
      description: answers.description
    })

    const preset = answers.preset
    return preset
  }

  resolveIntroPrompts () {
    const presetPrompt = {
      name: 'preset',
      type: 'list',
      message: `Please pick a preset:`,
      choices: [
        {
          name: 'ERP Project',
          value: 'erp'
        },
        {
          name: 'Electron Project (clear)',
          value: 'electron(clear)'
        },
        {
          name: 'CRM Project',
          value: 'crm'
        },
        {
          name: 'Landing Project',
          value: 'landing'
        }
      ]
    }
    return {
      presetPrompt
    }
  }

  resolveOutroPrompts () {
    const outroPrompts = []

    // ask for packageManager once
    if (hasYarn() || hasPnpm3OrLater()) {
      const packageManagerChoices = []

      packageManagerChoices.push({
        name: 'Use NPM',
        value: 'npm',
        short: 'NPM'
      })

      if (hasYarn()) {
        packageManagerChoices.push({
          name: 'Use Yarn',
          value: 'yarn',
          short: 'Yarn'
        })
      }

      if (hasPnpm3OrLater()) {
        packageManagerChoices.push({
          name: 'Use PNPM',
          value: 'pnpm',
          short: 'PNPM'
        })
      }

      outroPrompts.push({
        name: 'packageManager',
        type: 'list',
        message: 'Pick the package manager to use when installing dependencies:',
        choices: packageManagerChoices
      })
    }

    outroPrompts.push({
      name: 'description',
      type: 'string',
      required: false,
      message: 'Project description',
      default: 'An vue project'
    })

    return outroPrompts
  }

  resolveFinalPrompts () {
    const prompts = [
      this.presetPrompt,
      ...this.injectedPrompts,
      ...this.outroPrompts
    ]
    debug('vue-cli:prompts')(prompts)
    return prompts
  }

  shouldInitGit (cliOptions) {
    if (!hasGit()) {
      return false
    }
    // --git
    if (cliOptions.forceGit) {
      return true
    }
    // --no-git
    if (cliOptions.git === false || cliOptions.git === 'false') {
      return false
    }
    // default: true unless already in a git repo
    return !hasProjectGit(this.context)
  }

  shouldBackend (preset) {
    if (preset === 'erp') return true

    return false
  }
}
