"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processNamespace = exports.StylableProcessor = void 0;
const diagnostics_1 = require("./diagnostics");
const native_reserved_lists_1 = require("./native-reserved-lists");
const stylable_meta_1 = require("./stylable-meta");
const features_1 = require("./features");
const diagnostics_2 = require("./features/diagnostics");
const features_2 = require("./features");
const process_declaration_functions_1 = require("./process-declaration-functions");
const selector_1 = require("./helpers/selector");
const rule_1 = require("./helpers/rule");
const feature_1 = require("./features/feature");
class StylableProcessor {
    constructor(diagnostics = new diagnostics_1.Diagnostics(), resolveNamespace = features_2.STNamespace.defaultProcessNamespace, flags = { ...feature_1.defaultFeatureFlags }) {
        this.diagnostics = diagnostics;
        this.resolveNamespace = resolveNamespace;
        this.flags = flags;
    }
    process(root) {
        this.meta = new stylable_meta_1.StylableMeta(root, this.diagnostics, this.flags);
        features_2.STStructure.hooks.analyzeInit(this);
        features_2.STImport.hooks.analyzeInit(this);
        features_1.CSSCustomProperty.hooks.analyzeInit(this);
        this.handleAtRules(root);
        root.walkRules((rule) => {
            if (!(0, rule_1.isChildOfAtRule)(rule, 'keyframes')) {
                this.handleRule(rule, {
                    isScoped: (0, rule_1.isChildOfAtRule)(rule, `st-scope`),
                    reportUnscoped: true,
                });
            }
        });
        root.walkDecls((decl) => {
            features_2.CSSClass.hooks.analyzeDeclaration({ context: this, decl });
            features_1.CSSCustomProperty.hooks.analyzeDeclaration({ context: this, decl });
            features_2.CSSContains.hooks.analyzeDeclaration({ context: this, decl });
            this.collectUrls(decl);
        });
        features_2.STNamespace.hooks.analyzeDone(this);
        features_1.STCustomSelector.hooks.analyzeDone(this);
        features_2.STStructure.hooks.analyzeDone(this);
        features_2.STNamespace.setMetaNamespace(this, this.resolveNamespace);
        features_2.STSymbol.reportRedeclare(this);
        return this.meta;
    }
    handleAtRules(root) {
        const analyzeRule = (rule, { isScoped, originalNode, }) => {
            return this.handleRule(rule, {
                isScoped,
                originalNode,
                reportUnscoped: false,
            });
        };
        root.walkAtRules((atRule) => {
            switch (atRule.name) {
                case 'st-import': {
                    features_2.STImport.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                }
                case 'namespace':
                case 'st-namespace': {
                    features_2.STNamespace.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                }
                case 'keyframes':
                    features_2.CSSKeyframes.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                case 'layer':
                    features_2.CSSLayer.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                case 'import':
                    features_2.STImport.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    features_2.CSSLayer.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                case 'custom-selector': {
                    features_1.STCustomSelector.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                }
                case 'st-scope':
                    features_2.STScope.hooks.analyzeAtRule({ context: this, atRule, analyzeRule });
                    break;
                case 'property':
                case 'st-global-custom-property': {
                    features_1.CSSCustomProperty.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                }
                case 'container': {
                    features_2.CSSContains.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                }
                case 'st': {
                    features_2.STStructure.hooks.analyzeAtRule({
                        context: this,
                        atRule,
                        analyzeRule,
                    });
                    break;
                }
            }
        });
    }
    collectUrls(decl) {
        (0, process_declaration_functions_1.processDeclarationFunctions)(decl, (node) => {
            if (node.type === 'url') {
                this.meta.urls.push(node.url);
            }
        }, false);
    }
    handleRule(rule, { isScoped, reportUnscoped, originalNode = rule, }) {
        const selectorAst = (0, selector_1.parseSelectorWithCache)(rule.selector);
        let locallyScoped = isScoped;
        let topSelectorIndex = -1;
        (0, selector_1.walkSelector)(selectorAst, (node, ...nodeContext) => {
            const [index, nodes, parents] = nodeContext;
            const type = node.type;
            if (type === 'selector' && !(0, selector_1.isInPseudoClassContext)(parents)) {
                // reset scope check between top level selectors
                locallyScoped = isScoped;
                topSelectorIndex++;
            }
            const walkSkip = features_2.STGlobal.hooks.analyzeSelectorNode({
                context: this,
                node,
                topSelectorIndex,
                rule,
                originalNode,
                walkContext: nodeContext,
            });
            if (walkSkip !== undefined) {
                return walkSkip;
            }
            if (node.type === 'pseudo_class') {
                if (node.value === 'import') {
                    features_2.STImport.hooks.analyzeSelectorNode({
                        context: this,
                        node,
                        topSelectorIndex,
                        rule,
                        originalNode,
                        walkContext: nodeContext,
                    });
                }
                else if (node.value === 'vars') {
                    return features_1.STVar.hooks.analyzeSelectorNode({
                        context: this,
                        node,
                        topSelectorIndex,
                        rule,
                        originalNode,
                        walkContext: nodeContext,
                    });
                }
                else if (node.value.startsWith('--')) {
                    // ToDo: move to css-class feature
                    locallyScoped =
                        locallyScoped ||
                            features_1.STCustomSelector.isScoped(this.meta, node.value.slice(2)) ||
                            false;
                }
                else if (!native_reserved_lists_1.knownPseudoClassesWithNestedSelectors.includes(node.value)) {
                    return selector_1.walkSelector.skipNested;
                }
            }
            else if (node.type === 'class') {
                features_2.CSSClass.hooks.analyzeSelectorNode({
                    context: this,
                    node,
                    topSelectorIndex,
                    rule,
                    originalNode,
                    walkContext: nodeContext,
                });
                locallyScoped = features_2.CSSClass.validateClassScoping({
                    context: this,
                    classSymbol: features_2.CSSClass.get(this.meta, node.value),
                    locallyScoped,
                    reportUnscoped,
                    node,
                    nodes,
                    index,
                    rule,
                });
            }
            else if (node.type === 'type') {
                features_2.CSSType.hooks.analyzeSelectorNode({
                    context: this,
                    node,
                    topSelectorIndex,
                    rule,
                    originalNode,
                    walkContext: nodeContext,
                });
                locallyScoped = features_2.CSSType.validateTypeScoping({
                    context: this,
                    locallyScoped,
                    reportUnscoped,
                    node,
                    nodes,
                    index,
                    rule,
                });
            }
            else if (node.type === `id`) {
                if (node.nodes) {
                    this.diagnostics.report(diagnostics_2.generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR(`#` + node.value, `id`), {
                        node: rule,
                        word: (0, selector_1.stringifySelector)(node),
                    });
                }
            }
            else if (node.type === `attribute`) {
                if (node.nodes) {
                    this.diagnostics.report(diagnostics_2.generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR(`[${node.value}]`, `attribute`), {
                        node: rule,
                        word: (0, selector_1.stringifySelector)(node),
                    });
                }
            }
            else if (node.type === `nesting`) {
                if (node.nodes) {
                    this.diagnostics.report(diagnostics_2.generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR(node.value, `nesting`), {
                        node: rule,
                        word: (0, selector_1.stringifySelector)(node),
                    });
                }
            }
            return;
        });
        features_2.STGlobal.hooks.analyzeSelectorDone({
            context: this,
            rule,
            originalNode,
        });
        return locallyScoped;
    }
}
exports.StylableProcessor = StylableProcessor;
// ToDo: remove export and reroute import from feature
exports.processNamespace = features_2.STNamespace.defaultProcessNamespace;
//# sourceMappingURL=stylable-processor.js.map