/* @flow */

const LIFECYCLE_HOOKS = [
  // App
  'onLaunch',
  'onShow',
  'onHide',
  'onUniNViewMessage',
  'onPageNotFound',
  'onThemeChange',
  'onError',
  'onUnhandledRejection',
  // Page
  'onInit',
  'onLoad',
  // 'onShow',
  'onReady',
  // 'onHide',
  'onUnload',
  'onPullDownRefresh',
  'onReachBottom',
  'onTabItemTap',
  'onAddToFavorites',
  'onShareTimeline',
  'onShareAppMessage',
  'onResize',
  'onPageScroll',
  'onNavigationBarButtonTap',
  'onBackPress',
  'onNavigationBarSearchInputChanged',
  'onNavigationBarSearchInputConfirmed',
  'onNavigationBarSearchInputClicked',
  'onNavigationBarSearchInputFocusChanged',
  // Component
  // 'onReady', // 兼容旧版本，应该移除该事件
  'onPageShow',
  'onPageHide',
  'onPageResize',
  // 小程序的 created,attached 生命周期(需要在 service 层的 Vue 内核 mounted 时触发,因小程序 created 可以使用 selectComponent)
  'onServiceCreated',
  'onServiceAttached'
]

const KEYS = ['data', 'properties', 'options', 'relations']

function mergeObject (ret, fromVal, key) {
  if (fromVal[key]) {
    Object.assign((ret[key] || (ret[key] = {})), fromVal[key])
  }
}

function mergeArray (toArray, fromArray) {
  toArray.push(...fromArray)
}

function mergeOptions (ret, toVal) {
  KEYS.forEach(key => {
    mergeObject(ret, toVal, key)
  })
  if (toVal.externalClasses) {
    mergeArray((ret.externalClasses || (ret.externalClasses = [])), toVal.externalClasses)
  }
  if (toVal.path) {
    ret.path = toVal.path
  }
}

export function lifecycleMixin (Vue) {
  // fixed vue-class-component
  const oldExtend = Vue.extend
  Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {}

    const methods = extendOptions.methods
    if (methods) {
      Object.keys(methods).forEach(methodName => {
        if (LIFECYCLE_HOOKS.indexOf(methodName) !== -1) {
          extendOptions[methodName] = methods[methodName]
          delete methods[methodName]
        }
      })
    }

    // script setup onPageScroll、onReachBottom not effective
    const setup = extendOptions.setup
    if (setup) {
      const injectHooks = ['onPageScroll', 'onReachBottom']
      let setupString = ''
      try {
        setupString = setup.toString()
      } catch (error) {}
      injectHooks.forEach(hook => {
        if (setupString.indexOf(`uniApp.${hook}`) && !extendOptions[hook]) {
          extendOptions[hook] = [() => {}]
        }
      })
    }

    return oldExtend.call(this, extendOptions)
  }

  const strategies = Vue.config.optionMergeStrategies
  const mergeHook = strategies.created
  LIFECYCLE_HOOKS.forEach(hook => {
    strategies[hook] = mergeHook
  })

  // mp runtime
  strategies.mpOptions = function (toVal, fromVal) {
    // data,properties,options,externalClasses,relations,path
    if (!toVal) {
      return fromVal
    }
    const ret = Object.create(null)
    mergeOptions(ret, toVal)
    if (fromVal) {
      mergeOptions(ret, fromVal)
    }
    return ret
  }
}
