import * as fs from 'fs-extra'
import * as path from 'path'

import { AppConfig, TabBar } from '@tarojs/taro'
import * as prettier from 'prettier'
import traverse, { NodePath } from 'babel-traverse'
import * as t from 'babel-types'
import * as taroize from '@tarojs/taroize'
import wxTransformer from '@tarojs/transformer-wx'
import * as postcss from 'postcss'
import * as unitTransform from 'postcss-taro-unit-transform'
// import * as inquirer from 'inquirer'

import {
  printLog,
  promoteRelativePath,
  resolveScriptPath,
  CSS_IMPORT_REG,
  pascalCase,
  emptyDirectory,
  processTypeEnum,
  REG_TYPESCRIPT,
  REG_URL,
  REG_IMAGE,
  chalk
} from '@tarojs/helper'
import { generateMinimalEscapeCode } from '../util/astConvert'
import Creator from '../create/creator'
import babylonConfig from '../config/babylon'
import { analyzeImportUrl, incrementId } from './helper'
import { getPkgVersion } from '../util'

const template = require('babel-template')

const prettierJSConfig: prettier.Options = {
  semi: false,
  singleQuote: true,
  parser: 'babel'
}

const OUTPUT_STYLE_EXTNAME = '.scss'

const WX_GLOBAL_FN = new Set<string>(['getApp', 'getCurrentPages', 'requirePlugin', 'Behavior'])

interface IComponent {
  name: string;
  path: string;
}

interface IImport {
  ast: t.File;
  name: string;
  wxs?: boolean;
}

interface IParseAstOptions {
  ast: t.File;
  sourceFilePath: string;
  outputFilePath: string;
  importStylePath?: string | null;
  depComponents?: Set<IComponent>;
  imports?: IImport[];
  isApp?: boolean;
}

interface ITaroizeOptions {
  json?: string;
  script?: string;
  wxml?: string;
  path?: string;
  rootPath?: string
}

function processStyleImports (content: string, processFn: (a: string, b: string) => string) {
  const style: string[] = []
  const imports: string[] = []
  const styleReg = new RegExp('.wxss')
  content = content.replace(CSS_IMPORT_REG, (m, $1, $2) => {
    if (styleReg.test($2)) {
      style.push(m)
      imports.push($2)
      if (processFn) {
        return processFn(m, $2)
      }
      return ''
    }
    if (processFn) {
      return processFn(m, $2)
    }
    return m
  })
  return {
    content,
    style,
    imports
  }
}

export default class Convertor {
  root: string
  convertRoot: string
  convertDir: string
  importsDir: string
  fileTypes: any
  pages: Set<string>
  components: Set<IComponent>
  hadBeenCopyedFiles: Set<string>
  hadBeenBuiltComponents: Set<string>
  hadBeenBuiltImports: Set<string>
  entryJSPath: string
  entryJSONPath: string
  entryStylePath: string
  entryJSON: AppConfig & {usingComponents?: Record<string, string>}
  entryStyle: string
  entryUsingComponents: Record<string, string>
  framework: 'react' | 'vue'

  constructor (root) {
    this.root = root
    this.convertRoot = path.join(this.root, 'taroConvert')
    this.convertDir = path.join(this.convertRoot, 'src')
    this.importsDir = path.join(this.convertDir, 'imports')
    this.fileTypes = {
      TEMPL: '.wxml',
      STYLE: '.wxss',
      CONFIG: '.json',
      SCRIPT: '.js'
    }
    this.pages = new Set<string>()
    this.components = new Set<IComponent>()
    this.hadBeenCopyedFiles = new Set<string>()
    this.hadBeenBuiltComponents = new Set<string>()
    this.hadBeenBuiltImports = new Set<string>()
    this.init()
  }

  init () {
    console.log(chalk.green('开始代码转换...'))
    this.initConvert()
    this.getApp()
    this.getPages()
    this.getSitemapLocation()
    this.getSubPackages()
  }

  initConvert () {
    if (fs.existsSync(this.convertRoot)) {
      emptyDirectory(this.convertRoot, { excludes: ['node_modules'] })
    } else {
      fs.ensureDirSync(this.convertRoot)
    }
  }

  wxsIncrementId = incrementId()

  parseAst ({
    ast,
    sourceFilePath,
    outputFilePath,
    importStylePath,
    depComponents,
    imports = []
  }: IParseAstOptions): { ast: t.File; scriptFiles: Set<string> } {
    const scriptFiles = new Set<string>()
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    let componentClassName: string
    let needInsertImportTaro = false
    traverse(ast, {
      Program: {
        enter (astPath) {
          astPath.traverse({
            ClassDeclaration (astPath) {
              const node = astPath.node
              let isTaroComponent = false
              if (node.superClass) {
                astPath.traverse({
                  ClassMethod (astPath) {
                    if (astPath.get('key').isIdentifier({ name: 'render' })) {
                      astPath.traverse({
                        JSXElement () {
                          isTaroComponent = true
                        }
                      })
                    }
                  }
                })
                if (isTaroComponent) {
                  componentClassName = node.id.name
                }
              }
            },

            ClassExpression (astPath) {
              const node = astPath.node
              if (node.superClass) {
                let isTaroComponent = false
                astPath.traverse({
                  ClassMethod (astPath) {
                    if (astPath.get('key').isIdentifier({ name: 'render' })) {
                      astPath.traverse({
                        JSXElement () {
                          isTaroComponent = true
                        }
                      })
                    }
                  }
                })
                if (isTaroComponent) {
                  if (node.id === null) {
                    const parentNode = astPath.parentPath.node as t.VariableDeclarator
                    if (t.isVariableDeclarator(astPath.parentPath)) {
                      componentClassName = (parentNode.id as t.Identifier).name
                    }
                  } else {
                    componentClassName = node.id.name
                  }
                }
              }
            },
            ExportDefaultDeclaration (astPath) {
              const node = astPath.node
              const declaration = node.declaration
              if (declaration && (declaration.type === 'ClassDeclaration' || declaration.type === 'ClassExpression')) {
                const superClass = declaration.superClass
                if (superClass) {
                  let isTaroComponent = false
                  astPath.traverse({
                    ClassMethod (astPath) {
                      if (astPath.get('key').isIdentifier({ name: 'render' })) {
                        astPath.traverse({
                          JSXElement () {
                            isTaroComponent = true
                          }
                        })
                      }
                    }
                  })
                  if (isTaroComponent) {
                    componentClassName = declaration.id.name
                  }
                }
              }
            },
            ImportDeclaration (astPath) {
              const node = astPath.node
              const source = node.source
              const value = source.value
              analyzeImportUrl(self.root, sourceFilePath, scriptFiles, source, value)
            },
            CallExpression (astPath) {
              const node = astPath.node
              const calleePath = astPath.get('callee')
              const callee = calleePath.node
              if (callee.type === 'Identifier') {
                if (callee.name === 'require') {
                  const args = node.arguments as Array<t.StringLiteral>
                  const value = args[0].value
                  analyzeImportUrl(self.root, sourceFilePath, scriptFiles, args[0], value)
                } else if (WX_GLOBAL_FN.has(callee.name)) {
                  calleePath.replaceWith(t.memberExpression(t.identifier('Taro'), callee as t.Identifier))
                  needInsertImportTaro = true
                }
              }
            },

            MemberExpression (astPath) {
              const node = astPath.node
              const object = node.object
              if (t.isIdentifier(object) && object.name === 'wx') {
                node.object = t.identifier('Taro')
                needInsertImportTaro = true
              }
            }
          })
        },
        exit (astPath) {
          const bodyNode = astPath.get('body') as NodePath<t.Node>[]
          const lastImport = bodyNode.filter(p => p.isImportDeclaration()).pop()
          const hasTaroImport = bodyNode.some(p => p.isImportDeclaration() && p.node.source.value === '@tarojs/taro')
          if (needInsertImportTaro && !hasTaroImport) {
            (astPath.node as t.Program).body.unshift(
              t.importDeclaration([t.importDefaultSpecifier(t.identifier('Taro'))], t.stringLiteral('@tarojs/taro'))
            )
          }
          astPath.traverse({
            StringLiteral (astPath) {
              const value = astPath.node.value
              const extname = path.extname(value)
              if (extname && REG_IMAGE.test(extname) && !REG_URL.test(value)) {
                let sourceImagePath: string
                if (path.isAbsolute(value)) {
                  sourceImagePath = path.join(self.root, value)
                } else {
                  sourceImagePath = path.resolve(sourceFilePath, '..', value)
                }
                const imageRelativePath = promoteRelativePath(path.relative(sourceFilePath, sourceImagePath))
                const outputImagePath = self.getDistFilePath(sourceImagePath)
                if (fs.existsSync(sourceImagePath)) {
                  self.copyFileToTaro(sourceImagePath, outputImagePath)
                  printLog(processTypeEnum.COPY, '图片', self.generateShowPath(outputImagePath))
                } else if (!t.isBinaryExpression(astPath.parent) || astPath.parent.operator !== '+') {
                  printLog(processTypeEnum.ERROR, '图片不存在', self.generateShowPath(sourceImagePath))
                }
                if (astPath.parentPath.isVariableDeclarator()) {
                  astPath.replaceWith(t.callExpression(t.identifier('require'), [t.stringLiteral(imageRelativePath)]))
                } else if (astPath.parentPath.isJSXAttribute()) {
                  astPath.replaceWith(
                    t.jSXExpressionContainer(
                      t.callExpression(t.identifier('require'), [t.stringLiteral(imageRelativePath)])
                    )
                  )
                }
              }
            }
          })
          if (lastImport) {
            if (importStylePath) {
              lastImport.insertAfter(
                t.importDeclaration(
                  [],
                  t.stringLiteral(promoteRelativePath(path.relative(sourceFilePath, importStylePath)))
                )
              )
            }
            if (imports && imports.length) {
              imports.forEach(({ name, ast, wxs }) => {
                const importName = wxs ? name : pascalCase(name)
                if (componentClassName === importName) {
                  return
                }
                const importPath = path.join(self.importsDir, importName + (wxs ? self.wxsIncrementId() : '') + '.js')
                if (!self.hadBeenBuiltImports.has(importPath)) {
                  self.hadBeenBuiltImports.add(importPath)
                  self.writeFileToTaro(importPath, prettier.format(generateMinimalEscapeCode(ast), prettierJSConfig))
                }
                lastImport.insertAfter(
                  template(
                    `import ${importName} from '${promoteRelativePath(path.relative(outputFilePath, importPath))}'`,
                    babylonConfig
                  )()
                )
              })
            }
            if (depComponents && depComponents.size) {
              depComponents.forEach(componentObj => {
                const name = pascalCase(componentObj.name)
                const component = componentObj.path
                lastImport.insertAfter(
                  template(
                    `import ${name} from '${promoteRelativePath(path.relative(sourceFilePath, component))}'`,
                    babylonConfig
                  )()
                )
              })
            }
          }
        }
      }
    })

    return {
      ast,
      scriptFiles
    }
  }

  getApp () {
    this.entryJSPath = path.join(this.root, `app${this.fileTypes.SCRIPT}`)
    this.entryJSONPath = path.join(this.root, `app${this.fileTypes.CONFIG}`)
    this.entryStylePath = path.join(this.root, `app${this.fileTypes.STYLE}`)
    try {
      this.entryJSON = JSON.parse(String(fs.readFileSync(this.entryJSONPath)))

      const using = this.entryJSON.usingComponents
      if (using && Object.keys(using).length) {
        for (const key in using) {
          if (using[key].startsWith('plugin://')) continue
          const componentPath = using[key]
          using[key] = path.join(this.root, componentPath)
        }
        this.entryUsingComponents = using
        delete this.entryJSON.usingComponents
      }

      printLog(processTypeEnum.CONVERT, '入口文件', this.generateShowPath(this.entryJSPath))
      printLog(processTypeEnum.CONVERT, '入口配置', this.generateShowPath(this.entryJSONPath))
      if (fs.existsSync(this.entryStylePath)) {
        this.entryStyle = String(fs.readFileSync(this.entryStylePath))
        printLog(processTypeEnum.CONVERT, '入口样式', this.generateShowPath(this.entryStylePath))
      }
    } catch (err) {
      this.entryJSON = {}
      console.log(chalk.red(`app${this.fileTypes.CONFIG} 读取失败，请检查！`))
      process.exit(1)
    }
  }

  getPages () {
    const pages = this.entryJSON.pages
    if (!pages || !pages.length) {
      console.log(chalk.red(`app${this.fileTypes.CONFIG} 配置有误，缺少页面相关配置`))
      return
    }
    this.pages = new Set(pages)
  }

  getSubPackages () {
    const subPackages = this.entryJSON.subpackages || this.entryJSON.subPackages
    if (!subPackages || !subPackages.length) {
      return
    }
    subPackages.forEach(item => {
      if (item.pages && item.pages.length) {
        const root = item.root
        item.pages.forEach(page => {
          let pagePath = `${root}/${page}`
          pagePath = pagePath.replace(/\/{2,}/g, '/')
          this.pages.add(pagePath)
        })
      }
    })
  }

  getSitemapLocation () {
    // eslint-disable-next-line dot-notation
    const sitemapLocation = this.entryJSON['sitemapLocation']
    if (sitemapLocation) {
      const sitemapFilePath = path.join(this.root, sitemapLocation)
      if (fs.existsSync(sitemapFilePath)) {
        const outputFilePath = path.join(this.convertRoot, sitemapLocation)
        this.copyFileToTaro(sitemapFilePath, outputFilePath)
      }
    }
  }

  generateScriptFiles (files: Set<string>) {
    if (!files) {
      return
    }
    if (files.size) {
      files.forEach(file => {
        if (!fs.existsSync(file) || this.hadBeenCopyedFiles.has(file)) {
          return
        }
        const code = fs.readFileSync(file).toString()
        let outputFilePath = file.replace(this.root, this.convertDir)
        const extname = path.extname(outputFilePath)
        if (/\.wxs/.test(extname)) {
          outputFilePath += '.js'
        }
        const transformResult = wxTransformer({
          code,
          sourcePath: file,
          isNormal: true,
          isTyped: REG_TYPESCRIPT.test(file)
        })
        const { ast, scriptFiles } = this.parseAst({
          ast: transformResult.ast,
          outputFilePath,
          sourceFilePath: file
        })
        const jsCode = generateMinimalEscapeCode(ast)
        this.writeFileToTaro(outputFilePath, prettier.format(jsCode, prettierJSConfig))
        printLog(processTypeEnum.COPY, 'JS 文件', this.generateShowPath(outputFilePath))
        this.hadBeenCopyedFiles.add(file)
        this.generateScriptFiles(scriptFiles)
      })
    }
  }

  writeFileToTaro (dist: string, code: string) {
    fs.ensureDirSync(path.dirname(dist))
    fs.writeFileSync(dist, code)
  }

  copyFileToTaro (from: string, to: string, options?: fs.CopyOptionsSync) {
    const filename = path.basename(from)
    if (fs.statSync(from).isFile() && !path.extname(to)) {
      fs.ensureDir(to)
      return fs.copySync(from, path.join(to, filename), options)
    }
    fs.ensureDir(path.dirname(to))
    return fs.copySync(from, to, options)
  }

  getDistFilePath (src: string, extname?: string): string {
    if (!extname) return src.replace(this.root, this.convertDir)
    return src.replace(this.root, this.convertDir).replace(path.extname(src), extname)
  }

  getConfigFilePath (src: string) {
    const { dir, name } = path.parse(src)
    return path.join(dir, name + '.config.js')
  }

  writeFileToConfig (src: string, json = '{}') {
    const configSrc = this.getConfigFilePath(src)
    const code = `export default ${json}`
    this.writeFileToTaro(configSrc, prettier.format(code, prettierJSConfig))
  }

  generateShowPath (filePath: string): string {
    return filePath
      .replace(path.join(this.root, '/'), '')
      .split(path.sep)
      .join('/')
  }

  private formatFile (jsCode: string, template = '') {
    let code = jsCode
    const config = { ...prettierJSConfig }
    if (this.framework === 'vue') {
      code = `
${template}
<script>
${code}
</script>
      `
      config.parser = 'vue'
      config.semi = false
      config.htmlWhitespaceSensitivity = 'ignore'
    }
    return prettier.format(code, config)
  }

  generateEntry () {
    try {
      const entryJS = String(fs.readFileSync(this.entryJSPath))
      const entryJSON = JSON.stringify(this.entryJSON)
      const entryDistJSPath = this.getDistFilePath(this.entryJSPath)
      const taroizeResult = taroize({
        json: entryJSON,
        script: entryJS,
        path: this.root,
        rootPath: this.root,
        framework: this.framework,
        isApp: true
      })
      const { ast, scriptFiles } = this.parseAst({
        ast: taroizeResult.ast,
        sourceFilePath: this.entryJSPath,
        outputFilePath: entryDistJSPath,
        importStylePath: this.entryStyle
          ? this.entryStylePath.replace(path.extname(this.entryStylePath), OUTPUT_STYLE_EXTNAME)
          : null,
        isApp: true
      })
      const jsCode = generateMinimalEscapeCode(ast)
      this.writeFileToTaro(entryDistJSPath, jsCode)
      this.writeFileToConfig(entryDistJSPath, entryJSON)
      printLog(processTypeEnum.GENERATE, '入口文件', this.generateShowPath(entryDistJSPath))
      if (this.entryStyle) {
        this.traverseStyle(this.entryStylePath, this.entryStyle)
      }
      this.generateScriptFiles(scriptFiles)
      if (this.entryJSON.tabBar) {
        this.generateTabBarIcon(this.entryJSON.tabBar)
        this.generateCustomTabbar(this.entryJSON.tabBar)
      }
    } catch (err) {
      console.log(err)
    }
  }

  generateTabBarIcon (tabBar: TabBar) {
    const { list = [] } = tabBar
    const icons = new Set<string>()
    if (Array.isArray(list) && list.length) {
      list.forEach(item => {
        if (typeof item.iconPath === 'string') icons.add(item.iconPath)
        if (typeof item.selectedIconPath === 'string') icons.add(item.selectedIconPath)
      })
      if (icons.size > 0) {
        Array.from(icons)
          .map(icon => path.join(this.root, icon))
          .forEach(iconPath => {
            const iconDistPath = this.getDistFilePath(iconPath)
            this.copyFileToTaro(iconPath, iconDistPath)
            printLog(processTypeEnum.COPY, 'TabBar 图标', this.generateShowPath(iconDistPath))
          })
      }
    }
  }

  generateCustomTabbar (tabBar: TabBar) {
    if (!tabBar.custom) return

    const customTabbarPath = path.join(this.root, 'custom-tab-bar')
    if (fs.existsSync(customTabbarPath)) {
      const customTabbarDistPath = this.getDistFilePath(customTabbarPath)
      this.copyFileToTaro(customTabbarPath, customTabbarDistPath)
      printLog(processTypeEnum.COPY, '自定义 TabBar', this.generateShowPath(customTabbarDistPath))
    }
  }

  private getComponentDest (file: string) {
    if (this.framework === 'react') {
      return file
    }

    return path.join(path.dirname(file), path.basename(file, path.extname(file)) + '.vue')
  }

  traversePages () {
    this.pages.forEach(page => {
      const pagePath = path.join(this.root, page)
      const pageJSPath = pagePath + this.fileTypes.SCRIPT
      const pageDistJSPath = this.getDistFilePath(pageJSPath)
      const pageConfigPath = pagePath + this.fileTypes.CONFIG
      const pageStylePath = pagePath + this.fileTypes.STYLE
      const pageTemplPath = pagePath + this.fileTypes.TEMPL

      try {
        const depComponents = new Set<IComponent>()
        if (!fs.existsSync(pageJSPath)) {
          throw new Error(`页面 ${page} 没有 JS 文件！`)
        }
        const param: ITaroizeOptions = {}
        printLog(processTypeEnum.CONVERT, '页面文件', this.generateShowPath(pageJSPath))

        let pageConfig
        if (fs.existsSync(pageConfigPath)) {
          printLog(processTypeEnum.CONVERT, '页面配置', this.generateShowPath(pageConfigPath))
          const pageConfigStr = String(fs.readFileSync(pageConfigPath))
          pageConfig = JSON.parse(pageConfigStr)
        } else if (this.entryUsingComponents) {
          pageConfig = {}
        }
        if (pageConfig) {
          if (this.entryUsingComponents) {
            pageConfig.usingComponents = {
              ...pageConfig.usingComponents,
              ...this.entryUsingComponents
            }
          }
          const pageUsingComponents = pageConfig.usingComponents
          if (pageUsingComponents) {
            // 页面依赖组件
            const usingComponents = {}
            Object.keys(pageUsingComponents).forEach(component => {
              const unResolveComponentPath: string = pageUsingComponents[component]
              if (unResolveComponentPath.startsWith('plugin://')) {
                usingComponents[component] = unResolveComponentPath
              } else {
                let componentPath
                if (unResolveComponentPath.startsWith(this.root)) {
                  componentPath = unResolveComponentPath
                } else {
                  componentPath = path.resolve(pageConfigPath, '..', pageUsingComponents[component])
                  if (!fs.existsSync(resolveScriptPath(componentPath))) {
                    componentPath = path.join(this.root, pageUsingComponents[component])
                  }
                }

                depComponents.add({
                  name: component,
                  path: componentPath
                })
              }
            })
            if (Object.keys(usingComponents).length === 0) {
              delete pageConfig.usingComponents
            } else {
              pageConfig.usingComponents = usingComponents
            }
          }
          param.json = JSON.stringify(pageConfig)
        }

        param.script = String(fs.readFileSync(pageJSPath))
        if (fs.existsSync(pageTemplPath)) {
          printLog(processTypeEnum.CONVERT, '页面模板', this.generateShowPath(pageTemplPath))
          param.wxml = String(fs.readFileSync(pageTemplPath))
        }
        let pageStyle: string | null = null
        if (fs.existsSync(pageStylePath)) {
          printLog(processTypeEnum.CONVERT, '页面样式', this.generateShowPath(pageStylePath))
          pageStyle = String(fs.readFileSync(pageStylePath))
        }
        param.path = path.dirname(pageJSPath)
        param.rootPath = this.root
        const taroizeResult = taroize({
          ...param,
          framework: this.framework
        })
        const { ast, scriptFiles } = this.parseAst({
          ast: taroizeResult.ast,
          sourceFilePath: pageJSPath,
          outputFilePath: pageDistJSPath,
          importStylePath: pageStyle ? pageStylePath.replace(path.extname(pageStylePath), OUTPUT_STYLE_EXTNAME) : null,
          depComponents,
          imports: taroizeResult.imports
        })
        const jsCode = generateMinimalEscapeCode(ast)
        this.writeFileToTaro(this.getComponentDest(pageDistJSPath), this.formatFile(jsCode, taroizeResult.template))
        this.writeFileToConfig(pageDistJSPath, param.json)
        printLog(processTypeEnum.GENERATE, '页面文件', this.generateShowPath(pageDistJSPath))
        if (pageStyle) {
          this.traverseStyle(pageStylePath, pageStyle)
        }
        this.generateScriptFiles(scriptFiles)
        this.traverseComponents(depComponents)
      } catch (err) {
        printLog(processTypeEnum.ERROR, '页面转换', this.generateShowPath(pageJSPath))
        console.log(err)
      }
    })
  }

  traverseComponents (components: Set<IComponent>) {
    if (!components || !components.size) {
      return
    }
    components.forEach(componentObj => {
      const component = componentObj.path
      if (this.hadBeenBuiltComponents.has(component)) return
      this.hadBeenBuiltComponents.add(component)

      const componentJSPath = component + this.fileTypes.SCRIPT
      const componentDistJSPath = this.getDistFilePath(componentJSPath)
      const componentConfigPath = component + this.fileTypes.CONFIG
      const componentStylePath = component + this.fileTypes.STYLE
      const componentTemplPath = component + this.fileTypes.TEMPL

      try {
        const param: ITaroizeOptions = {}
        const depComponents = new Set<IComponent>()
        if (!fs.existsSync(componentJSPath)) {
          throw new Error(`组件 ${component} 没有 JS 文件！`)
        }
        printLog(processTypeEnum.CONVERT, '组件文件', this.generateShowPath(componentJSPath))
        if (fs.existsSync(componentConfigPath)) {
          printLog(processTypeEnum.CONVERT, '组件配置', this.generateShowPath(componentConfigPath))
          const componentConfigStr = String(fs.readFileSync(componentConfigPath))
          const componentConfig = JSON.parse(componentConfigStr)
          const componentUsingComponnets = componentConfig.usingComponents
          if (componentUsingComponnets) {
            // 页面依赖组件
            Object.keys(componentUsingComponnets).forEach(component => {
              let componentPath = path.resolve(componentConfigPath, '..', componentUsingComponnets[component])
              if (!fs.existsSync(resolveScriptPath(componentPath))) {
                componentPath = path.join(this.root, componentUsingComponnets[component])
              }
              depComponents.add({
                name: component,
                path: componentPath
              })
            })
            delete componentConfig.usingComponents
          }
          param.json = JSON.stringify(componentConfig)
        }
        param.script = String(fs.readFileSync(componentJSPath))
        if (fs.existsSync(componentTemplPath)) {
          printLog(processTypeEnum.CONVERT, '组件模板', this.generateShowPath(componentTemplPath))
          param.wxml = String(fs.readFileSync(componentTemplPath))
        }
        let componentStyle: string | null = null
        if (fs.existsSync(componentStylePath)) {
          printLog(processTypeEnum.CONVERT, '组件样式', this.generateShowPath(componentStylePath))
          componentStyle = String(fs.readFileSync(componentStylePath))
        }
        param.path = path.dirname(componentJSPath)
        param.rootPath = this.root
        const taroizeResult = taroize({
          ...param,
          framework: this.framework
        })
        const { ast, scriptFiles } = this.parseAst({
          ast: taroizeResult.ast,
          sourceFilePath: componentJSPath,
          outputFilePath: componentDistJSPath,
          importStylePath: componentStyle
            ? componentStylePath.replace(path.extname(componentStylePath), OUTPUT_STYLE_EXTNAME)
            : null,
          depComponents,
          imports: taroizeResult.imports
        })
        const jsCode = generateMinimalEscapeCode(ast)
        this.writeFileToTaro(this.getComponentDest(componentDistJSPath), this.formatFile(jsCode, taroizeResult.template))
        printLog(processTypeEnum.GENERATE, '组件文件', this.generateShowPath(componentDistJSPath))
        if (componentStyle) {
          this.traverseStyle(componentStylePath, componentStyle)
        }
        this.generateScriptFiles(scriptFiles)
        this.traverseComponents(depComponents)
      } catch (err) {
        printLog(processTypeEnum.ERROR, '组件转换', this.generateShowPath(componentJSPath))
        console.log(err)
      }
    })
  }

  async styleUnitTransform (filePath: string, content: string) {
    const postcssResult = await postcss([unitTransform()]).process(content, {
      from: filePath
    })
    return postcssResult
  }

  processStyleAssets (content: string, stylePath: string, styleDist: string) {
    const reg = /url\(["'](.+?)["']\)/g
    let token = reg.exec(content)
    stylePath = path.dirname(stylePath)
    styleDist = path.dirname(styleDist)

    while (token?.length) {
      let url = token[1]

      if (
        url &&
        url.indexOf('data:') !== 0 &&
        url.indexOf('#') !== 0 &&
        !(/^[a-z]+:\/\//.test(url))
      ) {
        url = url.trim()
        url.replace(/[/\\]/g, path.sep)
        url = url.split('?')[0]
        url = url.split('#')[0]

        const originPath = path.resolve(stylePath, url)
        const destPath = path.resolve(styleDist, url)
        const destDir = path.dirname(destPath)

        if (!fs.existsSync(originPath)) {
          printLog(processTypeEnum.WARNING, '静态资源', `找不到资源：${originPath}`)
        } else if (!fs.existsSync(destPath)) {
          fs.ensureDirSync(destDir)
          fs.copyFile(originPath, destPath)
          printLog(processTypeEnum.COPY, '样式资源', this.generateShowPath(destPath))
        }
      }

      token = reg.exec(content)
    }
  }

  async traverseStyle (filePath: string, style: string) {
    const { imports, content } = processStyleImports(style, (str, stylePath) => {
      let relativePath = stylePath
      if (path.isAbsolute(relativePath)) {
        relativePath = promoteRelativePath(path.relative(filePath, path.join(this.root, stylePath)))
      }
      return str.replace(stylePath, relativePath).replace('.wxss', OUTPUT_STYLE_EXTNAME)
    })
    const styleDist = this.getDistFilePath(filePath, OUTPUT_STYLE_EXTNAME)
    this.processStyleAssets(content, filePath, styleDist)
    const { css } = await this.styleUnitTransform(filePath, content)
    this.writeFileToTaro(styleDist, css)
    printLog(processTypeEnum.GENERATE, '样式文件', this.generateShowPath(styleDist))
    if (imports && imports.length) {
      imports.forEach(importItem => {
        const importPath = path.isAbsolute(importItem)
          ? path.join(this.root, importItem)
          : path.resolve(path.dirname(filePath), importItem)
        if (fs.existsSync(importPath)) {
          const styleText = fs.readFileSync(importPath).toString()
          this.traverseStyle(importPath, styleText)
        }
      })
    }
  }

  generateConfigFiles () {
    const creator = new Creator()
    const templateName = 'default'
    const configDir = path.join(this.convertRoot, 'config')
    const pkgPath = path.join(this.convertRoot, 'package.json')
    const projectName = 'taroConvert'
    const description = ''
    const version = getPkgVersion()
    const dateObj = new Date()
    const date = `${dateObj.getFullYear()}-${dateObj.getMonth() + 1}-${dateObj.getDate()}`
    creator.template(templateName, 'package.json.tmpl', pkgPath, {
      description,
      projectName,
      version,
      css: 'sass',
      typescript: false,
      template: templateName,
      framework: this.framework
    })
    creator.template(templateName, path.join('config', 'index.js'), path.join(configDir, 'index.js'), {
      date,
      projectName,
      framework: this.framework
    })
    creator.template(templateName, path.join('config', 'dev.js'), path.join(configDir, 'dev.js'), {
      framework: this.framework
    })
    creator.template(templateName, path.join('config', 'prod.js'), path.join(configDir, 'prod.js'), {
      framework: this.framework
    })
    creator.template(templateName, 'project.config.json', path.join(this.convertRoot, 'project.config.json'), {
      description,
      projectName,
      framework: this.framework
    })
    creator.template(templateName, '.gitignore', path.join(this.convertRoot, '.gitignore'))
    creator.template(templateName, '.editorconfig', path.join(this.convertRoot, '.editorconfig'))
    creator.template(templateName, '.eslintrc.js', path.join(this.convertRoot, '.eslintrc.js'), {
      typescript: false,
      framework: this.framework
    })
    creator.template(templateName, 'babel.config.js', path.join(this.convertRoot, 'babel.config.js'), {
      typescript: false,
      framework: this.framework
    })
    creator.template(templateName, path.join('src', 'index.html'), path.join(this.convertDir, 'index.html'))
    creator.fs.commit(() => {
      const pkgObj = JSON.parse(fs.readFileSync(pkgPath).toString())
      pkgObj.dependencies['@tarojs/with-weapp'] = `^${version}`
      fs.writeJSONSync(pkgPath, pkgObj, {
        spaces: 2,
        EOL: '\n'
      })
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'index.js')))
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'dev.js')))
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'prod.js')))
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(pkgPath))
      printLog(
        processTypeEnum.GENERATE,
        '文件',
        this.generateShowPath(path.join(this.convertRoot, 'project.config.json'))
      )
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.gitignore')))
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.editorconfig')))
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.eslintrc')))
      printLog(processTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertDir, 'index.html')))
      this.showLog()
    })
  }

  showLog () {
    console.log()
    console.log(
      `${chalk.green('✔ ')} 转换成功，请进入 ${chalk.bold(
        'taroConvert'
      )} 目录下使用 npm 或者 yarn 安装项目依赖后再运行！`
    )
  }

  run () {
    // inquirer.prompt([{
    //   type: 'list',
    //   name: 'framework',
    //   message: '你想转换为哪种框架？',
    //   choices: ['react', 'vue'],
    //   default: 'react'
    // }]).then(({ framework }) => {
    this.framework = 'react'
    this.generateEntry()
    this.traversePages()
    this.generateConfigFiles()
    // })
  }
}
