import {
  isFn,
  noop
} from 'uni-shared'

import {
  wrapperMPEvent
} from 'uni-helpers/patch'

import {
  VD_SYNC,
  UI_EVENT,
  PAGE_CREATE,
  PAGE_CREATED,
  MOUNTED_DATA,
  UPDATED_DATA,
  VD_SYNC_VERSION
} from '../../../constants'

import {
  generateId
} from '../../../helpers/util'

import {
  removeVdSync,
  registerVdSync
} from '../subscribe-handlers/on-vd-sync'

import {
  vdSyncCallbacks
} from '../subscribe-handlers/on-vd-sync-callback'

import {
  hookKeyboardEvent
} from './keyboard'

import parseComponentCreateOptions from './parse-component-create-options'

function wrapperEvent (event) {
  event.preventDefault = noop
  event.stopPropagation = noop
  return wrapperMPEvent(event)
}

const handleVdData = {
  [UI_EVENT]: function onUIEvent (vdBatchEvent, vd) {
    vdBatchEvent.forEach(([cid, nid, event]) => {
      nid = String(nid)
      const target = vd.elements.find(target => target.cid === cid && target.nid === nid)
      if (!target) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(`event handler[${cid}][${nid}] not found`)
        }
        return
      }
      const type = event.type
      const mpEvent = wrapperEvent(event)
      if (type === 'focus' || type === 'blur') {
        hookKeyboardEvent(mpEvent, event => {
          target.dispatchEvent(type, event)
        })
      } else {
        target.dispatchEvent(type, mpEvent)
      }
    })
  }
}

function onVdSync (vdBatchData, vd) {
  vdBatchData.forEach(([type, vdData]) => {
    handleVdData[type](vdData, vd)
  })
}

export class VDomSync {
  constructor (pageId, pagePath, pageQuery, pageVm) {
    this.pageId = pageId
    this.pagePath = pagePath
    this.pageQuery = pageQuery
    this.pageVm = pageVm
    this.batchData = []
    this.vms = Object.create(null)
    this.initialized = false

    this.pageCreateData = false

    this.elements = [] //  目前仅存储事件 element

    this._init()
  }

  _init () {
    registerVdSync(this.pageId, (vdBatchData) => {
      onVdSync(vdBatchData, this)
    })
  }

  addMountedVm (vm) {
    vm._$mounted() // 触发vd数据同步
    this.addVdSyncCallback(function mounted () {
      vm.__call_hook('mounted')
    })
  }

  addUpdatedVm (vm) {
    vm._$updated() // 触发vd数据同步
    this.addVdSyncCallback(function mounted () {
      vm.__call_hook('updated')
    })
  }

  addVdSyncCallback (callback) {
    isFn(callback) && vdSyncCallbacks.push(callback)
  }

  getVm (id) {
    return this.vms[id]
  }

  addVm (vm) {
    const id = vm._$id
    const oldVm = this.vms[id]
    if (oldVm) {
      const newId = generateId(oldVm, oldVm.$parent, VD_SYNC_VERSION)
      oldVm._$id = newId
      this.vms[newId] = oldVm
      this.elements.forEach(element => {
        const cid = element.cid
        element.cid = cid === id ? newId : cid
      })
    }
    this.vms[id] = vm
  }

  removeVm (vm) {
    const cid = vm._$id
    if (vm === this.vms[cid]) { // 仅相同vm的才移除，否则保留
      // 目前同一位置的vm，cid均一样
      // 移除尚未同步的data
      this.batchData = this.batchData.filter(data => data[1][0] !== cid)
      delete this.vms[cid]
    }
  }

  addElement (elm) {
    this.elements.indexOf(elm) === -1 && this.elements.push(elm)
  }

  removeElement (elm) {
    const elmIndex = this.elements.indexOf(elm)
    if (elmIndex === -1) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`removeElement[${elm.cid}][${elm.nid}] not found`)
      }
      return
    }
    this.elements.splice(elmIndex, 1)
  }

  push (type, cid, data, options) {
    const typeData = [cid, data]
    if (options) {
      typeData.push(options)
    }
    this.batchData.push([type, typeData])
  }

  find (type, cid) {
    return this.batchData.find(data => data[0] === type && data[1][0] === cid)
  }

  sendPageCreate (data) {
    this.pageCreateData = data
    UniServiceJSBridge.publishHandler(VD_SYNC, {
      data: [
        [PAGE_CREATE, data]
      ],
      options: {
        timestamp: Date.now()
      }
    }, [this.pageId])
  }

  flush () {
    if (!this.initialized) {
      this.initialized = true
      this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath, this.pageQuery]])
    }
    const batchData = this.batchData.filter(data => {
      if (data[0] === UPDATED_DATA && !Object.keys(data[1][1]).length) {
        return false
      }
      return true
    })
    this.batchData.length = 0
    if (batchData.length) {
      UniServiceJSBridge.publishHandler(VD_SYNC, {
        data: batchData,
        options: {
          timestamp: Date.now()
        }
      }, [this.pageId])
    }
  }

  restorePageCreate () {
    this.batchData.push([PAGE_CREATE, this.pageCreateData])
  }

  restoreMountedData () {
    const addMountedData = (vm) => {
      if (vm._$id) {
        this.push(MOUNTED_DATA, vm._$id, vm._$data, parseComponentCreateOptions(vm))
      }
      // TODO vue 中 $children 顺序不可靠，可能存在恢复误差
      vm.$children.forEach(childVm => addMountedData(childVm))
    }
    addMountedData(this.pageVm)
  }

  restorePageCreated () {
    this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath, this.pageQuery]])
  }

  restore () {
    this.initialized = true
    this.batchData.length = 0

    this.restorePageCreate()
    this.restoreMountedData()
    this.restorePageCreated()

    this.flush()
  }

  destroy () {
    this.batchData.length = 0
    this.vms = Object.create(null)
    this.initialized = false
    this.elements.length = 0
    removeVdSync(this.pageId)
  }
}
