/**
 * Copyright (c) Tiny Technologies, Inc. All rights reserved.
 * Licensed under the LGPL or a commercial license.
 * For LGPL see License.txt in the project root for license information.
 * For commercial licenses see https://www.tiny.cloud/
 */

import { Arr } from '@ephox/katamari';

/* jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */

/*eslint-disable */

/**
 * Sizzle CSS Selector Engine v@VERSION
 * http://sizzlejs.com/
 *
 * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: @DATE
 */

/* eslint-enable */

/* eslint-disable no-bitwise, prefer-const, no-nested-ternary, @typescript-eslint/consistent-type-assertions, prefer-arrow/prefer-arrow-functions, @tinymce/prefer-fun, @typescript-eslint/unbound-method */

let support,
  Expr,
  getText,
  isXML,
  tokenize,
  compile,
  select,
  outermostContext,
  sortInput,
  hasDuplicate,

  // Local document vars
  setDocument,
  document,
  docElem,
  documentIsHTML,
  rbuggyQSA,
  rbuggyMatches,
  matches,
  contains,

  // Instance-specific data
  expando = 'sizzle' + -(new Date()),
  preferredDoc = window.document,
  dirruns = 0,
  done = 0,
  classCache = createCache(),
  tokenCache = createCache(),
  compilerCache = createCache(),
  sortOrder = function (a, b) {
    if (a === b) {
      hasDuplicate = true;
    }
    return 0;
  },

  // General-purpose constants
  strundefined = typeof undefined,
  MAX_NEGATIVE = 1 << 31,

  // Instance methods
  hasOwn = ({}).hasOwnProperty,
  arr = [],
  pop = arr.pop,
  push_native = arr.push,
  push: any = arr.push,
  slice = arr.slice,
  // Use a stripped-down indexOf if we can't use a native one
  indexOf = arr.indexOf || function (elem) {
    let i = 0,
      len = this.length;
    for (; i < len; i++) {
      if (this[i] === elem) {
        return i;
      }
    }
    return -1;
  },

  booleans = 'checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped',

  // Regular expressions

  // http://www.w3.org/TR/css3-selectors/#whitespace
  whitespace = '[\\x20\\t\\r\\n\\f]',

  // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
  identifier = '(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+',

  // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
  attributes = '\\[' + whitespace + '*(' + identifier + ')(?:' + whitespace +
    // Operator (capture 2)
    '*([*^$|!~]?=)' + whitespace +
    // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
    `*(?:'((?:\\\\.|[^\\\\'])*)'|"((?:\\\\.|[^\\\\"])*)"|(` + identifier + '))|)' + whitespace +
    '*\\]',

  pseudos = ':(' + identifier + ')(?:\\((' +
    // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
    // 1. quoted (capture 3; capture 4 or capture 5)
    `('((?:\\\\.|[^\\\\'])*)'|"((?:\\\\.|[^\\\\"])*)")|` +
    // 2. simple (capture 6)
    '((?:\\\\.|[^\\\\()[\\]]|' + attributes + ')*)|' +
    // 3. anything else (capture 2)
    '.*' +
    ')\\)|)',

  // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
  rtrim = new RegExp('^' + whitespace + '+|((?:^|[^\\\\])(?:\\\\.)*)' + whitespace + '+$', 'g'),

  rcomma = new RegExp('^' + whitespace + '*,' + whitespace + '*'),
  rcombinators = new RegExp('^' + whitespace + '*([>+~]|' + whitespace + ')' + whitespace + '*'),

  rattributeQuotes = new RegExp('=' + whitespace + `*([^\\]'"]*?)` + whitespace + '*\\]', 'g'),

  rpseudo = new RegExp(pseudos),
  ridentifier = new RegExp('^' + identifier + '$'),

  matchExpr = {
    ID: new RegExp('^#(' + identifier + ')'),
    CLASS: new RegExp('^\\.(' + identifier + ')'),
    TAG: new RegExp('^(' + identifier + '|[*])'),
    ATTR: new RegExp('^' + attributes),
    PSEUDO: new RegExp('^' + pseudos),
    CHILD: new RegExp('^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(' + whitespace +
      '*(even|odd|(([+-]|)(\\d*)n|)' + whitespace + '*(?:([+-]|)' + whitespace +
      '*(\\d+)|))' + whitespace + '*\\)|)', 'i'),
    bool: new RegExp('^(?:' + booleans + ')$', 'i'),
    // For use in libraries implementing .is()
    // We use this for POS matching in `select`
    needsContext: new RegExp('^' + whitespace + '*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(' +
      whitespace + '*((?:-\\d)?\\d*)' + whitespace + '*\\)|)(?=[^-]|$)', 'i')
  },

  rinputs = /^(?:input|select|textarea|button)$/i,
  rheader = /^h\d$/i,

  rnative = /^[^{]+\{\s*\[native \w/,

  // Easily-parseable/retrievable ID or TAG or CLASS selectors
  rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

  rsibling = /[+~]/,
  rescape = /'|\\/g,

  // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
  runescape = new RegExp('\\\\([\\da-f]{1,6}' + whitespace + '?|(' + whitespace + ')|.)', 'ig'),
  funescape = function (_, escaped, escapedWhitespace) {
    const high = (<any> '0x') + escaped - 0x10000;
    // NaN means non-codepoint
    // Support: Firefox<24
    // Workaround erroneous numeric interpretation of +"0x"
    return high !== high || escapedWhitespace ?
      escaped :
      high < 0 ?
        // BMP codepoint
        String.fromCharCode(high + 0x10000) :
        // Supplemental Plane codepoint (surrogate pair)
        String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00);
  };

// Optimize for push.apply( _, NodeList )
try {
  push.apply(
    (arr = slice.call(preferredDoc.childNodes)),
    preferredDoc.childNodes
  );
  // Support: Android<4.0
  // Detect silently failing push.apply
  // eslint-disable-next-line no-unused-expressions
  arr[preferredDoc.childNodes.length].nodeType;
} catch (e) {
  push = {
    apply: arr.length ?

      // Leverage slice if possible
      function (target, els) {
        push_native.apply(target, slice.call(els));
      } :

      // Support: IE<9
      // Otherwise append directly
      function (target, els) {
        let j = target.length,
          i = 0;
        // Can't trust NodeList.length
        while ((target[j++] = els[i++])) { }
        target.length = j - 1;
      }
  };
}

const Sizzle: any = function (selector, context, results, seed) {
  let match, elem, m, nodeType,
    // QSA vars
    i, groups, old, nid, newContext, newSelector;

  if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
    setDocument(context);
  }

  context = context || document;
  results = results || [];

  if (!selector || typeof selector !== 'string') {
    return results;
  }

  if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) {
    return [];
  }

  if (documentIsHTML && !seed) {

    // Shortcuts
    if ((match = rquickExpr.exec(selector))) {
      // Speed-up: Sizzle("#ID")
      if ((m = match[1])) {
        if (nodeType === 9) {
          elem = context.getElementById(m);
          // Check parentNode to catch when Blackberry 4.6 returns
          // nodes that are no longer in the document (jQuery #6963)
          if (elem && elem.parentNode) {
            // Handle the case where IE, Opera, and Webkit return items
            // by name instead of ID
            if (elem.id === m) {
              results.push(elem);
              return results;
            }
          } else {
            return results;
          }
        } else {
          // Context is not a document
          if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) &&
            contains(context, elem) && elem.id === m) {
            results.push(elem);
            return results;
          }
        }

        // Speed-up: Sizzle("TAG")
      } else if (match[2]) {
        push.apply(results, context.getElementsByTagName(selector));
        return results;

        // Speed-up: Sizzle(".CLASS")
      } else if ((m = match[3]) && support.getElementsByClassName) {
        push.apply(results, context.getElementsByClassName(m));
        return results;
      }
    }

    // QSA path
    if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) {
      nid = old = expando;
      newContext = context;
      newSelector = nodeType === 9 && selector;

      // qSA works strangely on Element-rooted queries
      // We can work around this by specifying an extra ID on the root
      // and working up from there (Thanks to Andrew Dupont for the technique)
      // IE 8 doesn't work on object elements
      if (nodeType === 1 && context.nodeName.toLowerCase() !== 'object') {
        groups = tokenize(selector);

        if ((old = context.getAttribute('id'))) {
          nid = old.replace(rescape, '\\$&');
        } else {
          context.setAttribute('id', nid);
        }
        nid = `[id='${nid}'] `;

        i = groups.length;
        while (i--) {
          groups[i] = nid + toSelector(groups[i]);
        }
        newContext = rsibling.test(selector) && testContext(context.parentNode) || context;
        newSelector = groups.join(',');
      }

      if (newSelector) {
        try {
          push.apply(results,
            newContext.querySelectorAll(newSelector)
          );
          return results;
        } catch (qsaError) {
        } finally {
          if (!old) {
            context.removeAttribute('id');
          }
        }
      }
    }
  }

  // All others
  return select(selector.replace(rtrim, '$1'), context, results, seed);
};

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 * deleting the oldest entry
 */
function createCache() {
  const keys = [];

  function cache(key, value) {
    // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
    if (keys.push(key + ' ') > Expr.cacheLength) {
      // Only keep the most recent entries
      delete cache[keys.shift()];
    }
    return (cache[key + ' '] = value);
  }
  return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction(fn) {
  fn[expando] = true;
  return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created div and expects a boolean result
 */
/* function assert(fn) {
  var div = document.createElement("div");

  try {
    return !!fn(div);
  } catch (e) {
    return false;
  } finally {
    // Remove from its parent by default
    if (div.parentNode) {
      div.parentNode.removeChild(div);
    }
    // release memory in IE
    div = null;
  }
}*/

// /**
//  * Adds the same handler for all of the specified attrs
//  * @param {String} attrs Pipe-separated list of attributes
//  * @param {Function} handler The method that will be applied
//  */
// function addHandle(attrs, handler) {
//   let arr = attrs.split('|'),
//     i = attrs.length;

//   while (i--) {
//     Expr.attrHandle[arr[i]] = handler;
//   }
// }

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck(a, b) {
  let cur = b && a,
    diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
      (~b.sourceIndex || MAX_NEGATIVE) -
      (~a.sourceIndex || MAX_NEGATIVE);

  // Use IE sourceIndex if available on both nodes
  if (diff) {
    return diff;
  }

  // Check if b follows a
  if (cur) {
    while ((cur = cur.nextSibling)) {
      if (cur === b) {
        return -1;
      }
    }
  }

  return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo(type) {
  return function (elem) {
    const name = elem.nodeName.toLowerCase();
    return name === 'input' && elem.type === type;
  };
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo(type) {
  return function (elem) {
    const name = elem.nodeName.toLowerCase();
    return (name === 'input' || name === 'button') && elem.type === type;
  };
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo(fn) {
  return markFunction((argument) => {
    argument = +argument;
    return markFunction((seed, matches) => {
      let j,
        matchIndexes = fn([], seed.length, argument),
        i = matchIndexes.length;

      // Match elements found at the specified indexes
      while (i--) {
        if (seed[(j = matchIndexes[i])]) {
          seed[j] = !(matches[j] = seed[j]);
        }
      }
    });
  });
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext(context) {
  return context && typeof context.getElementsByTagName !== strundefined && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function (elem) {
  // documentElement is verified for cases where it doesn't yet exist
  // (such as loading iframes in IE - #4833)
  const documentElement = elem && (elem.ownerDocument || elem).documentElement;
  return documentElement ? documentElement.nodeName !== 'HTML' : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function (node) {
  let hasCompare,
    doc = node ? node.ownerDocument || node : preferredDoc,
    parent = doc.defaultView;

  function getTop(win) {
    // Edge throws a lovely Object expected if you try to get top on a detached reference see #2642
    try {
      return win.top;
    } catch (ex) {
      // Ignore
    }

    return null;
  }

  // If no document and documentElement is available, return
  if (doc === document || doc.nodeType !== 9 || !doc.documentElement) {
    return document;
  }

  // Set our document
  document = doc;
  docElem = doc.documentElement;

  // Support tests
  documentIsHTML = !isXML(doc);

  // Support: IE>8
  // If iframe document is assigned to "document" variable and if iframe has been reloaded,
  // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
  // IE6-8 do not support the defaultView property so parent will be undefined
  if (parent && parent !== getTop(parent)) {
    // IE11 does not have attachEvent, so all must suffer
    if (parent.addEventListener) {
      parent.addEventListener('unload', () => {
        setDocument();
      }, false);
    } else if (parent.attachEvent) {
      parent.attachEvent('onunload', () => {
        setDocument();
      });
    }
  }

  /* Attributes
  ---------------------------------------------------------------------- */

  // Support: IE<8
  // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
  support.attributes = true;

  /* getElement(s)By*
  ---------------------------------------------------------------------- */

  // Check if getElementsByTagName("*") returns only elements
  support.getElementsByTagName = true;

  // Support: IE<9
  support.getElementsByClassName = rnative.test(doc.getElementsByClassName);

  // Support: IE<10
  // Check if getElementById returns elements by name
  // The broken getElementById methods don't pick up programatically-set names,
  // so use a roundabout getElementsByName test
  support.getById = true;

  // ID find and filter
  /* if (support.getById) {*/
  Expr.find.ID = function (id, context) {
    if (typeof context.getElementById !== strundefined && documentIsHTML) {
      const m = context.getElementById(id);
      // Check parentNode to catch when Blackberry 4.6 returns
      // nodes that are no longer in the document #6963
      return m && m.parentNode ? [ m ] : [];
    }
  };
  Expr.filter.ID = function (id) {
    const attrId = id.replace(runescape, funescape);
    return function (elem) {
      return elem.getAttribute('id') === attrId;
    };
  };
  /* } else {
    // Support: IE6/7
    // getElementById is not reliable as a find shortcut
    delete Expr.find["ID"];

    Expr.filter["ID"] = function (id) {
      var attrId = id.replace(runescape, funescape);
      return function (elem) {
        var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
        return node && node.value === attrId;
      };
    };
  }*/

  // Tag
  Expr.find.TAG = support.getElementsByTagName ?
    function (tag, context) {
      if (typeof context.getElementsByTagName !== strundefined) {
        return context.getElementsByTagName(tag);
      }
    } :
    function (tag, context) {
      let elem,
        tmp = [],
        i = 0,
        results = context.getElementsByTagName(tag);

      // Filter out possible comments
      if (tag === '*') {
        while ((elem = results[i++])) {
          if (elem.nodeType === 1) {
            tmp.push(elem);
          }
        }

        return tmp;
      }
      return results;
    };

  // Class
  Expr.find.CLASS = support.getElementsByClassName && function (className, context) {
    if (documentIsHTML) {
      return context.getElementsByClassName(className);
    }
  };

  /* QSA/matchesSelector
  ---------------------------------------------------------------------- */

  // QSA and matchesSelector support

  // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
  rbuggyMatches = [];

  // qSa(:focus) reports false when true (Chrome 21)
  // We allow this because of a bug in IE8/9 that throws an error
  // whenever `document.activeElement` is accessed on an iframe
  // So, we allow :focus to pass through QSA all the time to avoid the IE error
  // See http://bugs.jquery.com/ticket/13378
  rbuggyQSA = [];

  /*
  if ((support.qsa = rnative.test(doc.querySelectorAll))) {
    // Build QSA regex
    // Regex strategy adopted from Diego Perini
    assert(function (div) {
      // Select is set to empty string on purpose
      // This is to test IE's treatment of not explicitly
      // setting a boolean content attribute,
      // since its presence should be enough
      // http://bugs.jquery.com/ticket/12359
      div.innerHTML = "<select msallowcapture=''><option selected=''></option></select>";

      // Support: IE8, Opera 11-12.16
      // Nothing should be selected when empty strings follow ^= or $= or *=
      // The test attribute must be unknown in Opera but "safe" for WinRT
      // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
      if (div.querySelectorAll("[msallowcapture^='']").length) {
        rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")");
      }

      // Support: IE8
      // Boolean attributes and "value" are not treated correctly
      if (!div.querySelectorAll("[selected]").length) {
        rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
      }

      // Webkit/Opera - :checked should return selected option elements
      // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
      // IE8 throws error here and will not see later tests
      if (!div.querySelectorAll(":checked").length) {
        rbuggyQSA.push(":checked");
      }
    });

    assert(function (div) {
      // Support: Windows 8 Native Apps
      // The type and name attributes are restricted during .innerHTML assignment
      var input = doc.createElement("input");
      input.setAttribute("type", "hidden");
      div.appendChild(input).setAttribute("name", "D");

      // Support: IE8
      // Enforce case-sensitivity of name attribute
      if (div.querySelectorAll("[name=d]").length) {
        rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
      }

      // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
      // IE8 throws error here and will not see later tests
      if (!div.querySelectorAll(":enabled").length) {
        rbuggyQSA.push(":enabled", ":disabled");
      }

      // Opera 10-11 does not throw on post-comma invalid pseudos
      div.querySelectorAll("*,:x");
      rbuggyQSA.push(",.*:");
    });
  }
  */

  support.disconnectedMatch = true;

  /*
  if ((support.matchesSelector = rnative.test((matches = docElem.matches ||
    docElem.webkitMatchesSelector ||
    docElem.mozMatchesSelector ||
    docElem.oMatchesSelector ||
    docElem.msMatchesSelector)))) {

    assert(function (div) {
      // Check to see if it's possible to do matchesSelector
      // on a disconnected node (IE 9)
      support.disconnectedMatch = matches.call(div, "div");

      // This should fail with an exception
      // Gecko does not error, returns false instead
      matches.call(div, "[s!='']:x");
      rbuggyMatches.push("!=", pseudos);
    });
  }
  */

  rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join('|'));
  rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join('|'));

  /* Contains
  ---------------------------------------------------------------------- */
  hasCompare = rnative.test(docElem.compareDocumentPosition);

  // Element contains another
  // Purposefully does not implement inclusive descendent
  // As in, an element does not contain itself
  contains = hasCompare || rnative.test(docElem.contains) ?
    function (a, b) {
      const adown = a.nodeType === 9 ? a.documentElement : a,
        bup = b && b.parentNode;
      return a === bup || !!(bup && bup.nodeType === 1 && (
        adown.contains ?
          adown.contains(bup) :
          a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
      ));
    } :
    function (a, b) {
      if (b) {
        while ((b = b.parentNode)) {
          if (b === a) {
            return true;
          }
        }
      }
      return false;
    };

  /* Sorting
  ---------------------------------------------------------------------- */

  // Document order sorting
  sortOrder = hasCompare ?
    function (a, b) {

      // Flag for duplicate removal
      if (a === b) {
        hasDuplicate = true;
        return 0;
      }

      // Sort on method existence if only one input has compareDocumentPosition
      let compare = (<any> (!a.compareDocumentPosition)) - (<any> (!b.compareDocumentPosition));
      if (compare) {
        return compare;
      }

      // Calculate position if both inputs belong to the same document
      compare = (a.ownerDocument || a) === (b.ownerDocument || b) ?
        a.compareDocumentPosition(b) :

        // Otherwise we know they are disconnected
        1;

      // Disconnected nodes
      if (compare & 1 ||
        (!support.sortDetached && b.compareDocumentPosition(a) === compare)) {

        // Choose the first element that is related to our preferred document
        if (a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a)) {
          return -1;
        }
        if (b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b)) {
          return 1;
        }

        // Maintain original order
        return sortInput ?
          (indexOf.call(sortInput, a) - indexOf.call(sortInput, b)) :
          0;
      }

      return compare & 4 ? -1 : 1;
    } :
    function (a, b) {
      // Exit early if the nodes are identical
      if (a === b) {
        hasDuplicate = true;
        return 0;
      }

      let cur,
        i = 0,
        aup = a.parentNode,
        bup = b.parentNode,
        ap = [ a ],
        bp = [ b ];

      // Parentless nodes are either documents or disconnected
      if (!aup || !bup) {
        return a === doc ? -1 :
          b === doc ? 1 :
            aup ? -1 :
              bup ? 1 :
                sortInput ?
                  (indexOf.call(sortInput, a) - indexOf.call(sortInput, b)) :
                  0;

        // If the nodes are siblings, we can do a quick check
      } else if (aup === bup) {
        return siblingCheck(a, b);
      }

      // Otherwise we need full lists of their ancestors for comparison
      cur = a;
      while ((cur = cur.parentNode)) {
        ap.unshift(cur);
      }
      cur = b;
      while ((cur = cur.parentNode)) {
        bp.unshift(cur);
      }

      // Walk down the tree looking for a discrepancy
      while (ap[i] === bp[i]) {
        i++;
      }

      return i ?
        // Do a sibling check if the nodes have a common ancestor
        siblingCheck(ap[i], bp[i]) :

        // Otherwise nodes in our document sort first
        ap[i] === preferredDoc ? -1 :
          bp[i] === preferredDoc ? 1 :
            0;
    };

  return doc;
};

Sizzle.matches = function (expr, elements) {
  return Sizzle(expr, null, null, elements);
};

Sizzle.matchesSelector = function (elem, expr) {
  // Set document vars if needed
  if ((elem.ownerDocument || elem) !== document) {
    setDocument(elem);
  }

  // Make sure that attribute selectors are quoted
  expr = expr.replace(rattributeQuotes, `='$1']`);

  if (support.matchesSelector && documentIsHTML &&
    (!rbuggyMatches || !rbuggyMatches.test(expr)) &&
    (!rbuggyQSA || !rbuggyQSA.test(expr))) {

    try {
      const ret = matches.call(elem, expr);

      // IE 9's matchesSelector returns false on disconnected nodes
      if (ret || support.disconnectedMatch ||
        // As well, disconnected nodes are said to be in a document
        // fragment in IE 9
        elem.document && elem.document.nodeType !== 11) {
        return ret;
      }
    } catch (e) { }
  }

  return Sizzle(expr, document, null, [ elem ]).length > 0;
};

Sizzle.contains = function (context, elem) {
  // Set document vars if needed
  if ((context.ownerDocument || context) !== document) {
    setDocument(context);
  }
  return contains(context, elem);
};

Sizzle.attr = function (elem, name) {
  // Set document vars if needed
  if ((elem.ownerDocument || elem) !== document) {
    setDocument(elem);
  }

  let fn = Expr.attrHandle[name.toLowerCase()],
    // Don't get fooled by Object.prototype properties (jQuery #13807)
    val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ?
      fn(elem, name, !documentIsHTML) :
      undefined;

  return val !== undefined ?
    val :
    support.attributes || !documentIsHTML ?
      elem.getAttribute(name) :
      (val = elem.getAttributeNode(name)) && val.specified ?
        val.value :
        null;
};

Sizzle.error = function (msg) {
  throw new Error('Syntax error, unrecognized expression: ' + msg);
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function (results) {
  let elem,
    duplicates = [],
    j = 0,
    i = 0;

  // Unless we *know* we can detect duplicates, assume their presence
  hasDuplicate = !support.detectDuplicates;
  sortInput = !support.sortStable && results.slice(0);
  results.sort(sortOrder);

  if (hasDuplicate) {
    while ((elem = results[i++])) {
      if (elem === results[i]) {
        j = duplicates.push(i);
      }
    }
    while (j--) {
      results.splice(duplicates[j], 1);
    }
  }

  // Clear input after sorting to release objects
  // See https://github.com/jquery/sizzle/pull/225
  sortInput = null;

  return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function (elem) {
  let node,
    ret = '',
    i = 0,
    nodeType = elem.nodeType;

  if (!nodeType) {
    // If no nodeType, this is expected to be an array
    while ((node = elem[i++])) {
      // Do not traverse comment nodes
      ret += getText(node);
    }
  } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) {
    // Use textContent for elements
    // innerText usage removed for consistency of new lines (jQuery #11153)
    if (typeof elem.textContent === 'string') {
      return elem.textContent;
    } else {
      // Traverse its children
      for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
        ret += getText(elem);
      }
    }
  } else if (nodeType === 3 || nodeType === 4) {
    return elem.nodeValue;
  }
  // Do not include comment or processing instruction nodes

  return ret;
};

Expr = Sizzle.selectors = {

  // Can be adjusted by the user
  cacheLength: 50,

  createPseudo: markFunction,

  match: matchExpr,

  attrHandle: {},

  find: {},

  relative: {
    '>': { dir: 'parentNode', first: true },
    ' ': { dir: 'parentNode' },
    '+': { dir: 'previousSibling', first: true },
    '~': { dir: 'previousSibling' }
  },

  preFilter: {
    ATTR(match) {
      match[1] = match[1].replace(runescape, funescape);

      // Move the given value to match[3] whether quoted or unquoted
      match[3] = (match[3] || match[4] || match[5] || '').replace(runescape, funescape);

      if (match[2] === '~=') {
        match[3] = ' ' + match[3] + ' ';
      }

      return match.slice(0, 4);
    },

    CHILD(match) {
      /* matches from matchExpr["CHILD"]
        1 type (only|nth|...)
        2 what (child|of-type)
        3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
        4 xn-component of xn+y argument ([+-]?\d*n|)
        5 sign of xn-component
        6 x of xn-component
        7 sign of y-component
        8 y of y-component
      */
      match[1] = match[1].toLowerCase();

      if (match[1].slice(0, 3) === 'nth') {
        // nth-* requires argument
        if (!match[3]) {
          Sizzle.error(match[0]);
        }

        // numeric x and y parameters for Expr.filter.CHILD
        // remember that false/true cast respectively to 0/1
        match[4] = +(match[4] ? match[5] + (match[6] || 1) : 2 * (<any> (match[3] === 'even') || match[3] === 'odd'));
        match[5] = +((match[7] + match[8]) || match[3] === 'odd');

        // other types prohibit arguments
      } else if (match[3]) {
        Sizzle.error(match[0]);
      }

      return match;
    },

    PSEUDO(match) {
      let excess,
        unquoted = !match[6] && match[2];

      if (matchExpr.CHILD.test(match[0])) {
        return null;
      }

      // Accept quoted arguments as-is
      if (match[3]) {
        match[2] = match[4] || match[5] || '';

        // Strip excess characters from unquoted arguments
      } else if (unquoted && rpseudo.test(unquoted) &&
        // Get excess from tokenize (recursively)
        (excess = tokenize(unquoted, true)) &&
        // advance to the next closing parenthesis
        (excess = unquoted.indexOf(')', unquoted.length - excess) - unquoted.length)) {

        // excess is a negative index
        match[0] = match[0].slice(0, excess);
        match[2] = unquoted.slice(0, excess);
      }

      // Return only captures needed by the pseudo filter method (type and argument)
      return match.slice(0, 3);
    }
  },

  filter: {

    TAG(nodeNameSelector) {
      const nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase();
      return nodeNameSelector === '*' ?
        function () {
          return true;
        } :
        function (elem) {
          return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
        };
    },

    CLASS(className) {
      let pattern = classCache[className + ' '];

      return pattern ||
        (pattern = new RegExp('(^|' + whitespace + ')' + className + '(' + whitespace + '|$)')) &&
        classCache(className, (elem) => {
          return pattern.test(typeof elem.className === 'string' && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute('class') || '');
        });
    },

    ATTR(name, operator, check) {
      return function (elem) {
        let result = Sizzle.attr(elem, name);

        if (result == null) {
          return operator === '!=';
        }
        if (!operator) {
          return true;
        }

        result += '';

        return operator === '=' ? result === check :
          operator === '!=' ? result !== check :
            operator === '^=' ? check && result.indexOf(check) === 0 :
              operator === '*=' ? check && result.indexOf(check) > -1 :
                operator === '$=' ? check && result.slice(-check.length) === check :
                  operator === '~=' ? (' ' + result + ' ').indexOf(check) > -1 :
                    operator === '|=' ? result === check || result.slice(0, check.length + 1) === check + '-' :
                      false;
      };
    },

    CHILD(type, what, argument, first, last) {
      const simple = type.slice(0, 3) !== 'nth',
        forward = type.slice(-4) !== 'last',
        ofType = what === 'of-type';

      return first === 1 && last === 0 ?

        // Shortcut for :nth-*(n)
        function (elem) {
          return !!elem.parentNode;
        } :

        function (elem, context, xml) {
          let cache, outerCache, node, diff, nodeIndex, start,
            dir = simple !== forward ? 'nextSibling' : 'previousSibling',
            parent = elem.parentNode,
            name = ofType && elem.nodeName.toLowerCase(),
            useCache = !xml && !ofType;

          if (parent) {

            // :(first|last|only)-(child|of-type)
            if (simple) {
              while (dir) {
                node = elem;
                while ((node = node[dir])) {
                  if (ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) {
                    return false;
                  }
                }
                // Reverse direction for :only-* (if we haven't yet done so)
                start = dir = type === 'only' && !start && 'nextSibling';
              }
              return true;
            }

            start = [ forward ? parent.firstChild : parent.lastChild ];

            // non-xml :nth-child(...) stores cache data on `parent`
            if (forward && useCache) {
              // Seek `elem` from a previously-cached index
              outerCache = parent[expando] || (parent[expando] = {});
              cache = outerCache[type] || [];
              nodeIndex = cache[0] === dirruns && cache[1];
              diff = cache[0] === dirruns && cache[2];
              node = nodeIndex && parent.childNodes[nodeIndex];

              while ((node = ++nodeIndex && node && node[dir] ||

                // Fallback to seeking `elem` from the start
                (diff = nodeIndex = 0) || start.pop())) {

                // When found, cache indexes on `parent` and break
                if (node.nodeType === 1 && ++diff && node === elem) {
                  outerCache[type] = [ dirruns, nodeIndex, diff ];
                  break;
                }
              }

              // Use previously-cached element index if available
            } else if (useCache && (cache = (elem[expando] || (elem[expando] = {}))[type]) && cache[0] === dirruns) {
              diff = cache[1];

              // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
            } else {
              // Use the same loop as above to seek `elem` from the start
              while ((node = ++nodeIndex && node && node[dir] ||
                (diff = nodeIndex = 0) || start.pop())) {

                if ((ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) && ++diff) {
                  // Cache the index of each encountered element
                  if (useCache) {
                    (node[expando] || (node[expando] = {}))[type] = [ dirruns, diff ];
                  }

                  if (node === elem) {
                    break;
                  }
                }
              }
            }

            // Incorporate the offset, then check against cycle size
            diff -= last;
            return diff === first || (diff % first === 0 && diff / first >= 0);
          }
        };
    },

    PSEUDO(pseudo, argument) {
      // pseudo-class names are case-insensitive
      // http://www.w3.org/TR/selectors/#pseudo-classes
      // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
      // Remember that setFilters inherits from pseudos
      let args,
        fn = Expr.pseudos[pseudo] || Expr.setFilters[pseudo.toLowerCase()] ||
          Sizzle.error('unsupported pseudo: ' + pseudo);

      // The user may use createPseudo to indicate that
      // arguments are needed to create the filter function
      // just as Sizzle does
      if (fn[expando]) {
        return fn(argument);
      }

      // But maintain support for old signatures
      if (fn.length > 1) {
        args = [ pseudo, pseudo, '', argument ];
        return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ?
          markFunction((seed, matches) => {
            let idx,
              matched = fn(seed, argument),
              i = matched.length;
            while (i--) {
              idx = indexOf.call(seed, matched[i]);
              seed[idx] = !(matches[idx] = matched[i]);
            }
          }) :
          function (elem) {
            return fn(elem, 0, args);
          };
      }

      return fn;
    }
  },

  pseudos: {
    // Potentially complex pseudos
    not: markFunction((selector) => {
      // Trim the selector passed to compile
      // to avoid treating leading and trailing
      // spaces as combinators
      const input = [],
        results = [],
        matcher = compile(selector.replace(rtrim, '$1'));

      return matcher[expando] ?
        markFunction((seed, matches, context, xml) => {
          let elem,
            unmatched = matcher(seed, null, xml, []),
            i = seed.length;

          // Match elements unmatched by `matcher`
          while (i--) {
            if ((elem = unmatched[i])) {
              seed[i] = !(matches[i] = elem);
            }
          }
        }) :
        function (elem, context, xml) {
          input[0] = elem;
          matcher(input, null, xml, results);
          // Don't keep the element (issue #299)
          input[0] = null;
          return !results.pop();
        };
    }),

    has: markFunction((selector) => {
      return function (elem) {
        return Sizzle(selector, elem).length > 0;
      };
    }),

    contains: markFunction((text) => {
      text = text.replace(runescape, funescape);
      return function (elem) {
        return (elem.textContent || elem.innerText || getText(elem)).indexOf(text) > -1;
      };
    }),

    // "Whether an element is represented by a :lang() selector
    // is based solely on the element's language value
    // being equal to the identifier C,
    // or beginning with the identifier C immediately followed by "-".
    // The matching of C against the element's language value is performed case-insensitively.
    // The identifier C does not have to be a valid language name."
    // http://www.w3.org/TR/selectors/#lang-pseudo
    lang: markFunction((lang) => {
      // lang value must be a valid identifier
      if (!ridentifier.test(lang || '')) {
        Sizzle.error('unsupported lang: ' + lang);
      }
      lang = lang.replace(runescape, funescape).toLowerCase();
      return function (elem) {
        let elemLang;
        do {
          if ((elemLang = documentIsHTML ?
            elem.lang :
            elem.getAttribute('xml:lang') || elem.getAttribute('lang'))) {

            elemLang = elemLang.toLowerCase();
            return elemLang === lang || elemLang.indexOf(lang + '-') === 0;
          }
        } while ((elem = elem.parentNode) && elem.nodeType === 1);
        return false;
      };
    }),

    // Miscellaneous
    target(elem) {
      const hash = window.location && window.location.hash;
      return hash && hash.slice(1) === elem.id;
    },

    root(elem) {
      return elem === docElem;
    },

    focus(elem) {
      return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
    },

    // Boolean properties
    enabled(elem) {
      return elem.disabled === false;
    },

    disabled(elem) {
      return elem.disabled === true;
    },

    checked(elem) {
      // In CSS3, :checked should return both checked and selected elements
      // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
      const nodeName = elem.nodeName.toLowerCase();
      return (nodeName === 'input' && !!elem.checked) || (nodeName === 'option' && !!elem.selected);
    },

    selected(elem) {
      // Accessing this property makes selected-by-default
      // options in Safari work properly
      if (elem.parentNode) {
        // eslint-disable-next-line no-unused-expressions
        elem.parentNode.selectedIndex;
      }

      return elem.selected === true;
    },

    // Contents
    empty(elem) {
      // http://www.w3.org/TR/selectors/#empty-pseudo
      // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
      //   but not by others (comment: 8; processing instruction: 7; etc.)
      // nodeType < 6 works because attributes (2) do not appear as children
      for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
        if (elem.nodeType < 6) {
          return false;
        }
      }
      return true;
    },

    parent(elem) {
      return !Expr.pseudos.empty(elem);
    },

    // Element/input types
    header(elem) {
      return rheader.test(elem.nodeName);
    },

    input(elem) {
      return rinputs.test(elem.nodeName);
    },

    button(elem) {
      const name = elem.nodeName.toLowerCase();
      return name === 'input' && elem.type === 'button' || name === 'button';
    },

    text(elem) {
      let attr;
      return elem.nodeName.toLowerCase() === 'input' &&
        elem.type === 'text' &&

        // Support: IE<8
        // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
        ((attr = elem.getAttribute('type')) == null || attr.toLowerCase() === 'text');
    },

    // Position-in-collection
    first: createPositionalPseudo(() => {
      return [ 0 ];
    }),

    last: createPositionalPseudo((matchIndexes, length) => {
      return [ length - 1 ];
    }),

    eq: createPositionalPseudo((matchIndexes, length, argument) => {
      return [ argument < 0 ? argument + length : argument ];
    }),

    even: createPositionalPseudo((matchIndexes, length) => {
      let i = 0;
      for (; i < length; i += 2) {
        matchIndexes.push(i);
      }
      return matchIndexes;
    }),

    odd: createPositionalPseudo((matchIndexes, length) => {
      let i = 1;
      for (; i < length; i += 2) {
        matchIndexes.push(i);
      }
      return matchIndexes;
    }),

    lt: createPositionalPseudo((matchIndexes, length, argument) => {
      let i = argument < 0 ? argument + length : argument;
      for (; --i >= 0;) {
        matchIndexes.push(i);
      }
      return matchIndexes;
    }),

    gt: createPositionalPseudo((matchIndexes, length, argument) => {
      let i = argument < 0 ? argument + length : argument;
      for (; ++i < length;) {
        matchIndexes.push(i);
      }
      return matchIndexes;
    })
  }
};

Expr.pseudos.nth = Expr.pseudos.eq;

// Add button/input type pseudos
Arr.each([ 'radio', 'checkbox', 'file', 'password', 'image' ], (i) => {
  Expr.pseudos[i] = createInputPseudo(i);
});
Arr.each([ 'submit', 'reset' ], (i) => {
  Expr.pseudos[i] = createButtonPseudo(i);
});

// Easy API for creating new setFilters
function setFilters() { }
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function (selector, parseOnly) {
  let matched, match, tokens, type,
    soFar, groups, preFilters,
    cached = tokenCache[selector + ' '];

  if (cached) {
    return parseOnly ? 0 : cached.slice(0);
  }

  soFar = selector;
  groups = [];
  preFilters = Expr.preFilter;

  while (soFar) {

    // Comma and first run
    if (!matched || (match = rcomma.exec(soFar))) {
      if (match) {
        // Don't consume trailing commas as valid
        soFar = soFar.slice(match[0].length) || soFar;
      }
      groups.push((tokens = []));
    }

    matched = false;

    // Combinators
    if ((match = rcombinators.exec(soFar))) {
      matched = match.shift();
      tokens.push({
        value: matched,
        // Cast descendant combinators to space
        type: match[0].replace(rtrim, ' ')
      });
      soFar = soFar.slice(matched.length);
    }

    // Filters
    for (type in Expr.filter) {
      if (!Expr.filter.hasOwnProperty(type)) {
        continue;
      }
      if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] ||
        (match = preFilters[type](match)))) {
        matched = match.shift();
        tokens.push({
          value: matched,
          type,
          matches: match
        });
        soFar = soFar.slice(matched.length);
      }
    }

    if (!matched) {
      break;
    }
  }

  // Return the length of the invalid excess
  // if we're just parsing
  // Otherwise, throw an error or return tokens
  return parseOnly ?
    soFar.length :
    soFar ?
      Sizzle.error(selector) :
      // Cache the tokens
      tokenCache(selector, groups).slice(0);
};

function toSelector(tokens) {
  let i = 0,
    len = tokens.length,
    selector = '';
  for (; i < len; i++) {
    selector += tokens[i].value;
  }
  return selector;
}

function addCombinator(matcher, combinator, base?) {
  const dir = combinator.dir,
    checkNonElements = base && dir === 'parentNode',
    doneName = done++;

  return combinator.first ?
    // Check against closest ancestor/preceding element
    function (elem, context, xml) {
      while ((elem = elem[dir])) {
        if (elem.nodeType === 1 || checkNonElements) {
          return matcher(elem, context, xml);
        }
      }
    } :

    // Check against all ancestor/preceding elements
    function (elem, context, xml) {
      let oldCache, outerCache,
        newCache = [ dirruns, doneName ];

      // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
      if (xml) {
        while ((elem = elem[dir])) {
          if (elem.nodeType === 1 || checkNonElements) {
            if (matcher(elem, context, xml)) {
              return true;
            }
          }
        }
      } else {
        while ((elem = elem[dir])) {
          if (elem.nodeType === 1 || checkNonElements) {
            outerCache = elem[expando] || (elem[expando] = {});
            if ((oldCache = outerCache[dir]) &&
              oldCache[0] === dirruns && oldCache[1] === doneName) {

              // Assign to newCache so results back-propagate to previous elements
              return (newCache[2] = oldCache[2]);
            } else {
              // Reuse newcache so results back-propagate to previous elements
              outerCache[dir] = newCache;

              // A match means we're done; a fail means we have to keep checking
              if ((newCache[2] = matcher(elem, context, xml))) {
                return true;
              }
            }
          }
        }
      }
    };
}

function elementMatcher(matchers) {
  return matchers.length > 1 ?
    function (elem, context, xml) {
      let i = matchers.length;
      while (i--) {
        if (!matchers[i](elem, context, xml)) {
          return false;
        }
      }
      return true;
    } :
    matchers[0];
}

function multipleContexts(selector, contexts, results) {
  let i = 0,
    len = contexts.length;
  for (; i < len; i++) {
    Sizzle(selector, contexts[i], results);
  }
  return results;
}

function condense(unmatched, map?, filter?, context?, xml?) {
  let elem,
    newUnmatched = [],
    i = 0,
    len = unmatched.length,
    mapped = map != null;

  for (; i < len; i++) {
    if ((elem = unmatched[i])) {
      if (!filter || filter(elem, context, xml)) {
        newUnmatched.push(elem);
        if (mapped) {
          map.push(i);
        }
      }
    }
  }

  return newUnmatched;
}

function setMatcher(preFilter, selector?, matcher?, postFilter?, postFinder?, postSelector?) {
  if (postFilter && !postFilter[expando]) {
    postFilter = setMatcher(postFilter);
  }
  if (postFinder && !postFinder[expando]) {
    postFinder = setMatcher(postFinder, postSelector);
  }
  return markFunction((seed, results, context, xml) => {
    let temp, i, elem,
      preMap = [],
      postMap = [],
      preexisting = results.length,

      // Get initial elements from seed or context
      elems = seed || multipleContexts(selector || '*', context.nodeType ? [ context ] : context, []),

      // Prefilter to get matcher input, preserving a map for seed-results synchronization
      matcherIn = preFilter && (seed || !selector) ?
        condense(elems, preMap, preFilter, context, xml) :
        elems,

      matcherOut = matcher ?
        // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
        postFinder || (seed ? preFilter : preexisting || postFilter) ?

          // ...intermediate processing is necessary
          [] :

          // ...otherwise use results directly
          results :
        matcherIn;

    // Find primary matches
    if (matcher) {
      matcher(matcherIn, matcherOut, context, xml);
    }

    // Apply postFilter
    if (postFilter) {
      temp = condense(matcherOut, postMap);
      postFilter(temp, [], context, xml);

      // Un-match failing elements by moving them back to matcherIn
      i = temp.length;
      while (i--) {
        if ((elem = temp[i])) {
          matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem);
        }
      }
    }

    if (seed) {
      if (postFinder || preFilter) {
        if (postFinder) {
          // Get the final matcherOut by condensing this intermediate into postFinder contexts
          temp = [];
          i = matcherOut.length;
          while (i--) {
            if ((elem = matcherOut[i])) {
              // Restore matcherIn since elem is not yet a final match
              temp.push((matcherIn[i] = elem));
            }
          }
          postFinder(null, (matcherOut = []), temp, xml);
        }

        // Move matched elements from seed to results to keep them synchronized
        i = matcherOut.length;
        while (i--) {
          if ((elem = matcherOut[i]) &&
            (temp = postFinder ? indexOf.call(seed, elem) : preMap[i]) > -1) {

            seed[temp] = !(results[temp] = elem);
          }
        }
      }

      // Add elements to results, through postFinder if defined
    } else {
      matcherOut = condense(
        matcherOut === results ?
          matcherOut.splice(preexisting, matcherOut.length) :
          matcherOut
      );
      if (postFinder) {
        postFinder(null, results, matcherOut, xml);
      } else {
        push.apply(results, matcherOut);
      }
    }
  });
}

function matcherFromTokens(tokens) {
  let checkContext, matcher, j,
    len = tokens.length,
    leadingRelative = Expr.relative[tokens[0].type],
    implicitRelative = leadingRelative || Expr.relative[' '],
    i = leadingRelative ? 1 : 0,

    // The foundational matcher ensures that elements are reachable from top-level context(s)
    matchContext = addCombinator((elem) => {
      return elem === checkContext;
    }, implicitRelative, true),
    matchAnyContext = addCombinator((elem) => {
      return indexOf.call(checkContext, elem) > -1;
    }, implicitRelative, true),
    matchers = [ function (elem, context, xml) {
      const ret = (!leadingRelative && (xml || context !== outermostContext)) || (
        (checkContext = context).nodeType ?
          matchContext(elem, context, xml) :
          matchAnyContext(elem, context, xml));
      // Avoid hanging onto element (issue #299)
      checkContext = null;
      return ret;
    } ];

  for (; i < len; i++) {
    if ((matcher = Expr.relative[tokens[i].type])) {
      matchers = [ addCombinator(elementMatcher(matchers), matcher) ];
    } else {
      matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);

      // Return special upon seeing a positional matcher
      if (matcher[expando]) {
        // Find the next relative operator (if any) for proper handling
        j = ++i;
        for (; j < len; j++) {
          if (Expr.relative[tokens[j].type]) {
            break;
          }
        }
        return setMatcher(
          i > 1 && elementMatcher(matchers),
          i > 1 && toSelector(
            // If the preceding token was a descendant combinator, insert an implicit any-element `*`
            tokens.slice(0, i - 1).concat({ value: tokens[i - 2].type === ' ' ? '*' : '' })
          ).replace(rtrim, '$1'),
          matcher,
          i < j && matcherFromTokens(tokens.slice(i, j)),
          j < len && matcherFromTokens((tokens = tokens.slice(j))),
          j < len && toSelector(tokens)
        );
      }
      matchers.push(matcher);
    }
  }

  return elementMatcher(matchers);
}

function matcherFromGroupMatchers(elementMatchers, setMatchers) {
  const bySet = setMatchers.length > 0,
    byElement = elementMatchers.length > 0,
    superMatcher = function (seed, context, xml, results, outermost) {
      let elem, j, matcher,
        matchedCount = 0,
        i: any = '0',
        unmatched = seed && [],
        setMatched = [],
        contextBackup = outermostContext,
        // We must always have either seed elements or outermost context
        elems = seed || byElement && Expr.find.TAG('*', outermost),
        // Use integer dirruns iff this is the outermost matcher
        dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
        len = elems.length;

      if (outermost) {
        outermostContext = context !== document && context;
      }

      // Add elements passing elementMatchers directly to results
      // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
      // Support: IE<9, Safari
      // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
      for (; i !== len && (elem = elems[i]) != null; i++) {
        if (byElement && elem) {
          j = 0;
          while ((matcher = elementMatchers[j++])) {
            if (matcher(elem, context, xml)) {
              results.push(elem);
              break;
            }
          }
          if (outermost) {
            dirruns = dirrunsUnique;
          }
        }

        // Track unmatched elements for set filters
        if (bySet) {
          // They will have gone through all possible matchers
          if ((elem = !matcher && elem)) {
            matchedCount--;
          }

          // Lengthen the array for every element, matched or not
          if (seed) {
            unmatched.push(elem);
          }
        }
      }

      // Apply set filters to unmatched elements
      matchedCount += i;
      if (bySet && i !== matchedCount) {
        j = 0;
        while ((matcher = setMatchers[j++])) {
          matcher(unmatched, setMatched, context, xml);
        }

        if (seed) {
          // Reintegrate element matches to eliminate the need for sorting
          if (matchedCount > 0) {
            while (i--) {
              if (!(unmatched[i] || setMatched[i])) {
                setMatched[i] = pop.call(results);
              }
            }
          }

          // Discard index placeholder values to get only actual matches
          setMatched = condense(setMatched);
        }

        // Add matches to results
        push.apply(results, setMatched);

        // Seedless set matches succeeding multiple successful matchers stipulate sorting
        if (outermost && !seed && setMatched.length > 0 &&
          (matchedCount + setMatchers.length) > 1) {

          Sizzle.uniqueSort(results);
        }
      }

      // Override manipulation of globals by nested matchers
      if (outermost) {
        dirruns = dirrunsUnique;
        outermostContext = contextBackup;
      }

      return unmatched;
    };

  return bySet ?
    markFunction(superMatcher) :
    superMatcher;
}

compile = Sizzle.compile = function (selector, match /* Internal Use Only */) {
  let i,
    setMatchers = [],
    elementMatchers = [],
    cached = compilerCache[selector + ' '];

  if (!cached) {
    // Generate a function of recursive functions that can be used to check each element
    if (!match) {
      match = tokenize(selector);
    }
    i = match.length;
    while (i--) {
      cached = matcherFromTokens(match[i]);
      if (cached[expando]) {
        setMatchers.push(cached);
      } else {
        elementMatchers.push(cached);
      }
    }

    // Cache the compiled function
    cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers));

    // Save selector and tokenization
    cached.selector = selector;
  }
  return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function (selector, context, results, seed) {
  let i, tokens, token, type, find,
    compiled = typeof selector === 'function' && selector,
    match = !seed && tokenize((selector = compiled.selector || selector));

  results = results || [];

  // Try to minimize operations if there is no seed and only one group
  if (match.length === 1) {

    // Take a shortcut and set the context if the root selector is an ID
    tokens = match[0] = match[0].slice(0);
    if (tokens.length > 2 && (token = tokens[0]).type === 'ID' &&
      support.getById && context.nodeType === 9 && documentIsHTML &&
      Expr.relative[tokens[1].type]) {

      context = (Expr.find.ID(token.matches[0].replace(runescape, funescape), context) || [])[0];
      if (!context) {
        return results;

        // Precompiled matchers will still verify ancestry, so step up a level
      } else if (compiled) {
        context = context.parentNode;
      }

      selector = selector.slice(tokens.shift().value.length);
    }

    // Fetch a seed set for right-to-left matching
    i = matchExpr.needsContext.test(selector) ? 0 : tokens.length;
    while (i--) {
      token = tokens[i];

      // Abort if we hit a combinator
      if (Expr.relative[(type = token.type)]) {
        break;
      }
      if ((find = Expr.find[type])) {
        // Search, expanding context for leading sibling combinators
        if ((seed = find(
          token.matches[0].replace(runescape, funescape),
          rsibling.test(tokens[0].type) && testContext(context.parentNode) || context
        ))) {

          // If seed is empty or no tokens remain, we can return early
          tokens.splice(i, 1);
          selector = seed.length && toSelector(tokens);
          if (!selector) {
            push.apply(results, seed);
            return results;
          }

          break;
        }
      }
    }
  }

  // Compile and execute a filtering function if one is not provided
  // Provide `match` to avoid retokenization if we modified the selector above
  (compiled || compile(selector, match))(
    seed,
    context,
    !documentIsHTML,
    results,
    rsibling.test(selector) && testContext(context.parentNode) || context
  );
  return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split('').sort(sortOrder).join('') === expando;

// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = true;

export default Sizzle;
