<!DOCTYPE html>
<html>

<head>
  <title>Reactivity System Demo</title>
  <script src="dep.js"></script>
  <script src="watcher.js"></script>
  <script src="observer.js"></script>
</head>

<body>
  <input type="text" placeholder="First name" onchange="window.data.firstName = this.value"  />
  <input type="text" placeholder="Last name"  onchange="window.data.lastName  = this.value" />
  <ul id="console">
  </ul>
  <script>
    window.data = {
      firstName: 'hello',
      lastName: 'world',
    }
    walk(window.data)

    const watcher = new Watcher(() => `Hi! My name is "${window.data.firstName} ${window.data.lastName}"`,
                                (val) => consoleLog(val)
                              )

    function consoleLog(val) {
      const consoleEl = document.querySelector('#console')
      const newItem = document.createElement('li')
      newItem.appendChild(document.createTextNode(val))
      consoleEl.appendChild(newItem)
    }
  </script>
</body>

</html>
// See https://github.com/vuejs/vue/blob/be9ac624c81bb698ed75628fe0cbeaba4a2fc991/src/core/observer/dep.js
// for full implementation

class Dep {
  constructor() {
    this.subs = new Set()
  }

  addSub(sub) {
    this.subs.add(sub)
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify() {
    this.subs.forEach(sub => sub.update())
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []

function pushTarget(_target) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

function popTarget() {
  Dep.target = targetStack.pop()
}
// See https://github.com/vuejs/vue/blob/61187596b9af48f1cb7b1848ad3eccc02ac2509d/src/core/observer/index.js
// for full implementation

/* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
function walk(obj) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i], obj[keys[i]])
  }
}

function defineReactive(obj, key, val) {
  if (val !== null && typeof val === 'object') {
    walk(val) // Add reactivity to all children of val
  }

  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      dep.depend()

      return val
    },
    set: function reactiveSetter(newVal) {
      val = newVal
      dep.notify()
    }
  })
}
// See https://github.com/vuejs/vue/blob/be9ac624c81bb698ed75628fe0cbeaba4a2fc991/src/core/observer/watcher.js
// for full implementation

class Watcher {
  constructor(getter, cb) {
    this.getter = getter // function that returns a value based on reactive properties
    this.cb = cb // function that is run on value updates, and is passed value and old value
    this.value = this.get()
    this.cb(this.value, null)
  }

  get() {
    pushTarget(this) // from dep.js
    const value = this.getter()
    popTarget() // from dep.js

    return value
  }

  addDep(dep) {
    dep.addSub(this)
  }

  update() {
    const value = this.get()
    const oldValue = this.value
    this.value = value

    this.cb(value, oldValue)
  }
}