define([
  './core',
  './var/concat',
  './var/push',
  './core/access',
  './manipulation/var/rcheckableType',
  './manipulation/support',
  './data/var/data_priv',
  './data/var/data_user',

  './core/init',
  './data/accepts',
  './traversing',
  './selector',
  './event',
], function(
  jQuery,
  concat,
  push,
  access,
  rcheckableType,
  support,
  data_priv,
  data_user
) {
  var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
    rtagName = /<([\w:]+)/,
    rhtml = /<|&#?\w+;/,
    rnoInnerhtml = /<(?:script|style|link)/i,
    // checked="checked" or checked
    rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
    rscriptType = /^$|\/(?:java|ecma)script/i,
    rscriptTypeMasked = /^true\/(.*)/,
    rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
    // We have to close these tags to support XHTML (#13200)
    wrapMap = {
      // Support: IE9
      option: [1, "<select multiple='multiple'>", '</select>'],

      thead: [1, '<table>', '</table>'],
      col: [2, '<table><colgroup>', '</colgroup></table>'],
      tr: [2, '<table><tbody>', '</tbody></table>'],
      td: [3, '<table><tbody><tr>', '</tr></tbody></table>'],

      _default: [0, '', ''],
    };

  // Support: IE9
  wrapMap.optgroup = wrapMap.option;

  wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption =
    wrapMap.thead;
  wrapMap.th = wrapMap.td;

  // Support: 1.x compatibility
  // Manipulating tables requires a tbody
  function manipulationTarget(elem, content) {
    return jQuery.nodeName(elem, 'table') &&
      jQuery.nodeName(
        content.nodeType !== 11 ? content : content.firstChild,
        'tr'
      )
      ? elem.getElementsByTagName('tbody')[0] ||
          elem.appendChild(elem.ownerDocument.createElement('tbody'))
      : elem;
  }

  // Replace/restore the type attribute of script elements for safe DOM manipulation
  function disableScript(elem) {
    elem.type = (elem.getAttribute('type') !== null) + '/' + elem.type;
    return elem;
  }
  function restoreScript(elem) {
    var match = rscriptTypeMasked.exec(elem.type);

    if (match) {
      elem.type = match[1];
    } else {
      elem.removeAttribute('type');
    }

    return elem;
  }

  // Mark scripts as having already been evaluated
  function setGlobalEval(elems, refElements) {
    var i = 0,
      l = elems.length;

    for (; i < l; i++) {
      data_priv.set(
        elems[i],
        'globalEval',
        !refElements || data_priv.get(refElements[i], 'globalEval')
      );
    }
  }

  function cloneCopyEvent(src, dest) {
    var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;

    if (dest.nodeType !== 1) {
      return;
    }

    // 1. Copy private data: events, handlers, etc.
    if (data_priv.hasData(src)) {
      pdataOld = data_priv.access(src);
      pdataCur = data_priv.set(dest, pdataOld);
      events = pdataOld.events;

      if (events) {
        delete pdataCur.handle;
        pdataCur.events = {};

        for (type in events) {
          for (i = 0, l = events[type].length; i < l; i++) {
            jQuery.event.add(dest, type, events[type][i]);
          }
        }
      }
    }

    // 2. Copy user data
    if (data_user.hasData(src)) {
      udataOld = data_user.access(src);
      udataCur = jQuery.extend({}, udataOld);

      data_user.set(dest, udataCur);
    }
  }

  function getAll(context, tag) {
    var ret = context.getElementsByTagName
      ? context.getElementsByTagName(tag || '*')
      : context.querySelectorAll ? context.querySelectorAll(tag || '*') : [];

    return tag === undefined || (tag && jQuery.nodeName(context, tag))
      ? jQuery.merge([context], ret)
      : ret;
  }

  // Fix IE bugs, see support tests
  function fixInput(src, dest) {
    var nodeName = dest.nodeName.toLowerCase();

    // Fails to persist the checked state of a cloned checkbox or radio button.
    if (nodeName === 'input' && rcheckableType.test(src.type)) {
      dest.checked = src.checked;

      // Fails to return the selected option to the default selected state when cloning options
    } else if (nodeName === 'input' || nodeName === 'textarea') {
      dest.defaultValue = src.defaultValue;
    }
  }

  jQuery.extend({
    clone: function(elem, dataAndEvents, deepDataAndEvents) {
      var i,
        l,
        srcElements,
        destElements,
        clone = elem.cloneNode(true),
        inPage = jQuery.contains(elem.ownerDocument, elem);

      // Fix IE cloning issues
      if (
        !support.noCloneChecked &&
        (elem.nodeType === 1 || elem.nodeType === 11) &&
        !jQuery.isXMLDoc(elem)
      ) {
        // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
        destElements = getAll(clone);
        srcElements = getAll(elem);

        for (i = 0, l = srcElements.length; i < l; i++) {
          fixInput(srcElements[i], destElements[i]);
        }
      }

      // Copy the events from the original to the clone
      if (dataAndEvents) {
        if (deepDataAndEvents) {
          srcElements = srcElements || getAll(elem);
          destElements = destElements || getAll(clone);

          for (i = 0, l = srcElements.length; i < l; i++) {
            cloneCopyEvent(srcElements[i], destElements[i]);
          }
        } else {
          cloneCopyEvent(elem, clone);
        }
      }

      // Preserve script evaluation history
      destElements = getAll(clone, 'script');
      if (destElements.length > 0) {
        setGlobalEval(destElements, !inPage && getAll(elem, 'script'));
      }

      // Return the cloned set
      return clone;
    },

    buildFragment: function(elems, context, scripts, selection) {
      var elem,
        tmp,
        tag,
        wrap,
        contains,
        j,
        fragment = context.createDocumentFragment(),
        nodes = [],
        i = 0,
        l = elems.length;

      for (; i < l; i++) {
        elem = elems[i];

        if (elem || elem === 0) {
          // Add nodes directly
          if (jQuery.type(elem) === 'object') {
            // Support: QtWebKit, PhantomJS
            // push.apply(_, arraylike) throws on ancient WebKit
            jQuery.merge(nodes, elem.nodeType ? [elem] : elem);

            // Convert non-html into a text node
          } else if (!rhtml.test(elem)) {
            nodes.push(context.createTextNode(elem));

            // Convert html into DOM nodes
          } else {
            tmp = tmp || fragment.appendChild(context.createElement('div'));

            // Deserialize a standard representation
            tag = (rtagName.exec(elem) || ['', ''])[1].toLowerCase();
            wrap = wrapMap[tag] || wrapMap._default;
            tmp.innerHTML =
              wrap[1] + elem.replace(rxhtmlTag, '<$1></$2>') + wrap[2];

            // Descend through wrappers to the right content
            j = wrap[0];
            while (j--) {
              tmp = tmp.lastChild;
            }

            // Support: QtWebKit, PhantomJS
            // push.apply(_, arraylike) throws on ancient WebKit
            jQuery.merge(nodes, tmp.childNodes);

            // Remember the top-level container
            tmp = fragment.firstChild;

            // Ensure the created nodes are orphaned (#12392)
            tmp.textContent = '';
          }
        }
      }

      // Remove wrapper from fragment
      fragment.textContent = '';

      i = 0;
      while ((elem = nodes[i++])) {
        // #4087 - If origin and destination elements are the same, and this is
        // that element, do not do anything
        if (selection && jQuery.inArray(elem, selection) !== -1) {
          continue;
        }

        contains = jQuery.contains(elem.ownerDocument, elem);

        // Append to fragment
        tmp = getAll(fragment.appendChild(elem), 'script');

        // Preserve script evaluation history
        if (contains) {
          setGlobalEval(tmp);
        }

        // Capture executables
        if (scripts) {
          j = 0;
          while ((elem = tmp[j++])) {
            if (rscriptType.test(elem.type || '')) {
              scripts.push(elem);
            }
          }
        }
      }

      return fragment;
    },

    cleanData: function(elems) {
      var data,
        elem,
        type,
        key,
        special = jQuery.event.special,
        i = 0;

      for (; (elem = elems[i]) !== undefined; i++) {
        if (jQuery.acceptData(elem)) {
          key = elem[data_priv.expando];

          if (key && (data = data_priv.cache[key])) {
            if (data.events) {
              for (type in data.events) {
                if (special[type]) {
                  jQuery.event.remove(elem, type);

                  // This is a shortcut to avoid jQuery.event.remove's overhead
                } else {
                  jQuery.removeEvent(elem, type, data.handle);
                }
              }
            }
            if (data_priv.cache[key]) {
              // Discard any remaining `private` data
              delete data_priv.cache[key];
            }
          }
        }
        // Discard any remaining `user` data
        delete data_user.cache[elem[data_user.expando]];
      }
    },
  });

  jQuery.fn.extend({
    text: function(value) {
      return access(
        this,
        function(value) {
          return value === undefined
            ? jQuery.text(this)
            : this.empty().each(function() {
                if (
                  this.nodeType === 1 ||
                  this.nodeType === 11 ||
                  this.nodeType === 9
                ) {
                  this.textContent = value;
                }
              });
        },
        null,
        value,
        arguments.length
      );
    },

    append: function() {
      return this.domManip(arguments, function(elem) {
        if (
          this.nodeType === 1 ||
          this.nodeType === 11 ||
          this.nodeType === 9
        ) {
          var target = manipulationTarget(this, elem);
          target.appendChild(elem);
        }
      });
    },

    prepend: function() {
      return this.domManip(arguments, function(elem) {
        if (
          this.nodeType === 1 ||
          this.nodeType === 11 ||
          this.nodeType === 9
        ) {
          var target = manipulationTarget(this, elem);
          target.insertBefore(elem, target.firstChild);
        }
      });
    },

    before: function() {
      return this.domManip(arguments, function(elem) {
        if (this.parentNode) {
          this.parentNode.insertBefore(elem, this);
        }
      });
    },

    after: function() {
      return this.domManip(arguments, function(elem) {
        if (this.parentNode) {
          this.parentNode.insertBefore(elem, this.nextSibling);
        }
      });
    },

    remove: function(selector, keepData /* Internal Use Only */) {
      var elem,
        elems = selector ? jQuery.filter(selector, this) : this,
        i = 0;

      for (; (elem = elems[i]) != null; i++) {
        if (!keepData && elem.nodeType === 1) {
          jQuery.cleanData(getAll(elem));
        }

        if (elem.parentNode) {
          if (keepData && jQuery.contains(elem.ownerDocument, elem)) {
            setGlobalEval(getAll(elem, 'script'));
          }
          elem.parentNode.removeChild(elem);
        }
      }

      return this;
    },

    empty: function() {
      var elem,
        i = 0;

      for (; (elem = this[i]) != null; i++) {
        if (elem.nodeType === 1) {
          // Prevent memory leaks
          jQuery.cleanData(getAll(elem, false));

          // Remove any remaining nodes
          elem.textContent = '';
        }
      }

      return this;
    },

    clone: function(dataAndEvents, deepDataAndEvents) {
      dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
      deepDataAndEvents =
        deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

      return this.map(function() {
        return jQuery.clone(this, dataAndEvents, deepDataAndEvents);
      });
    },

    html: function(value) {
      return access(
        this,
        function(value) {
          var elem = this[0] || {},
            i = 0,
            l = this.length;

          if (value === undefined && elem.nodeType === 1) {
            return elem.innerHTML;
          }

          // See if we can take a shortcut and just use innerHTML
          if (
            typeof value === 'string' &&
            !rnoInnerhtml.test(value) &&
            !wrapMap[(rtagName.exec(value) || ['', ''])[1].toLowerCase()]
          ) {
            value = value.replace(rxhtmlTag, '<$1></$2>');

            try {
              for (; i < l; i++) {
                elem = this[i] || {};

                // Remove element nodes and prevent memory leaks
                if (elem.nodeType === 1) {
                  jQuery.cleanData(getAll(elem, false));
                  elem.innerHTML = value;
                }
              }

              elem = 0;

              // If using innerHTML throws an exception, use the fallback method
            } catch (e) {}
          }

          if (elem) {
            this.empty().append(value);
          }
        },
        null,
        value,
        arguments.length
      );
    },

    replaceWith: function() {
      var arg = arguments[0];

      // Make the changes, replacing each context element with the new content
      this.domManip(arguments, function(elem) {
        arg = this.parentNode;

        jQuery.cleanData(getAll(this));

        if (arg) {
          arg.replaceChild(elem, this);
        }
      });

      // Force removal if there was no new content (e.g., from empty arguments)
      return arg && (arg.length || arg.nodeType) ? this : this.remove();
    },

    detach: function(selector) {
      return this.remove(selector, true);
    },

    domManip: function(args, callback) {
      // Flatten any nested arrays
      args = concat.apply([], args);

      var fragment,
        first,
        scripts,
        hasScripts,
        node,
        doc,
        i = 0,
        l = this.length,
        set = this,
        iNoClone = l - 1,
        value = args[0],
        isFunction = jQuery.isFunction(value);

      // We can't cloneNode fragments that contain checked, in WebKit
      if (
        isFunction ||
        (l > 1 &&
          typeof value === 'string' &&
          !support.checkClone &&
          rchecked.test(value))
      ) {
        return this.each(function(index) {
          var self = set.eq(index);
          if (isFunction) {
            args[0] = value.call(this, index, self.html());
          }
          self.domManip(args, callback);
        });
      }

      if (l) {
        fragment = jQuery.buildFragment(
          args,
          this[0].ownerDocument,
          false,
          this
        );
        first = fragment.firstChild;

        if (fragment.childNodes.length === 1) {
          fragment = first;
        }

        if (first) {
          scripts = jQuery.map(getAll(fragment, 'script'), disableScript);
          hasScripts = scripts.length;

          // Use the original fragment for the last item instead of the first because it can end up
          // being emptied incorrectly in certain situations (#8070).
          for (; i < l; i++) {
            node = fragment;

            if (i !== iNoClone) {
              node = jQuery.clone(node, true, true);

              // Keep references to cloned scripts for later restoration
              if (hasScripts) {
                // Support: QtWebKit
                // jQuery.merge because push.apply(_, arraylike) throws
                jQuery.merge(scripts, getAll(node, 'script'));
              }
            }

            callback.call(this[i], node, i);
          }

          if (hasScripts) {
            doc = scripts[scripts.length - 1].ownerDocument;

            // Reenable scripts
            jQuery.map(scripts, restoreScript);

            // Evaluate executable scripts on first document insertion
            for (i = 0; i < hasScripts; i++) {
              node = scripts[i];
              if (
                rscriptType.test(node.type || '') &&
                !data_priv.access(node, 'globalEval') &&
                jQuery.contains(doc, node)
              ) {
                if (node.src) {
                  // Optional AJAX dependency, but won't run scripts if not present
                  if (jQuery._evalUrl) {
                    jQuery._evalUrl(node.src);
                  }
                } else {
                  jQuery.globalEval(node.textContent.replace(rcleanScript, ''));
                }
              }
            }
          }
        }
      }

      return this;
    },
  });

  jQuery.each(
    {
      appendTo: 'append',
      prependTo: 'prepend',
      insertBefore: 'before',
      insertAfter: 'after',
      replaceAll: 'replaceWith',
    },
    function(name, original) {
      jQuery.fn[name] = function(selector) {
        var elems,
          ret = [],
          insert = jQuery(selector),
          last = insert.length - 1,
          i = 0;

        for (; i <= last; i++) {
          elems = i === last ? this : this.clone(true);
          jQuery(insert[i])[original](elems);

          // Support: QtWebKit
          // .get() because push.apply(_, arraylike) throws
          push.apply(ret, elems.get());
        }

        return this.pushStack(ret);
      };
    }
  );

  return jQuery;
});
