class JsEngine {
  constructor(options) {
    this.options = options
    this.models = this.options.data || {}
    this.lifecycle = this.options.lifecycle || {}
    this.bindings = {}
    this.elementToUpdate = {}
    this.root = document.querySelector(this.options.el)
    this.searchBindModel()
    this.proxyBind()
  }

  searchBindModel() {
    this.lifecycle.componentWillMount
      && this.lifecycle.componentWillMount.call({ data: this.models })
    this.root.querySelectorAll('[bind]')
      .forEach((element) => {
        this.bindings[element.getAttribute('bind')] = element
      })
  }

  proxyBind() {
    const self = this
    this.models = new Proxy(this.models, {
      set (target, prop, value) {
        const element = self.bindings[prop]

        self.updateTextBind(prop, value)
        element.value = value
        element.setAttribute('value', value)

        return Reflect.set(target, prop, value)
      }
    })

    this.setInitialModel()
    this.inputBindingReaction()
    this.lifecycle.componentDidMount
      && this.lifecycle.componentDidMount.call({ data: this.models })
    this.setTextModels()
  }

  setInitialModel() {
    Object.keys(this.bindings)
      .map((modelName) => {
        const element = this.bindings[modelName]
        const value = this.models[modelName]

        element.value = value
        element.setAttribute('value', value)
      })
  }

  inputBindingReaction() {
    Object.keys(this.bindings)
      .map((modelName) => {
        const element = this.bindings[modelName]

        element
          .addEventListener('input', (event) => {
            this.models[modelName] = event.target.value
          })
      })
  }

  setTextModels() {
    let { children } = this.root

    for (let i = 0; i < children.length; i++) {
      let replaced = children[i].innerText.replace(/{{2}(.+)}{2}/igm, (_, matching) => {
        matching = matching.trim()
        this.elementToUpdate[matching] = children[i]
        if (this.models[matching]) {
          return this.models[matching]
        }

        return undefined
      })

      if (replaced) {
        children[i].innerText = replaced
      }
    }
  }

  updateTextBind(prop, currentValue = null) {
    const element = this.elementToUpdate[prop] || {}

    element.innerText = currentValue
  }
}