const path = require('path')

const {
  normalizePath
} = require('@dcloudio/uni-cli-shared')

const {
  getPageSet,
  getJsonFileMap,
  getChangedJsonFileMap,
  supportGlobalUsingComponents
} = require('@dcloudio/uni-cli-shared/lib/cache')

// 主要解决 extends 且未实际引用的组件
const EMPTY_COMPONENT = 'Component({})'

const usingComponentsMap = {}

// 百度小程序动态组件库 usingSwanComponents 引用组件
const mpBaiduDynamicLibs = [
  'dynamicLib://editorLib/editor',
  'dynamicLib://echartsLib/chart',
  'dynamicLib://myModelviewer/modelviewer',
  'dynamicLib://myDynamicLib/panoviewer',
  'dynamicLib://myDynamicLib/spintileviewer',
  'dynamicLib://myDynamicLib/vrvideo'
]

const AnalyzeDependency = require('@dcloudio/uni-mp-weixin/lib/independent-plugins/optimize-components-position/index')

function analyzeUsingComponents () {
  if (!process.env.UNI_OPT_SUBPACKAGES) {
    return
  }
  const pageSet = getPageSet()
  const jsonFileMap = getJsonFileMap()

  // 生成所有组件引用关系
  for (const name of jsonFileMap.keys()) {
    const jsonObj = JSON.parse(jsonFileMap.get(name))
    const usingComponents = jsonObj.usingComponents
    if (!usingComponents || !pageSet.has(name)) {
      continue
    }
    // usingComponentsMap[name] = {}

    Object.keys(usingComponents).forEach(componentName => {
      const componentPath = usingComponents[componentName].slice(1)
      if (!usingComponentsMap[componentPath]) {
        usingComponentsMap[componentPath] = new Set()
      }
      usingComponentsMap[componentPath].add(name)
    })
  }

  const subPackageRoots = Object.keys(process.UNI_SUBPACKAGES)

  const findSubPackage = function (pages) {
    const pkgs = new Set()
    for (let i = 0; i < pages.length; i++) {
      const pagePath = pages[i]
      const pkgRoot = subPackageRoots.find(root => pagePath.indexOf(root) === 0)
      if (!pkgRoot) { // 被非分包引用
        return false
      }
      pkgs.add(pkgRoot)
      if (pkgs.size > 1) { // 被多个分包引用
        return false
      }
    }
    return [...pkgs][0]
  }

  Object.keys(usingComponentsMap).forEach(componentName => {
    const subPackage = findSubPackage([...usingComponentsMap[componentName]])
    if (subPackage && componentName.indexOf(subPackage) !== 0) { // 仅存在一个子包引用且未在该子包
      console.warn(`自定义组件 ${componentName} 建议移动到子包 ${subPackage} 内`)
    }
  })

  // 生成所有组件递归引用关系
  //   Object.keys(usingComponentsMap).forEach(name => {
  //     Object.keys(usingComponentsMap[name]).forEach(componentName => {
  //       const usingComponents = usingComponentsMap[componentName.slice(1)]
  //       if (usingComponents) {
  //         usingComponentsMap[name][componentName] = usingComponents
  //       }
  //     })
  //   })
  //
  //   // 生成页面组件引用关系
  //   const pageSet = getPageSet()
  //   const pagesUsingComponents = Object.keys(usingComponentsMap).reduce((pages, name) => {
  //     if (pageSet.has(name)) {
  //       pages[name] = usingComponentsMap[name]
  //     }
  //     return pages
  //   }, {})
}

const parseRequirePath = path => /^[A-z]/.test(path) ? `./${path}` : path

function normalizeUsingComponents (file, usingComponents) {
  const names = Object.keys(usingComponents)
  if (!names.length) {
    return usingComponents
  }
  file = path.dirname('/' + file)
  names.forEach(name => {
    usingComponents[name] = normalizePath(parseRequirePath(path.relative(file, usingComponents[name])))
  })
  return usingComponents
}

const cacheFileMap = new Map()
module.exports = function generateJson (compilation) {
  analyzeUsingComponents()

  const emitFileMap = new Map([...cacheFileMap])
  const jsonFileMap = getChangedJsonFileMap()
  for (const name of jsonFileMap.keys()) {
    const jsonObj = JSON.parse(jsonFileMap.get(name))
    if (process.env.UNI_PLATFORM === 'app-plus') { // App平台默认增加usingComponents,激活__wxAppCode__
      jsonObj.usingComponents = jsonObj.usingComponents || {}
    }
    // customUsingComponents
    if (jsonObj.customUsingComponents && Object.keys(jsonObj.customUsingComponents).length) {
      jsonObj.usingComponents = Object.assign(jsonObj.customUsingComponents, jsonObj.usingComponents)
    }
    delete jsonObj.customUsingComponents
    // usingGlobalComponents
    if (!supportGlobalUsingComponents && jsonObj.usingGlobalComponents && Object.keys(jsonObj.usingGlobalComponents).length) {
      jsonObj.usingComponents = Object.assign(jsonObj.usingGlobalComponents, jsonObj.usingComponents)
    }

    // usingAutoImportComponents
    if (jsonObj.usingAutoImportComponents && Object.keys(jsonObj.usingAutoImportComponents).length) {
      jsonObj.usingComponents = Object.assign(jsonObj.usingAutoImportComponents, jsonObj.usingComponents)
    }
    delete jsonObj.usingAutoImportComponents

    // 百度小程序插件内组件使用 usingSwanComponents
    if (process.env.UNI_PLATFORM === 'mp-baidu') {
      const usingComponents = jsonObj.usingComponents || {}
      Object.keys(usingComponents).forEach(key => {
        const value = usingComponents[key]
        if (value.includes('://')) {
          /**
           * 部分动态库组件（如：editor）使用‘usingSwanComponents’ 引入
           * 部分动态库组件（如：swan-sitemap-list）使用'usingComponents'引入
           * 做白名单机制
           */
          if (mpBaiduDynamicLibs.includes(value)) {
            delete usingComponents[key]
            jsonObj.usingSwanComponents = jsonObj.usingSwanComponents || {}
            jsonObj.usingSwanComponents[key] = value
          }
        }
      })
    }
    // fix mp-alipay plugin
    if (process.env.UNI_PLATFORM === 'mp-alipay' && name !== 'app.json') {
      const usingComponents = jsonObj.usingComponents || {}
      if (Object.values(usingComponents).find(value => value.startsWith('plugin://'))) {
        const componentName = 'plugin-wrapper'
        usingComponents[componentName] = '/' + componentName
      }
    }

    if (jsonObj.genericComponents && jsonObj.genericComponents.length) { // scoped slots
      // 生成genericComponents json
      const genericComponents = Object.create(null)

      const scopedSlotComponents = []
      jsonObj.genericComponents.forEach(genericComponentName => {
        const genericComponentFile = normalizePath(
          path.join(path.dirname(name), genericComponentName + '.json')
        )
        genericComponents[genericComponentName] = '/' +
          genericComponentFile.replace(
            path.extname(genericComponentFile), ''
          )
        scopedSlotComponents.push(genericComponentFile)
      })

      jsonObj.usingComponents = Object.assign(genericComponents, jsonObj.usingComponents)

      const scopedSlotComponentJson = {
        component: true,
        usingComponents: jsonObj.usingComponents
      }

      const scopedSlotComponentJsonSource = JSON.stringify(scopedSlotComponentJson, null, 2)

      scopedSlotComponents.forEach(scopedSlotComponent => {
        compilation.assets[scopedSlotComponent] = {
          size () {
            return Buffer.byteLength(scopedSlotComponentJsonSource, 'utf8')
          },
          source () {
            return scopedSlotComponentJsonSource
          }
        }
      })
    }

    delete jsonObj.genericComponents

    if (process.env.UNI_PLATFORM !== 'app-plus' && process.env.UNI_PLATFORM !== 'h5') {
      delete jsonObj.navigationBarShadow
    }

    if ((process.env.UNI_SUBPACKGE || process.env.UNI_MP_PLUGIN) && jsonObj.usingComponents) {
      jsonObj.usingComponents = normalizeUsingComponents(name, jsonObj.usingComponents)
    }

    emitFileMap.set(name, jsonObj)
    cacheFileMap.set(name, JSON.parse(JSON.stringify(jsonObj))) // 做一次拷贝，emitFileMap中内容在后面会被修改
  }

  // 组件依赖分析
  (new AnalyzeDependency()).init(emitFileMap, compilation)

  for (const [name, jsonObj] of emitFileMap) {
    if (name === 'app.json') { // 删除manifest.json携带的配置项
      delete jsonObj.insertAppCssToIndependent
      delete jsonObj.independent
      delete jsonObj.copyWxComponentsOnDemand
      if (process.env.UNI_PLATFORM === 'mp-weixin' && process.env.USE_UNI_AD) {
        require('./mp-weixin-uniad-app.json')(jsonObj)
      }
    } else { // 删除用于临时记录的属性
      delete jsonObj.usingGlobalComponents
    }
    emit(name, jsonObj, compilation)
  }

  if (process.env.UNI_USING_CACHE && jsonFileMap.size) {
    setTimeout(() => {
      require('@dcloudio/uni-cli-shared/lib/cache').store()
    }, 50)
  }
}

function emit (name, jsonObj, compilation) {
  if (jsonObj.usingComponents) {
    jsonObj.usingComponents = Object.assign({}, jsonObj.usingComponents)
  }
  const source = JSON.stringify(jsonObj, null, 2)

  const jsFile = name.replace('.json', '.js')
  if (
    ![
      'app.js',
      'manifest.js',
      'mini.project.js',
      'quickapp.config.js',
      'project.config.js',
      'project.swan.js'
    ].includes(
      jsFile) &&
      !compilation.assets[jsFile]
  ) {
    const jsFileAsset = {
      size () {
        return Buffer.byteLength(EMPTY_COMPONENT, 'utf8')
      },
      source () {
        return EMPTY_COMPONENT
      }
    }
    compilation.assets[jsFile] = jsFileAsset
  }
  const jsonAsset = {
    size () {
      return Buffer.byteLength(source, 'utf8')
    },
    source () {
      return source
    }
  }

  compilation.assets[name] = jsonAsset
}
