import Dep from './dep'
import { arrayMethods } from './array'
import { def } from '../util/util'

/*
确保在调用时，先调用到自定义的方法。有两种方式可以实现：
- 数组对象上直接有该方法，这样就不会去找对象上的原型链
- 覆盖对象的 __proto__ ，这样寻找原型链时，就会先找到我们的方法
*/
// 如果能使用 __proto__ 则将数组的处理方法进行替换
function protoAugment(target: any, src: any) {
  target.__proto__ = src
}
// 如果不能使用 __proto__ 则直接将该方法定义在当前对象下
function copyAugment(target: any, src: any, keys: Array<string>) {
  for (let i = 0; i < keys.length; i++) {
    def(target, keys[i], src[keys[i]])
  }
}

/**
 * 将对象下的某个属性变成可监听结构（响应化数据）
 */
function defineReactive(object: Object, key: string, value: any) {
  // 每一个属性中都存在一个 dep，用于管理依赖于属性的 watcher
  let dep = new Dep()
  let childOb = observe(value)
  Object.defineProperty(object, key, {
    configurable: true,
    enumerable: true,
    get: function() {
      if (Dep.target) {
        // 添加 watcher 到 dep.watchers，添加 dep 到 watcher.deps
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function(newValue) {
      if (newValue !== value) {
        value = newValue
        dep.notify()
      }
    }
  })
}

class Observer {
  value: any
  dep: Dep
  constructor(value: any) {
    this.value = value
    this.dep = new Dep() // 支持 Observer 实例调用 dep 的方法
    def(value, '__ob__', this)
    // 和对象处理不同的是，数组长度不能确定，一开始定义索引的 get/set 没有意义，所以这里并没有对索引使用 defineReactive
    if (Array.isArray(value)) {
      // 兼容某些浏览器不支持 Object.prototype.__proto__ 的情况
      const augment = '__proto__' in {} ? protoAugment : copyAugment
      // 改变数组原生方法，使得调用改变数据的方法时（例如 arr.push）得以响应数据
      augment(value, arrayMethods, Object.keys(arrayMethods))
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  walk(obj: any) {
    // Object.keys() 对数组也有作用，输出["0", "1", ...]
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * 观察数组的每一项
   */
  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

export default function observe(value: any): Observer | void {
  // 非对象无需进行 defineReactive
  if (typeof value !== 'object') return
  let ob
  // 如果对象下有 Observer 则不需要再次生成 Observer（不同的引用名指向同一个地址的情况）
  if (value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (Object.isExtensible(value)) {
    ob = new Observer(value)
  }
  return ob
}

/**
 * 在变更数组元素时收集对数组元素的依赖关系，因为
 * 我们不能利用属性 getter 来拦截对数组元素的访问
 */
function dependArray(value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
