text: var EventEmitter = require("events-light");
  var vdom = require("./vdom");
  var VElement = vdom.av_;
  var VDocumentFragment = vdom.aU_;
  var VComment = vdom.aV_;
  var VText = vdom.aW_;
  var VComponent = vdom.aX_;
  var VFragment = vdom.aY_;
  var virtualizeHTML = vdom.aZ_;
  var RenderResult = require("../RenderResult");
  var defaultDocument = vdom.b__;
  var morphdom = require("../../morphdom");
  var attrsHelper = require("./helper-attrs");
  
  var EVENT_UPDATE = "update";
  var EVENT_FINISH = "finish";
  
  function State(tree) {
      this.ba_ = new EventEmitter();
      this.bb_ = tree;
      this.bc_ = false;
  }
  
  function AsyncVDOMBuilder(globalData, parentNode, parentOut) {
      if (!parentNode) {
          parentNode = new VDocumentFragment();
      }
  
      var state;
  
      if (parentOut) {
          state = parentOut.f_;
      } else {
          state = new State(parentNode);
      }
  
      this.bd_ = 1;
      this.be_ = 0;
      this.bf_ = null;
      this.bg_ = parentOut;
  
      this.data = {};
      this.f_ = state;
      this.ae_ = parentNode;
      this.global = globalData || {};
      this.bh_ = [parentNode];
      this.bi_ = false;
      this.bj_ = undefined;
      this._r_ = null;
  
      this.aa_ = null;
      this.ab_ = null;
      this.ac_ = null;
  }
  
  var proto = AsyncVDOMBuilder.prototype = {
      aQ_: true,
      t_: defaultDocument,
  
      bc: function (component, key, ownerComponent) {
          var vComponent = new VComponent(component, key, ownerComponent);
          return this.bk_(vComponent, 0, true);
      },
  
      ah_: function (component, key, ownerComponent) {
          var vComponent = new VComponent(component, key, ownerComponent, true);
          this.bk_(vComponent, 0);
      },
  
      bk_: function (child, childCount, pushToStack) {
          this.ae_.bl_(child);
          if (pushToStack === true) {
              this.bh_.push(child);
              this.ae_ = child;
          }
          return childCount === 0 ? this : child;
      },
  
      element: function (tagName, attrs, key, component, childCount, flags, props) {
          var element = new VElement(tagName, attrs, key, component, childCount, flags, props);
          return this.bk_(element, childCount);
      },
  
      aN_: function (tagName, attrs, key, component, childCount, flags, props) {
          var element = VElement.bm_(tagName, attrsHelper(attrs), key, component, childCount, flags, props);
          return this.bk_(element, childCount);
      },
  
      n: function (node, component) {
          // NOTE: We do a shallow clone since we assume the node is being reused
          //       and a node can only have one parent node.
          var clone = node.bn_();
          this.node(clone);
          clone.aD_ = component;
  
          return this;
      },
  
      node: function (node) {
          this.ae_.bl_(node);
          return this;
      },
  
      text: function (text) {
          var type = typeof text;
  
          if (type != "string") {
              if (text == null) {
                  return;
              } else if (type === "object") {
                  if (text.toHTML) {
                      return this.h(text.toHTML());
                  }
              }
  
              text = text.toString();
          }
  
          this.ae_.bl_(new VText(text));
          return this;
      },
  
      comment: function (comment) {
          return this.node(new VComment(comment));
      },
  
      html: function (html) {
          if (html != null) {
              var vdomNode = virtualizeHTML(html, this.t_ || document);
              this.node(vdomNode);
          }
  
          return this;
      },
  
      beginElement: function (tagName, attrs, key, component, childCount, flags, props) {
          var element = new VElement(tagName, attrs, key, component, childCount, flags, props);
          this.bk_(element, childCount, true);
          return this;
      },
  
      aL_: function (tagName, attrs, key, component, childCount, flags, props) {
          var element = VElement.bm_(tagName, attrsHelper(attrs), key, component, childCount, flags, props);
          this.bk_(element, childCount, true);
          return this;
      },
  
      aO_: function (key, component, preserve) {
          var fragment = new VFragment(key, component, preserve);
          this.bk_(fragment, null, true);
          return this;
      },
  
      aP_: function () {
          this.endElement();
      },
  
      endElement: function () {
          var stack = this.bh_;
          stack.pop();
          this.ae_ = stack[stack.length - 1];
      },
  
      end: function () {
          this.ae_ = undefined;
  
          var remaining = --this.bd_;
          var parentOut = this.bg_;
  
          if (remaining === 0) {
              if (parentOut) {
                  parentOut.bo_();
              } else {
                  this.bp_();
              }
          } else if (remaining - this.be_ === 0) {
              this.bq_();
          }
  
          return this;
      },
  
      bo_: function () {
          var remaining = --this.bd_;
  
          if (remaining === 0) {
              var parentOut = this.bg_;
              if (parentOut) {
                  parentOut.bo_();
              } else {
                  this.bp_();
              }
          } else if (remaining - this.be_ === 0) {
              this.bq_();
          }
      },
  
      bp_: function () {
          var state = this.f_;
          state.bc_ = true;
          state.ba_.emit(EVENT_FINISH, this.aR_());
      },
  
      bq_: function () {
          var lastArray = this._last;
  
          var i = 0;
  
          function next() {
              if (i === lastArray.length) {
                  return;
              }
              var lastCallback = lastArray[i++];
              lastCallback(next);
  
              if (!lastCallback.length) {
                  next();
              }
          }
  
          next();
      },
  
      error: function (e) {
          try {
              this.emit("error", e);
          } finally {
              // If there is no listener for the error event then it will
              // throw a new Error here. In order to ensure that the async fragment
              // is still properly ended we need to put the end() in a `finally`
              // block
              this.end();
          }
  
          return this;
      },
  
      beginAsync: function (options) {
          if (this.bi_) {
              throw Error("Tried to render async while in sync mode. Note: Client side await is not currently supported in re-renders (Issue: #942).");
          }
  
          var state = this.f_;
  
          if (options) {
              if (options.last) {
                  this.be_++;
              }
          }
  
          this.bd_++;
  
          var documentFragment = this.ae_.br_();
          var asyncOut = new AsyncVDOMBuilder(this.global, documentFragment, this);
  
          state.ba_.emit("beginAsync", {
              out: asyncOut,
              parentOut: this
          });
  
          return asyncOut;
      },
  
      createOut: function () {
          return new AsyncVDOMBuilder(this.global);
      },
  
      flush: function () {
          var events = this.f_.ba_;
  
          if (events.listenerCount(EVENT_UPDATE)) {
              events.emit(EVENT_UPDATE, new RenderResult(this));
          }
      },
  
      Q_: function () {
          return this.f_.bb_;
      },
  
      aR_: function () {
          return this.bs_ || (this.bs_ = new RenderResult(this));
      },
  
      on: function (event, callback) {
          var state = this.f_;
  
          if (event === EVENT_FINISH && state.bc_) {
              callback(this.aR_());
          } else if (event === "last") {
              this.onLast(callback);
          } else {
              state.ba_.on(event, callback);
          }
  
          return this;
      },
  
      once: function (event, callback) {
          var state = this.f_;
  
          if (event === EVENT_FINISH && state.bc_) {
              callback(this.aR_());
          } else if (event === "last") {
              this.onLast(callback);
          } else {
              state.ba_.once(event, callback);
          }
  
          return this;
      },
  
      emit: function (type, arg) {
          var events = this.f_.ba_;
          switch (arguments.length) {
              case 1:
                  events.emit(type);
                  break;
              case 2:
                  events.emit(type, arg);
                  break;
              default:
                  events.emit.apply(events, arguments);
                  break;
          }
          return this;
      },
  
      removeListener: function () {
          var events = this.f_.ba_;
          events.removeListener.apply(events, arguments);
          return this;
      },
  
      sync: function () {
          this.bi_ = true;
      },
  
      isSync: function () {
          return this.bi_;
      },
  
      onLast: function (callback) {
          var lastArray = this._last;
  
          if (lastArray === undefined) {
              this._last = [callback];
          } else {
              lastArray.push(callback);
          }
  
          return this;
      },
  
      aJ_: function (doc) {
          var node = this.bj_;
          if (!node) {
              var vdomTree = this.Q_();
              // Create the root document fragment node
              doc = doc || this.t_ || document;
              this.bj_ = node = vdomTree.az_(doc);
              morphdom(node, vdomTree, doc, this._r_);
          }
          return node;
      },
  
      toString: function (doc) {
          var docFragment = this.aJ_(doc);
          var html = "";
  
          var child = docFragment.firstChild;
          while (child) {
              var nextSibling = child.nextSibling;
              if (child.nodeType != 1) {
                  var container = docFragment.ownerDocument.createElement("div");
                  container.appendChild(child.cloneNode());
                  html += container.innerHTML;
              } else {
                  html += child.outerHTML;
              }
  
              child = nextSibling;
          }
  
          return html;
      },
  
      then: function (fn, fnErr) {
          var out = this;
          var promise = new Promise(function (resolve, reject) {
              out.on("error", reject).on(EVENT_FINISH, function (result) {
                  resolve(result);
              });
          });
  
          return Promise.resolve(promise).then(fn, fnErr);
      },
  
      catch: function (fnErr) {
          return this.then(undefined, fnErr);
      },
  
      isVDOM: true,
  
      c: function (componentDef, key, customEvents) {
          this.aa_ = componentDef;
          this.ab_ = key;
          this.ac_ = customEvents;
      }
  };
  
  proto.e = proto.element;
  proto.be = proto.beginElement;
  proto.ee = proto.aM_ = proto.endElement;
  proto.t = proto.text;
  proto.h = proto.w = proto.write = proto.html;
  
  module.exports = AsyncVDOMBuilder;