import chalk from 'chalk'
import process, { ChildProcessWithoutNullStreams } from 'child_process'
import inquirer, { Answers, InputQuestion, ListQuestion } from 'inquirer'
import jsonFormat from 'json-format'
import ora, { Ora } from 'ora'
import path from 'path'
import { log } from '../ulits/log'
import { regAppId, regCn, regEn } from '../ulits/ulits'
import Base from './base'
import { CreatePageFs } from './create-page-fs'

interface CreateProjectInterface {
  init(): void

  create(): Promise<void>

}

export default class CreateProject extends Base implements CreateProjectInterface {
  protected name: string = ''
  protected depict: string = ''
  protected appid: string = ''
  protected mode: 'javascript' | 'typescript' = 'javascript'
  protected css: 'wxss' | 'less' = 'wxss'

  constructor() {
    super()
  }

  public init(): void {
    inquirer.prompt([
      this.__projectName(),
      this.__projectDescription(),
      this.__projectAppId(),
      this.__projectMode(),
      this.__projectCss()
    ]).then(async ({name, depict, appid, mode, css}: Answers): Promise<void> => {
      this.name = name
      this.depict = depict
      this.appid = appid
      this.mode = mode
      this.css = css
      await this.create()
    }).catch((err) => {
      console.log(err)
    })
  }

  public __projectName(): InputQuestion<Answers> {
    const that = this
    return {
      type: 'input',
      name: 'name',
      message: '请输入项目名称：',
      async validate(input: string): Promise<boolean | string> {
        // 校验文件名是否符合规范
        return new Promise<boolean | string>(async (resolve) => {
          if (regEn.test(input) || regCn.test(input)) {
            // 验证项目名是否符合规则
            resolve(chalk.red('项目名不符合规则，请重新输入项目名！'))
          } else if (await that.checkFileIsExists(path.join(that.projectRoot, input))) {
            // 获取当前打开的目录，验证当前要创建的项目是否跟当前目录里的文件夹命名冲突
            resolve(chalk.red('该项目已创建，请重新输入项目名！'))
          }
          resolve(true)
        })
      },
      default: 'app'
    }
  }

  public __projectDescription(): InputQuestion<Answers> {
    return {
      type: 'input',
      name: 'depict',
      message: '请输入项目描述：'
    }
  }

  public __projectAppId(): InputQuestion<Answers> {
    return {
      type: 'input',
      name: 'appid',
      message: (answers) => `请输入[${answers.name}]的AppId：`,
      async validate(input: string): Promise<boolean | string> {
        return new Promise<boolean | string>((resolve) => {
          if (!regAppId.test(input)) resolve(chalk.red('请输入正确的AppId！'))
          resolve(true)
        })
      }
    }
  }

  public __projectMode(): ListQuestion<Answers> {
    return {
      type: 'list',
      name: 'mode',
      message: '选择项目脚本语言：',
      choices: [
        'javascript',
        'typescript'
      ]
    }
  }

  public __projectCss(): ListQuestion<Answers> {
    return {
      type: 'list',
      name: 'css',
      message: '选择项目样式语言：',
      choices: [
        'wxss',
        'less'
      ]
    }
  }

  private async _copyTypeFiles(copyRoot, projectRoot, type): Promise<void> {
    const root: string = path.join(copyRoot, type)
    const files: string[] = await this.readDirs(root)
    await this.copyFilesArr(root, projectRoot, files)
      .catch(({result}) => this.fileIsRepeat(result))
  }

  public async create(): Promise<void> {
    const loading: Ora = ora('creating project...').start()
    // 创建项目文件夹
    let projectRoot: string = path.join(this.projectRoot, this.name)
    await this.makeDir(projectRoot)
    // 获取模板文件
    let tempRoot: string = path.join(this.templateRoot, 'project')
    let files: string[] = await this.readDirs(tempRoot)
    // 创建小程序默认文件
    await this.copyFilesArr(tempRoot, projectRoot, files)
    // 创建脚本文件
    this._copyTypeFiles(tempRoot, projectRoot, this.mode)
    // 创建样式文件
    this._copyTypeFiles(tempRoot, projectRoot, this.css)
    // 初始化npm项目
    const bash: ChildProcessWithoutNullStreams = process.spawn('bash')
    bash.on('error', () => loading.fail(chalk.red('初始化失败')))
    bash.on('close', async () => {
      loading.text = '初始化成功'
      // 修改npm配置文件
      await this._modifyPackageJson(projectRoot, 'package.json', loading)
      // 修改小程序配置文件
      await this._modifyProjectConfigJson(projectRoot, 'project.config.json', loading)
      // 创建默认页面
      let pageRoot: string = path.join(projectRoot, this.pageRoot)
      await this.makeDir(pageRoot)
      CreatePageFs.create({
        root: projectRoot,
        name: 'index'
      }).then(() => {
        loading.succeed(chalk.green(`项目【${this.name}】创建成功`))
        log.table([
          ['项目名称', this.name],
          ['APPID', this.appid],
          ['项目描述', this.depict],
          ['项目路径', this.projectRoot],
          ['脚本语言', this.mode],
          ['样式语言', this.css],
          ['操作提示', `Please switch to the project directory!
Run ${chalk.bgWhite('cd ' + this.name)} to directory!
Run ${chalk.bgWhite('npm install')} to start!`]
        ], false)
      })
    })
    bash.stdin.write(`cd ${this.name}\n`)
    bash.stdin.write('npm init -y \n')
    bash.stdin.end()
  }

  private async _modifyProjectConfigJson(path: string, file: string, loading: Ora): Promise<void> {
    // 读取配置文件
    let json = JSON.parse(await this.readFile(path, file))
    let modify = {
      miniprogramRoot: this.miniprogramRoot,
      projectname: this.name,
      projectmode: this.mode,
      projectcss: this.css,
      appid: this.appid
    }
    await this.writeFile(path, file, jsonFormat({...json, ...modify}))
    loading.text = `修改${file}成功`
  }

  private async _modifyPackageJson(path: string, file: string, loading: Ora): Promise<void> {
    // 读取文件
    let json = JSON.parse(await this.readFile(path, file))
    let modify = {
      scripts: {
        compile: './node_modules/typescript/bin/tsc',
        tsc: 'node ./node_modules/typescript/lib/tsc.js'
      },
      description: this.depict ? this.depict : 'project depict...',
      devDependencies: {
        typescript: '^3.5.3'
      },
      dependencies: {
        'miniprogram-api-typings': '^2.7.7',
        'tslint-config-alloy': '^0.2.1',
        'tslint-eslint-rules': '^5.4.0'
      }
    }
    await this.writeFile(path, file, jsonFormat({...json, ...modify}))
    loading.text = `修改${file}成功`
  }
}

export const CreateProjectFs = new CreateProject()
