"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileGeneralSelector = void 0;
var attributes_1 = require("./attributes");
var pseudo_selectors_1 = require("./pseudo-selectors");
/*
 * All available rules
 */
function compileGeneralSelector(next, selector, options, context, compileToken) {
    var adapter = options.adapter, equals = options.equals;
    switch (selector.type) {
        case "pseudo-element":
            throw new Error("Pseudo-elements are not supported by css-select");
        case "attribute":
            if (options.strict &&
                (selector.ignoreCase || selector.action === "not")) {
                throw new Error("Unsupported attribute selector");
            }
            return attributes_1.attributeRules[selector.action](next, selector, options);
        case "pseudo":
            return pseudo_selectors_1.compilePseudoSelector(next, selector, options, context, compileToken);
        // Tags
        case "tag":
            return function tag(elem) {
                return adapter.getName(elem) === selector.name && next(elem);
            };
        // Traversal
        case "descendant":
            if (options.cacheResults === false ||
                typeof WeakSet === "undefined") {
                return function descendant(elem) {
                    var current = elem;
                    while ((current = adapter.getParent(current))) {
                        if (adapter.isTag(current) && next(current)) {
                            return true;
                        }
                    }
                    return false;
                };
            }
            // @ts-expect-error `ElementNode` is not extending object
            // eslint-disable-next-line no-case-declarations
            var isFalseCache_1 = new WeakSet();
            return function cachedDescendant(elem) {
                var current = elem;
                while ((current = adapter.getParent(current))) {
                    if (!isFalseCache_1.has(current)) {
                        if (adapter.isTag(current) && next(current)) {
                            return true;
                        }
                        isFalseCache_1.add(current);
                    }
                }
                return false;
            };
        case "_flexibleDescendant":
            // Include element itself, only used while querying an array
            return function flexibleDescendant(elem) {
                var current = elem;
                do {
                    if (adapter.isTag(current) && next(current))
                        return true;
                } while ((current = adapter.getParent(current)));
                return false;
            };
        case "parent":
            if (options.strict) {
                throw new Error("Parent selector isn't part of CSS3");
            }
            return function parent(elem) {
                return adapter
                    .getChildren(elem)
                    .some(function (elem) { return adapter.isTag(elem) && next(elem); });
            };
        case "child":
            return function child(elem) {
                var parent = adapter.getParent(elem);
                return !!parent && adapter.isTag(parent) && next(parent);
            };
        case "sibling":
            return function sibling(elem) {
                var siblings = adapter.getSiblings(elem);
                for (var i = 0; i < siblings.length; i++) {
                    var currentSibling = siblings[i];
                    if (equals(elem, currentSibling))
                        break;
                    if (adapter.isTag(currentSibling) && next(currentSibling)) {
                        return true;
                    }
                }
                return false;
            };
        case "adjacent":
            return function adjacent(elem) {
                var siblings = adapter.getSiblings(elem);
                var lastElement;
                for (var i = 0; i < siblings.length; i++) {
                    var currentSibling = siblings[i];
                    if (equals(elem, currentSibling))
                        break;
                    if (adapter.isTag(currentSibling)) {
                        lastElement = currentSibling;
                    }
                }
                return !!lastElement && next(lastElement);
            };
        case "universal":
            return next;
    }
}
exports.compileGeneralSelector = compileGeneralSelector;
