class Core {
  constructor (page) {
    this.functions = {}
    this.data = {}
    this._watchers = {} // 数据监听集合
    this._depends = [] // 页面内的所有自定义组件实例
    this._dependsIds = [] // 页面内的自定义组件实例ID。
    this.pageId = page.__wxExparserNodeId__

    this.allReady = true
  }

  events () {
    return {}
  }

  emit (event, args) {
    try {
      const { pageId } = this
      return new Promise((resolve, reject) => {
        if (this.events()[pageId] && this.events()[pageId][event]) {
          return this.events()[pageId][event](args, resolve)
        } else {
          resolve(args)
        }
      })
    } catch (e) {
      return Promise.resolve()
    }
  }

  on (event, args) {
    const { pageId } = this
    const data = this.events()[pageId] || {}

    if (data[event]) {
      console.error('该事件已注册，请选择其他事件')
      return
    }
    data[event] = args
    this.events = function () {
      return {
        [pageId]: data
      }
    }
  }

  setFunction (funcName, funcBody) {
    if (typeof funcName !== 'string') {
      console.error('函数名称类型只能为string')
      return
    }
    if (typeof funcBody !== 'function') {
      console.error('函数体类型只能为function')
      return
    }
    this.functions[funcName] = funcBody
  }

  getFunction (functionName) {
    if (this.functions[functionName]) { return this.functions[functionName] } else {
      console.error('无此方法，请检查方法名')
      return null
    }
  }

  setData (key, value) {
    if (typeof key !== 'string' && typeof key !== 'number') {
      console.error('字段key类型只能为string或者number。异常的key:', key)
      return
    }
    if (key.indexOf('_') === 0) {
      console.error('设置数据失败：setData 的 key 不可以以下划线开头。异常的key:', key)
      return false
    }
    // 内部字段不允许设置。
    if (['data', 'pageId', 'functions', 'allReady'].includes(key)) {
      console.error('设置数据失败：setData 的 key 违法，请使用其他的key。异常的key: ', key)
      return false
    }
    const oldValue = this[key]
    this.data[key] = value

    // 直接将数据代理到顶级
    Object.defineProperty(this, key, {

      get () {
        return value
      }
    })
    this._update(key, value, oldValue) // 通知观察者去更新数据
  }

  // 通知回调
  _update (key, value, oldValue) {
    // 监听数据的事件们回调处理。
    this._watchers[key] && this._watchers[key].forEach((cb) => {
      cb(value, oldValue)
    })
    // 通知本页面的各个自定义组件，数据更新。
    this._depends.forEach((wx_cxt) => {
      wx_cxt.setData({
        [key]: value
      })
    })
  }

  /**
   * 观察数据。依赖收集
   * @param {string} name 数据的标识
   * @param {function} cb 数据变化时的回调
   * @returns {function} 返回取消观察的函数。执行一下即取消了cb的订阅
   */
  watch (name, cb) {
    if (typeof cb !== 'function') {
      console.error('订阅数据变化的回调函数类型异常！', name)
      return false
    }
    this._watchers[name] = this._watchers[name] || []
    this._watchers[name].push(cb)
    return this._unwatchWait(name, cb)
  }

  /**
   * 取消对 name 数据的 cb 回调观察
   * @param {string} name 数据的标识
   * @param {function} cb 数据变化时的回调
   */
  unwatch (name, cb) {
    if (typeof cb === 'function') { // 忽略除了 function 类型之外的第二个参数。
      if (this._watchers[name].includes(cb)) {
        this._watchers[name].splice(this._watchers[name].indexOf(cb), 1)
      } else {
        console.error('没有此回调函数，取消订阅数据失败')
      }
    } else {
      this._watchers[name] = [] // 一次性清空全部
    }
  }

  // 取消数据观察的准备器。
  _unwatchWait (name, cb) {
    return () => {
      this.unwatch(name, cb)
    }
  }

  /**
   * 收集开发者的所有自定义组件实例
   * @param {微信自定义组件实例} wx_cxt
   */
  dependsPush (wx_cxt) {
    // 判断实例是否已收藏过，只push一次
    if (!this._dependsIds.includes(wx_cxt.__wxExparserNodeId__)) {
      this._dependsIds.push(wx_cxt.__wxExparserNodeId__)
      this._depends.push(wx_cxt)
      this.bandDataOnece(wx_cxt) // 立即处理数据
    } else {
      console.warn('该组件已存在', wx_cxt.__wxExparserNodeId__)
    }
  }

  // 给自定义组件实例添加数据。
  bandDataOnece (wx_cxt) {
    for (const key in this.data) {
      wx_cxt.setData({ [key]: this.data[key] })
    }
  }

  checkReady (cb) {
    // let me = this
    return new Promise(resolve => {
      if (!this.allReady) {
        setTimeout(this.checkReady.bind(this, cb || resolve), 0)
      } else {
        if (cb) {
          cb()
        } else {
          resolve()
        }
      }
    })
  }
}

module.exports = Core
