"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.scopeCSSVar = exports.getTransformedName = exports.getRuntimeTypedDefinitionNames = exports.StylablePublicApi = exports.addCSSProperty = exports.get = exports.transformPropertyIdent = exports.hooks = exports.diagnostics = void 0;
const feature_1 = require("./feature");
const STSymbol = __importStar(require("./st-symbol"));
const css_custom_property_1 = require("../helpers/css-custom-property");
const value_1 = require("../helpers/value");
const global_1 = require("../helpers/global");
const plugable_record_1 = require("../helpers/plugable-record");
const diagnostics_1 = require("../diagnostics");
const stylable_resolver_1 = require("../stylable-resolver");
// ToDo: refactor out - parse once and pass to hooks
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
exports.diagnostics = {
    ...css_custom_property_1.atPropertyValidationWarnings,
    ILLEGAL_CSS_VAR_USE: (0, diagnostics_1.createDiagnosticReporter)('01005', 'error', (name) => `a custom css property must begin with "--" (double-dash), but received "${name}"`),
    ILLEGAL_CSS_VAR_ARGS: (0, diagnostics_1.createDiagnosticReporter)('01006', 'error', (name) => `custom property "${name}" usage (var()) must receive comma separated values`),
    DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY: (0, diagnostics_1.createDiagnosticReporter)('01007', 'info', () => `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${global_1.GLOBAL_FUNC}`),
    GLOBAL_CSS_VAR_MISSING_COMMA: (0, diagnostics_1.createDiagnosticReporter)('01008', 'error', (name) => `"@st-global-custom-property" received the value "${name}", but its values must be comma separated`),
    ILLEGAL_GLOBAL_CSS_VAR: (0, diagnostics_1.createDiagnosticReporter)('01009', 'error', (name) => `"@st-global-custom-property" received the value "${name}", but it must begin with "--" (double-dash)`),
    MISSING_PROP_NAME: (0, diagnostics_1.createDiagnosticReporter)('01010', 'error', () => `missing custom property name for "var(--[PROP NAME])"`),
    UNDEFINED_CSS_CUSTOM_PROP: (0, diagnostics_1.createDiagnosticReporter)('01011', 'error', (name) => `Undefined "${name}" custom property. Please define the property using '@property' or import it with '@st-import' when 'strictCustomProperty' is enabled.`),
};
const dataKey = plugable_record_1.plugableRecord.key('custom-property');
exports.hooks = (0, feature_1.createFeature)({
    metaInit({ meta }) {
        plugable_record_1.plugableRecord.set(meta.data, dataKey, {
            stCustomGlobalProperty: {},
            typedDefinitions: {},
        });
    },
    analyzeInit(context) {
        // ToDo: move to `STImport.ImportTypeHook`
        for (const [symbolName, symbol] of Object.entries(STSymbol.getAllByType(context.meta, `import`))) {
            if ((0, css_custom_property_1.validateCustomPropertyName)(symbolName)) {
                const importSymbol = STSymbol.get(context.meta, symbolName, `import`);
                if (!importSymbol) {
                    console.warn(`imported symbol "${symbolName}" not found on "${context.meta.source}"`);
                    continue;
                }
                addCSSProperty({
                    context,
                    node: symbol.import.rule,
                    name: symbolName,
                    global: false,
                    final: true,
                    alias: importSymbol,
                });
            }
        }
    },
    analyzeAtRule({ context, atRule }) {
        const isStylable = context.meta.type === 'stylable';
        if (atRule.name === `property`) {
            let name = atRule.params;
            let global = !isStylable;
            // check global
            const globalVarName = isStylable ? (0, global_1.globalValue)(name) : undefined;
            if (globalVarName !== undefined) {
                name = globalVarName.trim();
                global = true;
            }
            const { stCustomGlobalProperty, typedDefinitions } = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
            // handle conflict with deprecated `@st-global-custom-property`
            if (stCustomGlobalProperty[name]) {
                global = true;
            }
            addCSSProperty({
                context,
                node: atRule,
                name,
                global,
                final: true,
            });
            (0, css_custom_property_1.validateAtProperty)(atRule, context.diagnostics);
            // save reference to runtime definitions
            if (atRule.nodes) {
                typedDefinitions[name] ?? (typedDefinitions[name] = []);
                typedDefinitions[name].push(atRule);
            }
        }
        else if (atRule.name === `st-global-custom-property` && isStylable) {
            analyzeDeprecatedStGlobalCustomProperty(context, atRule);
        }
    },
    analyzeDeclaration({ context, decl }) {
        // register prop
        if ((0, css_custom_property_1.validateCustomPropertyName)(decl.prop)) {
            addCSSProperty({
                context,
                node: decl,
                name: decl.prop,
                global: context.meta.type === 'css',
                final: false,
            });
        }
        // register value
        if (decl.value.includes('var(')) {
            analyzeDeclValueVarCalls(context, decl);
        }
    },
    prepareAST({ context, node, toRemove }) {
        if (node.type === `atrule` &&
            node.name === 'st-global-custom-property' &&
            context.meta.type === 'stylable') {
            toRemove.push(node);
        }
    },
    transformResolve({ context: { meta, getResolvedSymbols } }) {
        const customPropsMapping = {
            localToGlobal: {},
            locals: new Set(),
        };
        const resolvedSymbols = getResolvedSymbols(meta);
        for (const [localVarName, localSymbol] of Object.entries(STSymbol.getAllByType(meta, `cssVar`))) {
            const resolve = resolveFinalSymbol(meta, localSymbol, resolvedSymbols);
            customPropsMapping.localToGlobal[localVarName] = getTransformedName(resolve);
            if (resolve.meta === meta) {
                customPropsMapping.locals.add(localVarName);
            }
        }
        return customPropsMapping;
    },
    transformAtRuleNode({ context, atRule, resolved }) {
        if (atRule.name !== `property`) {
            return;
        }
        if (atRule.nodes?.length) {
            const propName = (0, global_1.globalValue)(atRule.params) || atRule.params;
            if (resolved.localToGlobal[propName]) {
                atRule.params = resolved.localToGlobal[propName] || atRule.params;
            }
        }
        else if (context.meta.type === 'stylable') {
            // remove `@property` with no body
            atRule.remove();
        }
    },
    transformDeclaration({ decl, resolved }) {
        decl.prop = resolved.localToGlobal[decl.prop] || decl.prop;
    },
    transformValue({ node, data: { meta }, context: { getResolvedSymbols } }) {
        const { value } = node;
        const varWithPrefix = node.nodes[0]?.value || ``;
        if ((0, css_custom_property_1.validateCustomPropertyName)(varWithPrefix)) {
            transformPropertyIdent(meta, node.nodes[0], getResolvedSymbols);
        }
        // handle default values - ToDo: check if required
        if (node.nodes.length > 2) {
            node.resolvedValue = (0, value_1.stringifyFunction)(value, node);
        }
    },
    transformJSExports({ exports, resolved }) {
        for (const varName of resolved.locals) {
            exports.vars[varName.slice(2)] = resolved.localToGlobal[varName];
        }
    },
});
// API
function transformPropertyIdent(meta, node, getResolvedSymbols) {
    const varWithPrefix = node.value || '';
    const resolvedSymbols = getResolvedSymbols(meta);
    const localSymbol = STSymbol.get(meta, varWithPrefix, `cssVar`);
    if (localSymbol) {
        node.value = getTransformedName(resolveFinalSymbol(meta, localSymbol, resolvedSymbols));
    }
}
exports.transformPropertyIdent = transformPropertyIdent;
function get(meta, name) {
    return STSymbol.get(meta, name, `cssVar`);
}
exports.get = get;
function resolveFinalSymbol(meta, localSymbol, resolvedSymbols) {
    return (resolvedSymbols.cssVar[localSymbol.name] || {
        // fallback to local namespace
        _kind: `css`,
        symbol: localSymbol,
        meta,
    });
}
function addCSSProperty({ context, node, name, global, final, alias, }) {
    // validate indent
    if (!(0, css_custom_property_1.validateCustomPropertyName)(name)) {
        context.diagnostics.report(exports.diagnostics.ILLEGAL_CSS_VAR_USE(name), {
            node,
            word: name,
        });
        return;
    }
    // usages bailout: addition of weak definition reference `--x: var(--x)`
    if (!final) {
        const existing = STSymbol.get(context.meta, name, `cssVar`);
        if (existing) {
            // already defined
            return;
            // eslint-disable-next-line no-constant-condition
        }
        else if (context.meta.type === 'stylable' && context.meta.flags.strictCustomProperty) {
            // strict mode
            context.diagnostics.report(exports.diagnostics.UNDEFINED_CSS_CUSTOM_PROP(name), {
                node,
                word: name,
            });
        }
    }
    // define symbol
    STSymbol.addSymbol({
        context,
        symbol: {
            _kind: 'cssVar',
            name: name,
            global,
            alias,
        },
        safeRedeclare: !final || !!alias,
        node,
    });
}
exports.addCSSProperty = addCSSProperty;
const UNKNOWN_LOCATION = Object.freeze({
    offset: -1,
    line: -1,
    column: -1,
});
class StylablePublicApi {
    constructor(stylable) {
        this.stylable = stylable;
    }
    getProperties(meta) {
        const results = {};
        const topLevelDiagnostics = new diagnostics_1.Diagnostics();
        const getResolvedSymbols = (0, stylable_resolver_1.createSymbolResolverWithCache)(this.stylable.resolver, topLevelDiagnostics);
        const { cssVar } = getResolvedSymbols(meta);
        for (const [name, symbol] of Object.entries(cssVar)) {
            const defAst = STSymbol.getSymbolAstNode(symbol.meta, symbol.symbol);
            results[name] = {
                meta: symbol.meta,
                localName: symbol.symbol.name,
                targetName: getTransformedName(symbol),
                source: {
                    meta: symbol.meta,
                    start: defAst?.source?.start || UNKNOWN_LOCATION,
                    end: defAst?.source?.end || UNKNOWN_LOCATION,
                },
            };
        }
        return results;
    }
}
exports.StylablePublicApi = StylablePublicApi;
function analyzeDeclValueVarCalls(context, decl) {
    const parsed = (0, postcss_value_parser_1.default)(decl.value);
    parsed.walk((node) => {
        if (node.type === 'function' && node.value === 'var' && node.nodes) {
            const varName = node.nodes[0];
            if (!varName) {
                context.diagnostics.report(exports.diagnostics.MISSING_PROP_NAME(), {
                    node: decl,
                });
                return;
            }
            if (!(0, value_1.validateAllowedNodesUntil)(node, 1)) {
                const args = postcss_value_parser_1.default.stringify(node.nodes);
                context.diagnostics.report(exports.diagnostics.ILLEGAL_CSS_VAR_ARGS(args), {
                    node: decl,
                    word: args,
                });
            }
            addCSSProperty({
                context,
                name: postcss_value_parser_1.default.stringify(varName)?.trim() || ``,
                node: decl,
                global: context.meta.type === 'css',
                final: false,
            });
        }
    });
}
function analyzeDeprecatedStGlobalCustomProperty(context, atRule) {
    // report deprecation
    context.diagnostics.report(exports.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), {
        node: atRule,
    });
    //
    const cssVarsByComma = atRule.params.split(',');
    const cssVarsBySpacing = atRule.params
        .trim()
        .split(/\s+/g)
        .filter((s) => s !== ',');
    if (cssVarsBySpacing.length > cssVarsByComma.length) {
        context.diagnostics.report(exports.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), {
            node: atRule,
            word: atRule.params,
        });
        return;
    }
    for (const entry of cssVarsByComma) {
        const name = entry.trim();
        if ((0, css_custom_property_1.validateCustomPropertyName)(name)) {
            // ToDo: change to modify global instead of override
            addCSSProperty({
                context,
                node: atRule,
                name,
                global: true,
                final: true,
            });
            // keep track of defined props through `@st-custom-global-property` in order
            // to not override the default with following `@property` definitions
            const { stCustomGlobalProperty } = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
            stCustomGlobalProperty[name] = STSymbol.get(context.meta, name, `cssVar`);
        }
        else {
            context.diagnostics.report(exports.diagnostics.ILLEGAL_GLOBAL_CSS_VAR(name), {
                node: atRule,
                word: name,
            });
        }
    }
}
function getRuntimeTypedDefinitionNames(meta) {
    const { typedDefinitions } = plugable_record_1.plugableRecord.getUnsafe(meta.data, dataKey);
    return Object.keys(typedDefinitions);
}
exports.getRuntimeTypedDefinitionNames = getRuntimeTypedDefinitionNames;
function getTransformedName({ symbol, meta }) {
    return symbol.global ? symbol.name : (0, css_custom_property_1.generateScopedCSSVar)(meta.namespace, symbol.name.slice(2));
}
exports.getTransformedName = getTransformedName;
function scopeCSSVar(resolver, meta, symbolName) {
    const importedVar = resolver.deepResolve(STSymbol.get(meta, symbolName));
    if (importedVar &&
        importedVar._kind === 'css' &&
        importedVar.symbol &&
        importedVar.symbol._kind === 'cssVar') {
        importedVar;
        return getTransformedName(importedVar);
    }
    const cssVar = STSymbol.get(meta, symbolName, `cssVar`);
    return cssVar?.global ? symbolName : (0, css_custom_property_1.generateScopedCSSVar)(meta.namespace, symbolName.slice(2));
}
exports.scopeCSSVar = scopeCSSVar;
//# sourceMappingURL=css-custom-property.js.map