"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.scopeNestedSelector = exports.isCompRoot = exports.matchTypeAndValue = exports.isInPseudoClassContext = exports.createCombinatorSelector = exports.convertToPseudoClass = exports.convertToSelector = exports.convertToInvalid = exports.convertToAttribute = exports.convertToClass = exports.flattenFunctionalSelector = exports.isSimpleSelector = exports.cloneSelector = exports.parseSelectorWithCache = exports.walkSelector = exports.stringifySelector = exports.parseSelector = void 0;
const css_selector_parser_1 = require("@tokey/css-selector-parser");
const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
exports.parseSelector = css_selector_parser_1.parseCssSelector;
exports.stringifySelector = css_selector_parser_1.stringifySelectorAst;
exports.walkSelector = css_selector_parser_1.walk;
/**
 * parse selectors and cache them
 */
const selectorAstCache = new Map();
function parseSelectorWithCache(selector, options = {}) {
    if (!selectorAstCache.has(selector)) {
        if (selectorAstCache.size > 10000) {
            selectorAstCache.delete(selectorAstCache.keys().next().value);
        }
        selectorAstCache.set(selector, (0, css_selector_parser_1.parseCssSelector)(selector));
    }
    const cachedValue = selectorAstCache.get(selector);
    return options.clone
        ? (0, lodash_clonedeep_1.default)(cachedValue)
        : cachedValue;
}
exports.parseSelectorWithCache = parseSelectorWithCache;
function cloneSelector(s) {
    return (0, lodash_clonedeep_1.default)(s);
}
exports.cloneSelector = cloneSelector;
/**
 * returns for each selector if it contains only
 * a single class or an element selector.
 */
function isSimpleSelector(selector) {
    const selectorList = parseSelectorWithCache(selector);
    return selectorList.map((selector) => {
        let foundType = ``;
        (0, css_selector_parser_1.walk)(selector, (node) => {
            if ((node.type !== `class` && node.type !== `type`) || foundType || node.nodes) {
                foundType = `complex`;
                return css_selector_parser_1.walk.stopAll;
            }
            foundType = node.type;
            return;
        }, { ignoreList: [`selector`, `comment`] });
        if (foundType === `class` || foundType === `type`) {
            return { type: foundType, isSimple: true };
        }
        else {
            return { type: `complex`, isSimple: false };
        }
    });
}
exports.isSimpleSelector = isSimpleSelector;
/**
 * take an ast node with nested nodes "XXX(nest1,  nest2)"
 * and convert it to a flat selector as node: "nest1, nest2"
 */
function flattenFunctionalSelector(node) {
    node.value = ``;
    return convertToSelector(node);
}
exports.flattenFunctionalSelector = flattenFunctionalSelector;
/**
 * ast convertors
 */
function convertToClass(node) {
    const castedNode = node;
    castedNode.type = `class`;
    castedNode.dotComments = [];
    return castedNode;
}
exports.convertToClass = convertToClass;
function convertToAttribute(node) {
    const castedNode = node;
    castedNode.type = `attribute`;
    return castedNode;
}
exports.convertToAttribute = convertToAttribute;
function convertToInvalid(node) {
    const castedNode = node;
    castedNode.type = `invalid`;
    return castedNode;
}
exports.convertToInvalid = convertToInvalid;
function convertToSelector(node) {
    const castedNode = node;
    castedNode.type = `selector`;
    castedNode.before || (castedNode.before = ``);
    castedNode.after || (castedNode.after = ``);
    // ToDo: should this fix castedNode.end?
    return castedNode;
}
exports.convertToSelector = convertToSelector;
function convertToPseudoClass(node, name, nestedSelectors) {
    const castedNode = node;
    castedNode.type = 'pseudo_class';
    castedNode.value = name;
    castedNode.colonComments = [];
    if (nestedSelectors) {
        castedNode.nodes = nestedSelectors;
    }
    else {
        delete castedNode.nodes;
    }
    return castedNode;
}
exports.convertToPseudoClass = convertToPseudoClass;
function createCombinatorSelector(partial) {
    const type = partial.combinator || 'space';
    return {
        type: `combinator`,
        combinator: type,
        value: partial.value ?? (type === 'space' ? ` ` : type),
        before: partial.before ?? ``,
        after: partial.after ?? ``,
        start: partial.start ?? 0,
        end: partial.end ?? 0,
        invalid: partial.invalid ?? false,
    };
}
exports.createCombinatorSelector = createCombinatorSelector;
function isInPseudoClassContext(parents) {
    for (const parent of parents) {
        if (parent.type === `pseudo_class`) {
            return true;
        }
    }
    return false;
}
exports.isInPseudoClassContext = isInPseudoClassContext;
function matchTypeAndValue(a, b) {
    return a.type === b.type && a.value === b.value;
}
exports.matchTypeAndValue = matchTypeAndValue;
function isCompRoot(name) {
    return name.charAt(0).match(/[A-Z]/);
}
exports.isCompRoot = isCompRoot;
const isNestedNode = (node) => node.type === 'nesting';
/**
 * combine 2 selector lists.
 * - add each scoping selector at the begging of each nested selector
 * - replace any nesting `&` nodes in the nested selector with the scoping selector nodes
 */
function scopeNestedSelector(scopeSelectorAst, nestedSelectorAst, rootScopeLevel = false, isAnchor = isNestedNode) {
    const resultSelectors = [];
    nestedSelectorAst.forEach((targetAst) => {
        scopeSelectorAst.forEach((scopeAst) => {
            const outputAst = (0, lodash_clonedeep_1.default)(targetAst);
            outputAst.before = scopeAst.before || outputAst.before;
            let first = outputAst.nodes[0];
            // search first actual first selector part
            (0, exports.walkSelector)(outputAst, (node) => {
                first = node;
                return exports.walkSelector.stopAll;
            }, { ignoreList: [`selector`] });
            // merge scope flags
            const nestStartWithNesting = first.type === `nesting`;
            const nestedStartWithGlobal = rootScopeLevel && first.type === `pseudo_class` && first.value === `global`;
            const nestStartWithScope = rootScopeLevel &&
                scopeAst.nodes.every((node, i) => {
                    return matchTypeAndValue(node, outputAst.nodes[i]);
                });
            let scopeAlreadyMerged = false;
            // merge scope into selector
            (0, exports.walkSelector)(outputAst, (node, i, nodes) => {
                if (isAnchor(node)) {
                    scopeAlreadyMerged = true;
                    nodes.splice(i, 1, {
                        type: `selector`,
                        nodes: (0, lodash_clonedeep_1.default)(scopeAst.nodes),
                        start: node.start,
                        end: node.end,
                        after: ``,
                        before: ``,
                    });
                }
            });
            // merge scope at the beginning of selector
            if (first &&
                !nestStartWithNesting &&
                !nestStartWithScope &&
                !nestedStartWithGlobal &&
                !scopeAlreadyMerged) {
                outputAst.nodes.unshift(...(0, lodash_clonedeep_1.default)(scopeAst.nodes), {
                    type: `combinator`,
                    combinator: `space`,
                    value: ` `,
                    before: ``,
                    after: ``,
                    start: first.start,
                    end: first.start,
                    invalid: false,
                });
            }
            resultSelectors.push(outputAst);
        });
    });
    return {
        selector: (0, exports.stringifySelector)(resultSelectors),
        ast: resultSelectors,
    };
}
exports.scopeNestedSelector = scopeNestedSelector;
//# sourceMappingURL=selector.js.map