"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScopeContext = exports.InferredSelector = exports.StylableTransformer = exports.transformerDiagnostics = void 0;
const is_vendor_prefixed_1 = __importDefault(require("is-vendor-prefixed"));
const postcss = __importStar(require("postcss"));
const diagnostics_1 = require("./diagnostics");
const functions_1 = require("./functions");
const native_reserved_lists_1 = require("./native-reserved-lists");
const selector_1 = require("./helpers/selector");
const eql_1 = require("./helpers/eql");
const css_selector_parser_1 = require("@tokey/css-selector-parser");
const rule_1 = require("./helpers/rule");
const resolve_1 = require("./helpers/resolve");
const features_1 = require("./features");
const stylable_resolver_1 = require("./stylable-resolver");
const css_custom_property_1 = require("./helpers/css-custom-property");
const postcss_ast_extension_1 = require("./deprecated/postcss-ast-extension");
exports.transformerDiagnostics = {
    UNKNOWN_PSEUDO_ELEMENT: (0, diagnostics_1.createDiagnosticReporter)('12001', 'error', (name) => `unknown pseudo element "${name}"`),
};
class StylableTransformer {
    constructor(options) {
        this.directiveNodes = [];
        this.containerInferredSelectorMap = new Map();
        this.diagnostics = options.diagnostics;
        this.keepValues = options.keepValues || false;
        this.fileProcessor = options.fileProcessor;
        this.replaceValueHook = options.replaceValueHook;
        this.postProcessor = options.postProcessor;
        this.experimentalSelectorInference = options.experimentalSelectorInference === true;
        this.resolver = new stylable_resolver_1.StylableResolver(options.fileProcessor, options.requireModule, options.moduleResolver, options.resolverCache || new Map());
        this.mode = options.mode || 'production';
        this.defaultStVarOverride = options.stVarOverride || {};
        this.getResolvedSymbols = (0, stylable_resolver_1.createSymbolResolverWithCache)(this.resolver, this.diagnostics);
        this.evaluator = new functions_1.StylableEvaluator({
            valueHook: this.replaceValueHook,
            getResolvedSymbols: this.getResolvedSymbols,
        });
    }
    transform(meta) {
        meta.exports = {
            classes: {},
            vars: {},
            stVars: {},
            keyframes: {},
            layers: {},
            containers: {},
        };
        meta.transformedScopes = null;
        meta.targetAst = meta.sourceAst.clone();
        const context = {
            meta,
            diagnostics: this.diagnostics,
            resolver: this.resolver,
            evaluator: this.evaluator,
            getResolvedSymbols: this.getResolvedSymbols,
        };
        features_1.STImport.hooks.transformInit({ context });
        features_1.STGlobal.hooks.transformInit({ context });
        if (!this.experimentalSelectorInference) {
            meta.transformedScopes = validateScopes(this, meta);
        }
        this.transformAst(meta.targetAst, meta, meta.exports);
        meta.transformDiagnostics = this.diagnostics;
        const result = { meta, exports: meta.exports };
        return this.postProcessor ? this.postProcessor(result, this) : result;
    }
    transformAst(ast, meta, metaExports, stVarOverride = this.defaultStVarOverride, path = [], mixinTransform = false, inferredSelectorMixin) {
        if (meta.type !== 'stylable') {
            return;
        }
        const { evaluator } = this;
        const prevStVarOverride = evaluator.stVarOverride;
        evaluator.stVarOverride = stVarOverride;
        const transformContext = {
            meta,
            diagnostics: this.diagnostics,
            resolver: this.resolver,
            evaluator,
            getResolvedSymbols: this.getResolvedSymbols,
            passedThrough: path.slice(),
            inferredSelectorMixin,
        };
        const transformResolveOptions = {
            context: transformContext,
        };
        prepareAST(transformContext, ast, this.experimentalSelectorInference);
        const cssClassResolve = features_1.CSSClass.hooks.transformResolve(transformResolveOptions);
        const stVarResolve = features_1.STVar.hooks.transformResolve(transformResolveOptions);
        const keyframesResolve = features_1.CSSKeyframes.hooks.transformResolve(transformResolveOptions);
        const layerResolve = features_1.CSSLayer.hooks.transformResolve(transformResolveOptions);
        const containsResolve = features_1.CSSContains.hooks.transformResolve(transformResolveOptions);
        const cssVarsMapping = features_1.CSSCustomProperty.hooks.transformResolve(transformResolveOptions);
        const handleAtRule = (atRule) => {
            const { name } = atRule;
            if (name === 'media') {
                features_1.CSSMedia.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: {},
                    transformer: this,
                });
            }
            else if (name === 'property') {
                features_1.CSSCustomProperty.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: cssVarsMapping,
                    transformer: this,
                });
            }
            else if (name === 'keyframes') {
                features_1.CSSKeyframes.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: keyframesResolve,
                    transformer: this,
                });
            }
            else if (name === 'layer') {
                features_1.CSSLayer.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: layerResolve,
                    transformer: this,
                });
            }
            else if (name === 'import') {
                features_1.CSSLayer.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: layerResolve,
                    transformer: this,
                });
            }
            else if (name === 'container') {
                features_1.CSSContains.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: containsResolve,
                    transformer: this,
                });
            }
            else if (name === 'st-scope') {
                features_1.STScope.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: containsResolve,
                    transformer: this,
                });
            }
            else if (name === 'custom-selector') {
                features_1.STCustomSelector.hooks.transformAtRuleNode({
                    context: transformContext,
                    atRule,
                    resolved: containsResolve,
                    transformer: this,
                });
            }
        };
        const handleDeclaration = (decl) => {
            if ((0, css_custom_property_1.validateCustomPropertyName)(decl.prop)) {
                features_1.CSSCustomProperty.hooks.transformDeclaration({
                    context: transformContext,
                    decl,
                    resolved: cssVarsMapping,
                });
            }
            else if (decl.prop === `animation` || decl.prop === `animation-name`) {
                features_1.CSSKeyframes.hooks.transformDeclaration({
                    context: transformContext,
                    decl,
                    resolved: keyframesResolve,
                });
            }
            else if (decl.prop === 'container' || decl.prop === 'container-name') {
                features_1.CSSContains.hooks.transformDeclaration({
                    context: transformContext,
                    decl,
                    resolved: containsResolve,
                });
            }
            if (decl.prop.startsWith('-st-')) {
                if (this.mode === 'production') {
                    this.directiveNodes.push(decl);
                }
                return;
            }
            decl.value = this.evaluator.evaluateValue(transformContext, {
                value: decl.value,
                meta,
                node: decl,
            }).outputValue;
        };
        ast.walk((node) => {
            if (node.type === 'rule') {
                if ((0, rule_1.isChildOfAtRule)(node, 'keyframes')) {
                    return;
                }
                // get context inferred selector
                let currentParent = node.parent;
                while (currentParent && !this.containerInferredSelectorMap.has(currentParent)) {
                    currentParent = currentParent.parent;
                }
                // transform selector
                const { selector, inferredSelector } = this.scopeSelector(meta, node.selector, node, currentParent && this.containerInferredSelectorMap.get(currentParent), inferredSelectorMixin);
                // save results
                this.containerInferredSelectorMap.set(node, inferredSelector);
                node.selector = selector;
            }
            else if (node.type === 'atrule') {
                handleAtRule(node);
            }
            else if (node.type === 'decl') {
                handleDeclaration(node);
            }
        });
        if (!mixinTransform && meta.targetAst && this.mode === 'development') {
            features_1.CSSClass.addDevRules(transformContext);
        }
        const lastPassParams = {
            context: transformContext,
            ast,
            transformer: this,
            path,
        };
        if (this.experimentalSelectorInference) {
            features_1.STScope.hooks.transformLastPass(lastPassParams);
        }
        features_1.STMixin.hooks.transformLastPass(lastPassParams);
        if (!mixinTransform) {
            features_1.STGlobal.hooks.transformLastPass(lastPassParams);
            for (const node of this.directiveNodes) {
                node.remove();
            }
        }
        if (metaExports) {
            features_1.CSSClass.hooks.transformJSExports({
                exports: metaExports,
                resolved: cssClassResolve,
            });
            features_1.STVar.hooks.transformJSExports({
                exports: metaExports,
                resolved: stVarResolve,
            });
            features_1.CSSKeyframes.hooks.transformJSExports({
                exports: metaExports,
                resolved: keyframesResolve,
            });
            features_1.CSSLayer.hooks.transformJSExports({
                exports: metaExports,
                resolved: layerResolve,
            });
            features_1.CSSContains.hooks.transformJSExports({
                exports: metaExports,
                resolved: containsResolve,
            });
            features_1.CSSCustomProperty.hooks.transformJSExports({
                exports: metaExports,
                resolved: cssVarsMapping,
            });
        }
        // restore evaluator state
        this.evaluator.stVarOverride = prevStVarOverride;
    }
    resolveSelectorElements(meta, selector) {
        return this.scopeSelector(meta, selector).elements;
    }
    scopeSelector(originMeta, selector, selectorNode, inferredNestSelector, inferredMixinSelector, unwrapGlobals = false) {
        const context = this.createSelectorContext(originMeta, (0, selector_1.parseSelectorWithCache)(selector, { clone: true }), selectorNode || postcss.rule({ selector }), selector, inferredNestSelector, inferredMixinSelector);
        const targetSelectorAst = this.scopeSelectorAst(context);
        if (unwrapGlobals) {
            features_1.STGlobal.unwrapPseudoGlobals(targetSelectorAst);
        }
        return {
            targetSelectorAst,
            selector: (0, selector_1.stringifySelector)(targetSelectorAst),
            elements: context.elements,
            inferredSelector: context.inferredMultipleSelectors,
        };
    }
    createSelectorContext(meta, selectorAst, selectorNode, selectorStr, selectorNest, selectorMixin) {
        return new ScopeContext(meta, this.resolver, selectorAst, selectorNode, this.scopeSelectorAst.bind(this), this, selectorNest, selectorMixin, undefined, selectorStr);
    }
    createInferredSelector(meta, { name, type }) {
        const resolvedSymbols = this.getResolvedSymbols(meta);
        const resolved = resolvedSymbols[type][name];
        return new InferredSelector(this, resolved);
    }
    scopeSelectorAst(context) {
        // group compound selectors: .a.b .c:hover, a .c:hover -> [[[.a.b], [.c:hover]], [[.a], [.c:hover]]]
        const selectorList = (0, css_selector_parser_1.groupCompoundSelectors)(context.selectorAst);
        // loop over selectors
        for (const selector of selectorList) {
            context.elements.push([]);
            context.selectorIndex++;
            context.selector = selector;
            // loop over nodes
            for (const node of [...selector.nodes]) {
                if (node.type !== `compound_selector`) {
                    if (node.type === 'combinator') {
                        if (this.experimentalSelectorInference || context.isNested) {
                            context.setNextSelectorScope(context.inferredSelectorContext, node);
                        }
                    }
                    continue;
                }
                context.compoundSelector = node;
                // loop over each node in a compound selector
                for (const compoundNode of node.nodes) {
                    if (compoundNode.type === 'universal' && this.experimentalSelectorInference) {
                        context.setNextSelectorScope([
                            {
                                _kind: 'css',
                                meta: context.originMeta,
                                symbol: features_1.CSSType.createSymbol({ name: '*' }),
                            },
                        ], node);
                    }
                    context.node = compoundNode;
                    // transform node
                    this.handleCompoundNode(context);
                }
            }
            // add inferred selector end to multiple selector
            context.inferredMultipleSelectors.add(context.inferredSelector);
            if (selectorList.length - 1 > context.selectorIndex) {
                // reset current anchor for all except last selector
                context.inferredSelector = new InferredSelector(this, context.inferredSelectorStart);
            }
        }
        // backwards compatibility for elements - empty selector still have an empty first target
        if (selectorList.length === 0) {
            context.elements.push([]);
        }
        const targetAst = (0, css_selector_parser_1.splitCompoundSelectors)(selectorList);
        context.splitSelectors.duplicateSelectors(targetAst);
        for (let i = 0; i < targetAst.length; i++) {
            context.selectorAst[i] = targetAst[i];
        }
        return targetAst;
    }
    handleCompoundNode(context) {
        const { inferredSelector, node, originMeta } = context;
        const transformerContext = {
            meta: originMeta,
            diagnostics: this.diagnostics,
            resolver: this.resolver,
            evaluator: this.evaluator,
            getResolvedSymbols: this.getResolvedSymbols,
        };
        if (node.type === 'class') {
            features_1.CSSClass.hooks.transformSelectorNode({
                context: transformerContext,
                selectorContext: context,
                node,
            });
        }
        else if (node.type === 'type') {
            features_1.CSSType.hooks.transformSelectorNode({
                context: transformerContext,
                selectorContext: context,
                node,
            });
        }
        else if (node.type === 'pseudo_element') {
            if (node.value === ``) {
                // partial psuedo elemennt: `.x::`
                // ToDo: currently the transformer corrects the css without warning,
                // should stylable warn?
                return;
            }
            const inferredElement = inferredSelector.getPseudoElements({
                isFirstInSelector: context.isFirstInSelector(node),
                name: node.value,
                experimentalSelectorInference: this.experimentalSelectorInference,
            })[node.value];
            if (inferredElement) {
                context.setNextSelectorScope(inferredElement.inferred, node, node.value);
                if (context.transform) {
                    context.transformIntoMultiSelector(node, inferredElement.selectors);
                }
            }
            else {
                // first definition of a part in the extends/alias chain
                context.setNextSelectorScope([], node, node.value);
                if (!native_reserved_lists_1.nativePseudoElements.includes(node.value) &&
                    !(0, is_vendor_prefixed_1.default)(node.value) &&
                    !context.isDuplicateStScopeDiagnostic()) {
                    this.diagnostics.report(exports.transformerDiagnostics.UNKNOWN_PSEUDO_ELEMENT(node.value), {
                        node: context.ruleOrAtRule,
                        word: node.value,
                    });
                }
            }
        }
        else if (node.type === 'pseudo_class') {
            const isCustomSelector = features_1.STCustomSelector.hooks.transformSelectorNode({
                context: transformerContext,
                selectorContext: context,
                node,
            });
            if (!isCustomSelector) {
                features_1.CSSPseudoClass.hooks.transformSelectorNode({
                    context: transformerContext,
                    selectorContext: context,
                    node,
                });
            }
        }
        else if (node.type === `nesting`) {
            context.setNextSelectorScope(context.inferredSelectorNest, node, node.value);
        }
        else if (node.type === 'attribute') {
            features_1.STMixin.hooks.transformSelectorNode({
                context: transformerContext,
                selectorContext: context,
                node,
            });
        }
    }
}
exports.StylableTransformer = StylableTransformer;
function validateScopes(transformer, meta) {
    const transformedScopes = {};
    for (const scope of meta.scopes) {
        const len = transformer.diagnostics.reports.length;
        const rule = postcss.rule({ selector: scope.params });
        const context = transformer.createSelectorContext(meta, (0, selector_1.parseSelectorWithCache)(rule.selector, { clone: true }), rule, rule.selector);
        transformedScopes[rule.selector] = (0, css_selector_parser_1.groupCompoundSelectors)(transformer.scopeSelectorAst(context));
        const ruleReports = transformer.diagnostics.reports.splice(len);
        for (const { code, message, severity, word } of ruleReports) {
            transformer.diagnostics.report({
                code,
                message,
                severity,
            }, {
                node: scope,
                word: word || scope.params,
            });
        }
    }
    return transformedScopes;
}
function removeInitialCompoundMarker(selector, meta, structureMode) {
    let hadCompoundStart = false;
    const compoundedSelector = (0, css_selector_parser_1.groupCompoundSelectors)(selector);
    const first = compoundedSelector.nodes.find(({ type }) => type === `compound_selector`);
    if (first) {
        const matchNode = structureMode
            ? (node) => node.type === 'nesting'
            : (node) => node.type === 'class' && node.value === meta.root;
        for (let i = 0; i < first.nodes.length; i++) {
            const node = first.nodes[i];
            if (node.type === 'comment') {
                continue;
            }
            if (matchNode(node)) {
                hadCompoundStart = true;
                first.nodes.splice(i, 1);
            }
            break;
        }
    }
    return { selector: (0, css_selector_parser_1.splitCompoundSelectors)(compoundedSelector), hadCompoundStart };
}
class InferredSelector {
    constructor(api, resolve) {
        this.api = api;
        this.resolveSet = new Set();
        if (resolve) {
            this.add(resolve);
        }
    }
    isEmpty() {
        return this.resolveSet.size === 0;
    }
    set(resolve) {
        if (resolve === this) {
            return;
        }
        this.resolveSet.clear();
        this.add(resolve);
    }
    clone() {
        return new InferredSelector(this.api, this);
    }
    /**
     * Adds to the set of inferred resolved CSS
     * Assumes passes CSSResolved from the same meta/symbol are
     * the same from the same cached transform process to dedupe them.
     */
    add(resolve) {
        if (resolve instanceof InferredSelector) {
            resolve.resolveSet.forEach((resolve) => this.add(resolve));
        }
        else {
            this.resolveSet.add(resolve);
        }
    }
    /**
     * Takes a CSS part resolve and use it extend the current set of inferred resolved.
     * Used to expand the resolved mapped selector with the part definition
     * e.g. part can add nested states/parts that override the inferred mapped selector.
     */
    addPartOverride(partResolve) {
        const newSet = new Set();
        for (const resolve of this.resolveSet) {
            newSet.add([partResolve, ...resolve]);
        }
        if (!this.resolveSet.size) {
            newSet.add([partResolve]);
        }
        this.resolveSet = newSet;
    }
    getPseudoClasses({ name: searchedName } = {}) {
        const collectedStates = {};
        const resolvedCount = {};
        const expectedIntersectionCount = this.resolveSet.size; // ToDo: dec for any types
        const addInferredState = (name, meta, state) => {
            const existing = collectedStates[name];
            if (!existing) {
                collectedStates[name] = { meta, state };
                resolvedCount[name] = 1;
            }
            else {
                const isStatesEql = (0, eql_1.isEqual)(existing.state, state);
                if (isStatesEql &&
                    // states from same meta
                    (existing.meta === meta ||
                        // global states
                        typeof state === 'string' ||
                        state?.type === 'template')) {
                    resolvedCount[name]++;
                }
            }
        };
        // infer states from  multiple resolved selectors
        for (const resolvedContext of this.resolveSet.values()) {
            const resolvedFoundNames = new Set();
            resolved: for (const { symbol, meta } of resolvedContext) {
                const states = symbol[`-st-states`];
                if (!states) {
                    continue;
                }
                if (searchedName) {
                    if (Object.hasOwnProperty.call(states, searchedName)) {
                        // track state
                        addInferredState(searchedName, meta, states[searchedName]);
                        break resolved;
                    }
                }
                else {
                    // get all states
                    for (const [name, state] of Object.entries(states)) {
                        if (!resolvedFoundNames.has(name)) {
                            // track state
                            resolvedFoundNames.add(name);
                            addInferredState(name, meta, state);
                        }
                    }
                }
            }
        }
        // strict: remove states that do not exist on ALL resolved selectors
        return expectedIntersectionCount > 1
            ? Object.entries(collectedStates).reduce((resultStates, [name, InferredState]) => {
                if (resolvedCount[name] >= expectedIntersectionCount) {
                    resultStates[name] = InferredState;
                }
                return resultStates;
            }, {})
            : collectedStates;
    }
    getPseudoElements({ isFirstInSelector, experimentalSelectorInference, name, }) {
        const collectedElements = {};
        const resolvedCount = {};
        const checked = {};
        const expectedIntersectionCount = this.resolveSet.size; // ToDo: dec for any types
        const addInferredElement = (name, inferred, selectors) => {
            const item = (collectedElements[name] || (collectedElements[name] = {
                inferred: new InferredSelector(this.api),
                selectors: [],
            }));
            // check inferred matching
            if (!item.inferred.matchedElement(inferred)) {
                // ToDo: bailout fast
                return;
            }
            // add match
            resolvedCount[name]++;
            item.inferred.add(inferred);
            item.selectors.push(...selectors);
        };
        // infer elements from  multiple resolved selectors
        for (const resolvedContext of this.resolveSet.values()) {
            /**
             * search for elements in each resolved selector.
             * start at 1 for legacy flat mode to prefer inherited elements over local
             */
            const startIndex = resolvedContext.length === 1 ||
                (resolvedContext[0] &&
                    (features_1.STStructure.isStructureMode(resolvedContext[0].meta) ||
                        resolvedContext[0].symbol._kind === 'part'))
                ? 0
                : 1;
            resolved: for (let i = startIndex; i < resolvedContext.length; i++) {
                const { symbol, meta } = resolvedContext[i];
                const structureMode = features_1.STStructure.isStructureMode(meta);
                if (symbol._kind !== 'part' &&
                    (symbol.alias || (!structureMode && !symbol['-st-root']))) {
                    // non-root & alias classes don't have parts: bailout
                    continue;
                }
                if (name) {
                    const cacheContext = symbol._kind === 'part' ? symbol.id : symbol.name;
                    const uniqueId = meta.source + '::' + cacheContext;
                    resolvedCount[name] ?? (resolvedCount[name] = 0);
                    checked[name] || (checked[name] = new Map());
                    if (checked[name].has(uniqueId)) {
                        if (checked[name].get(uniqueId)) {
                            resolvedCount[name]++;
                        }
                        continue;
                    }
                    // get part symbol
                    const partDef = features_1.STStructure.getPart(symbol, name);
                    // save to cache
                    checked[name].set(uniqueId, !!partDef);
                    if (!partDef) {
                        continue;
                    }
                    if (Array.isArray(partDef.mapTo)) {
                        // prefer custom selector
                        const selectorList = (0, selector_1.cloneSelector)(partDef.mapTo);
                        const selectorStr = (0, selector_1.stringifySelector)(partDef.mapTo);
                        selectorList.forEach((selector) => {
                            const r = removeInitialCompoundMarker(selector, meta, structureMode);
                            selector.nodes = r.selector.nodes;
                            selector.before = '';
                            if (!r.hadCompoundStart && !isFirstInSelector) {
                                selector.nodes.unshift((0, selector_1.createCombinatorSelector)({ combinator: 'space' }));
                            }
                        });
                        const internalContext = this.api.createSelectorContext(meta, selectorList, postcss.rule({ selector: selectorStr }), selectorStr);
                        internalContext.isStandaloneSelector = isFirstInSelector;
                        if (!structureMode && experimentalSelectorInference) {
                            internalContext.inferredSelectorStart.set(this.api.createInferredSelector(meta, {
                                name: 'root',
                                type: 'class',
                            }));
                            internalContext.inferredSelector.set(internalContext.inferredSelectorStart);
                        }
                        const customAstSelectors = this.api.scopeSelectorAst(internalContext);
                        const inferred = customAstSelectors.length === 1 || experimentalSelectorInference
                            ? internalContext.inferredMultipleSelectors
                            : new InferredSelector(this.api, [
                                {
                                    _kind: 'css',
                                    meta,
                                    symbol: features_1.CSSType.createSymbol({ name: '*' }),
                                },
                            ]);
                        // add part resolve to inferred resolve set
                        if (structureMode) {
                            inferred.addPartOverride({ _kind: 'css', meta, symbol: partDef });
                        }
                        addInferredElement(name, inferred, customAstSelectors);
                        break resolved;
                    }
                    else {
                        // matching class part
                        const resolvedPart = this.api.getResolvedSymbols(meta).class[name];
                        const resolvedBaseSymbol = (0, resolve_1.getOriginDefinition)(resolvedPart);
                        const nodes = [];
                        // insert descendant combinator before internal custom element
                        if (!resolvedBaseSymbol.symbol[`-st-root`] && !isFirstInSelector) {
                            nodes.push((0, selector_1.createCombinatorSelector)({ combinator: 'space' }));
                        }
                        // create part class
                        const classNode = {};
                        features_1.CSSClass.namespaceClass(resolvedBaseSymbol.meta, resolvedBaseSymbol.symbol, classNode);
                        nodes.push(classNode);
                        addInferredElement(name, new InferredSelector(this.api, resolvedPart), [
                            { type: 'selector', after: '', before: '', end: 0, start: 0, nodes },
                        ]);
                        break resolved;
                    }
                }
                else {
                    // ToDo: implement get all elements
                }
            }
        }
        // strict: remove elements that do not exist on ALL resolved selectors
        return expectedIntersectionCount > 1
            ? Object.entries(collectedElements).reduce((resultElements, [name, InferredElement]) => {
                if (resolvedCount[name] >= expectedIntersectionCount) {
                    resultElements[name] = InferredElement;
                }
                return resultElements;
            }, {})
            : collectedElements;
    }
    matchedElement(inferred) {
        for (const target of this.resolveSet) {
            const targetBaseElementSymbol = (0, resolve_1.getOriginDefinition)(target);
            for (const tested of inferred.resolveSet) {
                const testedBaseElementSymbol = (0, resolve_1.getOriginDefinition)(tested);
                if (targetBaseElementSymbol !== testedBaseElementSymbol) {
                    return false;
                }
            }
        }
        return true;
    }
    // function to temporarily handle single resolved selector type while refactoring
    //      ToDo: remove temporarily single resolve
    getSingleResolve() {
        if (this.resolveSet.size !== 1) {
            return [];
        }
        return this.resolveSet.values().next().value;
    }
}
exports.InferredSelector = InferredSelector;
class SelectorMultiplier {
    constructor() {
        this.dupIndicesPerSelector = [];
    }
    addSplitPoint(selectorIndex, nodeIndex, selectors) {
        var _a;
        if (selectors.length) {
            (_a = this.dupIndicesPerSelector)[selectorIndex] || (_a[selectorIndex] = []);
            this.dupIndicesPerSelector[selectorIndex].push([nodeIndex, selectors]);
        }
    }
    duplicateSelectors(targetSelectors) {
        // iterate top level selector
        for (const [selectorIndex, insertionPoints] of Object.entries(this.dupIndicesPerSelector)) {
            const duplicationList = [targetSelectors[Number(selectorIndex)]];
            // iterate insertion points
            for (const [nodeIndex, selectors] of insertionPoints) {
                // collect the duplicate selectors to be multiplied by following insertion points
                const added = [];
                // iterate selectors for insertion point
                for (const replaceSelector of selectors) {
                    // duplicate selectors and replace selector at insertion point
                    for (const originSelector of duplicationList) {
                        const dupSelector = { ...originSelector, nodes: [...originSelector.nodes] };
                        dupSelector.nodes[nodeIndex] = replaceSelector;
                        added.push(dupSelector);
                    }
                }
                // add the duplicated selectors from insertion point to
                // the list of selector to be duplicated for following insertion
                // points and to the target selector list
                for (const addedSelector of added) {
                    duplicationList.push(addedSelector);
                    targetSelectors.push(addedSelector);
                }
            }
        }
    }
}
class ScopeContext {
    constructor(originMeta, resolver, selectorAst, ruleOrAtRule, scopeSelectorAst, transformer, inferredSelectorNest, inferredSelectorMixin, inferredSelectorContext, selectorStr) {
        this.originMeta = originMeta;
        this.resolver = resolver;
        this.selectorAst = selectorAst;
        this.ruleOrAtRule = ruleOrAtRule;
        this.scopeSelectorAst = scopeSelectorAst;
        this.transformer = transformer;
        this.inferredSelectorMixin = inferredSelectorMixin;
        this.transform = true;
        // source multi-selector input
        this.selectorStr = '';
        this.selectorIndex = -1;
        this.elements = [];
        this.selectorAstResolveMap = new Map();
        // store selector duplication points
        this.splitSelectors = new SelectorMultiplier();
        // selector is not a continuation of another selector
        this.isStandaloneSelector = true;
        // combined type of the multiple selectors
        this.inferredMultipleSelectors = new InferredSelector(this.transformer);
        this.isNested = !!(ruleOrAtRule.parent &&
            // top level
            ruleOrAtRule.parent.type !== 'root' &&
            // directly in @st-scope
            !features_1.STScope.isStScopeStatement(ruleOrAtRule.parent));
        /*
            resolve default selector context for initial selector and selector
            following a combinator.
            
            Currently set to stylesheet root for top level selectors and selectors
            directly nested under @st-scope. But will change in the future to a universal selector
            once experimentalSelectorInference will be the default behavior
        */
        const inferredContext = inferredSelectorContext ||
            (this.isNested || transformer.experimentalSelectorInference
                ? new InferredSelector(transformer, [
                    {
                        _kind: 'css',
                        meta: originMeta,
                        symbol: features_1.CSSType.createSymbol({ name: '*' }),
                    },
                ])
                : transformer.createInferredSelector(originMeta, {
                    name: originMeta.root,
                    type: 'class',
                }));
        // set selector data
        this.selectorStr = selectorStr || (0, selector_1.stringifySelector)(selectorAst);
        this.inferredSelectorContext = new InferredSelector(this.transformer, inferredContext);
        this.inferredSelectorStart = new InferredSelector(this.transformer, inferredContext);
        this.inferredSelectorNest = inferredSelectorNest || this.inferredSelectorContext.clone();
        this.inferredSelector = new InferredSelector(this.transformer, this.inferredSelectorContext);
    }
    get experimentalSelectorInference() {
        return this.transformer.experimentalSelectorInference;
    }
    setNextSelectorScope(resolved, node, name) {
        if (name && this.selectorIndex !== undefined && this.selectorIndex !== -1) {
            this.elements[this.selectorIndex].push({
                type: ScopeContext.legacyElementsTypesMapping[node.type] || 'unknown',
                name,
                resolved: Array.isArray(resolved) ? resolved : resolved.getSingleResolve(),
            });
        }
        this.inferredSelector.set(resolved);
        this.selectorAstResolveMap.set(node, this.inferredSelector.clone());
        this.lastInferredSelectorNode = node;
    }
    isFirstInSelector(node) {
        const isFirstNode = this.selectorAst[this.selectorIndex].nodes[0] === node;
        if (isFirstNode && this.selectorIndex === 0 && !this.isStandaloneSelector) {
            // force false incase a this context is a splitted part from another selector
            return false;
        }
        return isFirstNode;
    }
    createNestedContext(selectorAst, selectorContext) {
        const ctx = new ScopeContext(this.originMeta, this.resolver, selectorAst, this.ruleOrAtRule, this.scopeSelectorAst, this.transformer, this.inferredSelectorNest, this.inferredSelectorMixin, selectorContext || this.inferredSelectorContext);
        ctx.transform = this.transform;
        ctx.selectorAstResolveMap = this.selectorAstResolveMap;
        return ctx;
    }
    transformIntoMultiSelector(node, selectors) {
        // transform into the first selector
        Object.assign(node, selectors[0]);
        // keep track of additional selectors for
        // duplication at the end of the selector transform
        selectors.shift();
        const selectorNode = this.selectorAst[this.selectorIndex];
        const nodeIndex = selectorNode.nodes.indexOf(node);
        this.splitSelectors.addSplitPoint(this.selectorIndex, nodeIndex, selectors);
    }
    isDuplicateStScopeDiagnostic() {
        if (this.experimentalSelectorInference || this.ruleOrAtRule.type !== 'rule') {
            // this check is not required when experimentalSelectorInference is on
            // as @st-scope is not flatten at the beginning of the transformation
            // and diagnostics on it's selector is only checked once.
            return false;
        }
        // ToDo: should be removed once st-scope transformation moves to the end of the transform process
        const transformedScope = this.originMeta.transformedScopes?.[(0, postcss_ast_extension_1.getRuleScopeSelector)(this.ruleOrAtRule) || ``];
        if (transformedScope && this.selector && this.compoundSelector) {
            const currentCompoundSelector = (0, selector_1.stringifySelector)(this.compoundSelector);
            const i = this.selector.nodes.indexOf(this.compoundSelector);
            for (const stScopeSelectorCompounded of transformedScope) {
                // if we are in a chunk index that is in the rage of the @st-scope param
                if (i <= stScopeSelectorCompounded.nodes.length) {
                    for (const scopeNode of stScopeSelectorCompounded.nodes) {
                        const scopeNodeSelector = (0, selector_1.stringifySelector)(scopeNode);
                        // if the two chunks match the error is already reported by the @st-scope validation
                        if (scopeNodeSelector === currentCompoundSelector) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
}
exports.ScopeContext = ScopeContext;
ScopeContext.legacyElementsTypesMapping = {
    pseudo_element: 'pseudo-element',
    class: 'class',
    type: 'element',
};
/**
 * in the process of moving transformations that shouldn't be in the analyzer.
 * all changes were moved here to be called at the beginning of the transformer,
 * and should be inlined in the process in the future.
 */
function prepareAST(context, ast, experimentalSelectorInference) {
    // ToDo: inline transformations
    const toRemove = [];
    ast.walk((node) => {
        const input = { context, node, toRemove };
        features_1.STNamespace.hooks.prepareAST(input);
        features_1.STImport.hooks.prepareAST(input);
        if (!experimentalSelectorInference) {
            features_1.STScope.hooks.prepareAST(input);
        }
        features_1.STVar.hooks.prepareAST(input);
        if (!experimentalSelectorInference) {
            features_1.STCustomSelector.hooks.prepareAST(input);
        }
        features_1.CSSCustomProperty.hooks.prepareAST(input);
    });
    for (const removeOrNode of toRemove) {
        typeof removeOrNode === 'function' ? removeOrNode() : removeOrNode.remove();
    }
}
//# sourceMappingURL=stylable-transformer.js.map