define([
  './core',
  './var/indexOf',
  './traversing/var/rneedsContext',
  './core/init',
  './traversing/findFilter',
  './selector',
], function(jQuery, indexOf, rneedsContext) {
  var rparentsprev = /^(?:parents|prev(?:Until|All))/,
    // Methods guaranteed to produce a unique set when starting from a unique set
    guaranteedUnique = {
      children: true,
      contents: true,
      next: true,
      prev: true,
    };

  jQuery.extend({
    dir: function(elem, dir, until) {
      var matched = [],
        truncate = until !== undefined;

      while ((elem = elem[dir]) && elem.nodeType !== 9) {
        if (elem.nodeType === 1) {
          if (truncate && jQuery(elem).is(until)) {
            break;
          }
          matched.push(elem);
        }
      }
      return matched;
    },

    sibling: function(n, elem) {
      var matched = [];

      for (; n; n = n.nextSibling) {
        if (n.nodeType === 1 && n !== elem) {
          matched.push(n);
        }
      }

      return matched;
    },
  });

  jQuery.fn.extend({
    has: function(target) {
      var targets = jQuery(target, this),
        l = targets.length;

      return this.filter(function() {
        var i = 0;
        for (; i < l; i++) {
          if (jQuery.contains(this, targets[i])) {
            return true;
          }
        }
      });
    },

    closest: function(selectors, context) {
      var cur,
        i = 0,
        l = this.length,
        matched = [],
        pos =
          rneedsContext.test(selectors) || typeof selectors !== 'string'
            ? jQuery(selectors, context || this.context)
            : 0;

      for (; i < l; i++) {
        for (cur = this[i]; cur && cur !== context; cur = cur.parentNode) {
          // Always skip document fragments
          if (
            cur.nodeType < 11 &&
            (pos
              ? pos.index(cur) > -1
              : // Don't pass non-elements to Sizzle
                cur.nodeType === 1 &&
                jQuery.find.matchesSelector(cur, selectors))
          ) {
            matched.push(cur);
            break;
          }
        }
      }

      return this.pushStack(
        matched.length > 1 ? jQuery.unique(matched) : matched
      );
    },

    // Determine the position of an element within the set
    index: function(elem) {
      // No argument, return index in parent
      if (!elem) {
        return this[0] && this[0].parentNode
          ? this.first().prevAll().length
          : -1;
      }

      // Index in selector
      if (typeof elem === 'string') {
        return indexOf.call(jQuery(elem), this[0]);
      }

      // Locate the position of the desired element
      return indexOf.call(
        this,

        // If it receives a jQuery object, the first element is used
        elem.jquery ? elem[0] : elem
      );
    },

    add: function(selector, context) {
      return this.pushStack(
        jQuery.unique(jQuery.merge(this.get(), jQuery(selector, context)))
      );
    },

    addBack: function(selector) {
      return this.add(
        selector == null ? this.prevObject : this.prevObject.filter(selector)
      );
    },
  });

  function sibling(cur, dir) {
    while ((cur = cur[dir]) && cur.nodeType !== 1) {}
    return cur;
  }

  jQuery.each(
    {
      parent: function(elem) {
        var parent = elem.parentNode;
        return parent && parent.nodeType !== 11 ? parent : null;
      },
      parents: function(elem) {
        return jQuery.dir(elem, 'parentNode');
      },
      parentsUntil: function(elem, i, until) {
        return jQuery.dir(elem, 'parentNode', until);
      },
      next: function(elem) {
        return sibling(elem, 'nextSibling');
      },
      prev: function(elem) {
        return sibling(elem, 'previousSibling');
      },
      nextAll: function(elem) {
        return jQuery.dir(elem, 'nextSibling');
      },
      prevAll: function(elem) {
        return jQuery.dir(elem, 'previousSibling');
      },
      nextUntil: function(elem, i, until) {
        return jQuery.dir(elem, 'nextSibling', until);
      },
      prevUntil: function(elem, i, until) {
        return jQuery.dir(elem, 'previousSibling', until);
      },
      siblings: function(elem) {
        return jQuery.sibling((elem.parentNode || {}).firstChild, elem);
      },
      children: function(elem) {
        return jQuery.sibling(elem.firstChild);
      },
      contents: function(elem) {
        return elem.contentDocument || jQuery.merge([], elem.childNodes);
      },
    },
    function(name, fn) {
      jQuery.fn[name] = function(until, selector) {
        var matched = jQuery.map(this, fn, until);

        if (name.slice(-5) !== 'Until') {
          selector = until;
        }

        if (selector && typeof selector === 'string') {
          matched = jQuery.filter(selector, matched);
        }

        if (this.length > 1) {
          // Remove duplicates
          if (!guaranteedUnique[name]) {
            jQuery.unique(matched);
          }

          // Reverse order for parents* and prev-derivatives
          if (rparentsprev.test(name)) {
            matched.reverse();
          }
        }

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

  return jQuery;
});
