const fs = require('fs')
const path = require('path')
const updateComponents = require('./component')

const tmpDir = path.resolve(__dirname, '../../.tmp')

function writeFileSync (filename, content) {
  fs.writeFileSync(path.resolve(tmpDir, filename), content, 'utf8')
}

function parseImportPath (filepath) {
  if (filepath.indexOf('/platforms') === 0) { // api,appComponents(h5),appMixins(h5),systemRoutes(h5)
    return filepath.replace('/platforms/' + process.env.UNI_PLATFORM, 'uni-platform')
  } else if (filepath.indexOf('/core/helpers') === 0) { // protocol
    return filepath.replace('/core/helpers', 'uni-helpers')
  } else if (filepath.indexOf('/core/view') === 0) { // subscribe
    return filepath.replace('/core/view', 'uni-view')
  } else if (filepath.indexOf('/core') === 0) { // api
    return filepath.replace('/core', 'uni-core')
  }
  return filepath
}

function updateExportDefaultObject (paths, filename, isMulti = true, isExportArray = false) {
  const imports = []
  const exports = []
  Object.keys(paths).forEach(name => {
    if (isMulti) {
      imports.push(`import {${name}} from '${parseImportPath(paths[name])}'`)
    } else {
      imports.push(`import ${name} from '${parseImportPath(paths[name])}'`)
    }
    exports.push(name)
  })
  let content = isExportArray ? 'export default []' : 'export default {}'
  if (exports.length) {
    if (isExportArray) {
      content = `
      ${imports.join('\n')}
      export default [
        ${exports.join(',\n')}
      ]
      `
    } else {
      content = `
      ${imports.join('\n')}
      export default {
        ${exports.join(',\n')}
      }
      `
    }
  }
  writeFileSync(filename, content)
}

function updateApi (paths) {
  return updateExportDefaultObject(paths, 'api.js')
}

function updateApiProtocol (paths) {
  return updateExportDefaultObject(paths, 'protocol.js')
}

function updateApiSubscribe (paths) {
  return updateExportDefaultObject(paths, 'subscribe.js')
}

function updateInvokeApi (paths) {
  return updateExportDefaultObject(paths, 'invoke-api.js')
}

function updateAppComponents (paths) {
  return updateExportDefaultObject(paths, 'app-components.js', false)
}

function updateCoreComponents (paths) {
  const tags = process.UNI_TAGS || new Set()
  Object.keys(paths).forEach(tag => tags.add(tag.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()))
  updateComponents(tags)
}

function updateAppMixins (paths) {
  return updateExportDefaultObject(paths, 'app-mixins.js', false, true)
}

function updateSystemRoutes (paths) {
  return updateExportDefaultObject(paths, 'system-routes.js', false)
}

const isProtocol = filepath => {
  return filepath.indexOf('/core/helpers/protocol') === 0
}

const isPlatformApi = filepath => {
  return filepath.indexOf('/platforms/' + process.env.UNI_PLATFORM + '/service/api') === 0
}

const isAppComponents = filepath => {
  return path.extname(filepath) === '.vue' &&
    filepath.indexOf('/platforms/' + process.env.UNI_PLATFORM + '/components/app/') === 0
}

const isCoreComponents = filepath => {
  return path.extname(filepath) === '.vue' &&
    (filepath.indexOf('/core/view/components/') === 0 || filepath.indexOf('/platforms/' + process.env.UNI_PLATFORM +
      '/view/components/') === 0)
}

const isAppMixins = filepath => {
  return path.extname(filepath) === '.js' &&
    filepath.indexOf('/platforms/' + process.env.UNI_PLATFORM + '/components/app/') === 0
}

const isSystemRoutes = filepath => {
  return filepath.indexOf('/platforms/' + process.env.UNI_PLATFORM + '/components/system-routes') === 0
}

const isApiSubscribe = filepath => {
  return filepath.indexOf('/core/view/bridge/subscribe/api') === 0
}

function parseDeps (apis, manifest) {
  const apiPaths = Object.create(null)
  const apiProtocolPaths = Object.create(null)
  const invokeApiPaths = Object.create(null)
  const appComponentsPaths = Object.create(null)
  const coreComponentsPaths = Object.create(null)
  const appMixinsPaths = Object.create(null)
  const systemRoutesPaths = Object.create(null)
  const apiSubscribePaths = Object.create(null)

  const strategies = [{
    test: isProtocol,
    paths: apiProtocolPaths
  }, {
    test: isPlatformApi,
    paths: apiPaths
  }, {
    test: isAppComponents,
    paths: appComponentsPaths
  }, {
    test: isCoreComponents,
    paths: coreComponentsPaths
  }, {
    test: isAppMixins,
    paths: appMixinsPaths
  }, {
    test: isSystemRoutes,
    paths: systemRoutesPaths
  }, {
    test: isApiSubscribe,
    paths: apiSubscribePaths
  }]
  // 固定顺序，避免因顺序的变化导致内容变化，从而生成不同的 hash 文件名
  const apiNames = [...apis].sort()
  for (const name of apiNames) {
    const options = manifest[name]
    if (Array.isArray(options)) {
      apiPaths[name] = options[0]
      const deps = options[1]
      if (!Array.isArray(deps) || !deps.length) {
        continue
      }

      const isCoreApi = !isPlatformApi(options[0])

      deps.forEach(dep => {
        const filepath = dep[0]
        const exports = dep[1]

        if (isCoreApi && isPlatformApi(filepath)) { // invoke-api
          invokeApiPaths[exports] = filepath
        } else {
          const strategy = strategies.find(strategy => {
            return strategy.test(filepath)
          })
          if (strategy) {
            strategy.paths[exports] = filepath
          } else {
            console.log('dep', name, dep)
            console.warn(`${filepath} 未识别`)
          }
        }
      })
    } else {
      // console.warn(`${process.env.UNI_PLATFORM} 平台不支持 uni.${name}`)
    }
  }

  return {
    apiPaths,
    apiProtocolPaths,
    invokeApiPaths,
    appComponentsPaths,
    coreComponentsPaths,
    appMixinsPaths,
    systemRoutesPaths,
    apiSubscribePaths
  }
}

module.exports = function updateApis (apis = new Set(), userApis = new Set()) {
  if (!fs.existsSync(tmpDir)) {
    fs.mkdirSync(tmpDir)
  }

  const manifest = require('@dcloudio/uni-' + process.env.UNI_PLATFORM + '/manifest.json')
  // autoload
  Object.keys(manifest).forEach(name => {
    if (manifest[name][2]) {
      apis.add(name)
    }
  })

  apis = new Set([...apis, ...userApis])

  if (process.UNI_TAGS) {
    // TODO 临时硬编码
    if (process.UNI_TAGS.has('map')) {
      apis.add('getLocation')
      apis.add('stopCompass')
      apis.add('onCompassChange')
    }
  }

  const {
    apiPaths,
    apiProtocolPaths,
    invokeApiPaths,
    apiSubscribePaths,
    appComponentsPaths,
    coreComponentsPaths,
    appMixinsPaths,
    systemRoutesPaths
  } = parseDeps(apis, manifest)

  updateApi(apiPaths)
  updateApiProtocol(apiProtocolPaths)
  updateApiSubscribe(apiSubscribePaths)

  updateInvokeApi(invokeApiPaths)

  updateAppComponents(appComponentsPaths)
  updateCoreComponents(coreComponentsPaths)
  updateAppMixins(appMixinsPaths)
  updateSystemRoutes(systemRoutesPaths)
}
