"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.resolveReferencedVarNames = exports.parseVarsFromExpr = exports.StylablePublicApi = exports.get = exports.hooks = exports.diagnostics = void 0;
const feature_1 = require("./feature");
const custom_values_1 = require("../custom-values");
const diagnostics_1 = require("./diagnostics");
const STSymbol = __importStar(require("./st-symbol"));
const stylable_resolver_1 = require("../stylable-resolver");
const functions_1 = require("../functions");
const rule_1 = require("../helpers/rule");
const selector_1 = require("../helpers/selector");
const value_1 = require("../helpers/value");
const string_1 = require("../helpers/string");
const process_declaration_functions_1 = require("../process-declaration-functions");
const diagnostics_2 = require("../diagnostics");
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
exports.diagnostics = {
    FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: diagnostics_1.generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR,
    NO_VARS_DEF_IN_ST_SCOPE: (0, diagnostics_2.createDiagnosticReporter)('07002', 'error', () => `cannot define ":vars" inside of "@st-scope"`),
    DEPRECATED_ST_FUNCTION_NAME: (0, diagnostics_2.createDiagnosticReporter)('07003', 'info', (name, alternativeName) => `"${name}" is deprecated, use "${alternativeName}"`),
    CYCLIC_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07004', 'error', (cyclicChain) => `Cyclic value definition detected: "${cyclicChain
        .map((s, i) => (i === cyclicChain.length - 1 ? '↻ ' : i === 0 ? '→ ' : '↪ ') + s)
        .join('\n')}"`),
    MISSING_VAR_IN_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07005', 'error', () => `invalid value() with no var identifier`),
    COULD_NOT_RESOLVE_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07006', 'error', (args) => `cannot resolve value function${args ? ` using the arguments provided: "${args}"` : ''}`),
    MULTI_ARGS_IN_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07007', 'error', (args) => `value function accepts only a single argument: "value(${args})"`),
    CANNOT_USE_AS_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07008', 'error', (type, varName) => `${type} "${varName}" cannot be used as a variable`),
    CANNOT_USE_JS_AS_VALUE: (0, diagnostics_2.createDiagnosticReporter)('07009', 'error', (type, varName) => `JavaScript ${type} import "${varName}" cannot be used as a variable`),
    UNKNOWN_VAR: (0, diagnostics_2.createDiagnosticReporter)('07010', 'error', (name) => `unknown var "${name}"`),
};
// HOOKS
exports.hooks = (0, feature_1.createFeature)({
    analyzeSelectorNode({ context, node, rule }) {
        if (node.type !== `pseudo_class` || node.value !== `vars`) {
            return;
        }
        // make sure `:vars` is the only selector
        if (rule.selector === `:vars`) {
            if ((0, rule_1.isChildOfAtRule)(rule, `st-scope`)) {
                context.diagnostics.report(exports.diagnostics.NO_VARS_DEF_IN_ST_SCOPE(), { node: rule });
            }
            else {
                collectVarSymbols(context, rule);
            }
            // stop further walk into `:vars {}`
            return selector_1.walkSelector.stopAll;
        }
        else {
            context.diagnostics.report(exports.diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(`:vars`), {
                node: rule,
            });
        }
        return;
    },
    prepareAST({ node, toRemove }) {
        if (node.type === 'rule' && node.selector === ':vars') {
            toRemove.push(node);
        }
    },
    transformResolve({ context }) {
        // Resolve local vars
        const resolved = {};
        const symbols = STSymbol.getAllByType(context.meta, `var`);
        // Temporarily don't report issues here // ToDo: move reporting here (from value() transformation)
        const noDaigContext = {
            ...context,
            diagnostics: new diagnostics_2.Diagnostics(),
        };
        for (const name of Object.keys(symbols)) {
            const symbol = symbols[name];
            const evaluated = context.evaluator.evaluateValue(noDaigContext, {
                // ToDo: change to `value(${name})` in order to fix overrides in exports
                value: (0, string_1.stripQuotation)(symbol.text),
                meta: context.meta,
                node: symbol.node,
            });
            resolved[name] = evaluated;
        }
        return resolved;
    },
    transformValue({ context, node, data }) {
        evaluateValueCall(context, node, data);
    },
    transformJSExports({ exports, resolved }) {
        for (const [name, { topLevelType, outputValue }] of Object.entries(resolved)) {
            exports.stVars[name] = topLevelType ? (0, custom_values_1.unbox)(topLevelType) : outputValue;
        }
    },
});
// API
function get(meta, name) {
    return STSymbol.get(meta, name, `var`);
}
exports.get = get;
// Stylable StVar Public APIs
const UNKNOWN_LOCATION = Object.freeze({
    offset: -1,
    line: -1,
    column: -1,
});
class StylablePublicApi {
    constructor(stylable) {
        this.stylable = stylable;
    }
    getComputed(meta) {
        const topLevelDiagnostics = new diagnostics_2.Diagnostics();
        const getResolvedSymbols = (0, stylable_resolver_1.createSymbolResolverWithCache)(this.stylable.resolver, topLevelDiagnostics);
        const evaluator = new functions_1.StylableEvaluator({ getResolvedSymbols });
        const { var: stVars } = getResolvedSymbols(meta);
        const computed = {};
        for (const [localName, resolvedVar] of Object.entries(stVars)) {
            const diagnostics = new diagnostics_2.Diagnostics();
            const { outputValue, topLevelType, runtimeValue } = evaluator.evaluateValue({
                resolver: this.stylable.resolver,
                evaluator,
                meta,
                diagnostics,
            }, {
                meta: resolvedVar.meta,
                value: (0, string_1.stripQuotation)(resolvedVar.symbol.text),
                node: resolvedVar.symbol.node,
            });
            const computedStVar = {
                value: runtimeValue ?? outputValue,
                input: topLevelType ?? (0, custom_values_1.unbox)(outputValue, false),
                diagnostics,
                source: {
                    meta: resolvedVar.meta,
                    start: resolvedVar.symbol.node.source?.start || UNKNOWN_LOCATION,
                    end: resolvedVar.symbol.node.source?.end || UNKNOWN_LOCATION,
                },
            };
            computed[localName] = computedStVar;
        }
        return computed;
    }
    flatten(meta) {
        const computed = this.getComputed(meta);
        const flatStVars = [];
        for (const [symbol, stVar] of Object.entries(computed)) {
            flatStVars.push(...this.flatSingle(stVar.input, [symbol], stVar.source));
        }
        return flatStVars;
    }
    flatSingle(input, path, source) {
        const currentVars = [];
        if (input.flatValue) {
            currentVars.push({
                value: input.flatValue,
                path,
                source,
            });
        }
        if (typeof input.value === `object` && input.value !== null) {
            for (const [key, innerInput] of Object.entries(input.value)) {
                currentVars.push(...this.flatSingle(typeof innerInput === 'string' ? (0, custom_values_1.boxString)(innerInput) : innerInput, [...path, key], source));
            }
        }
        return currentVars;
    }
}
exports.StylablePublicApi = StylablePublicApi;
function parseVarsFromExpr(expr) {
    const nameSet = new Set();
    (0, postcss_value_parser_1.default)(expr).walk((node) => {
        if (node.type === 'function' && node.value === 'value') {
            for (const argNode of node.nodes) {
                switch (argNode.type) {
                    case 'word':
                        nameSet.add(argNode.value);
                        return;
                    case 'div':
                        if (argNode.value === ',') {
                            return;
                        }
                }
            }
        }
    });
    return nameSet;
}
exports.parseVarsFromExpr = parseVarsFromExpr;
function collectVarSymbols(context, rule) {
    rule.walkDecls((decl) => {
        warnOnDeprecatedCustomValues(context, decl);
        // check type annotation
        let type = null;
        const prev = decl.prev();
        if (prev && prev.type === 'comment') {
            const typeMatch = prev.text.match(/^@type (.+)$/);
            if (typeMatch) {
                type = typeMatch[1];
            }
        }
        // add symbol
        const name = decl.prop;
        STSymbol.addSymbol({
            context,
            symbol: {
                _kind: 'var',
                name,
                value: '',
                text: decl.value,
                node: decl,
                valueType: type,
            },
            node: decl,
        });
    });
}
function warnOnDeprecatedCustomValues(context, decl) {
    (0, process_declaration_functions_1.processDeclarationFunctions)(decl, (node) => {
        if (node.type === 'nested-item' && custom_values_1.deprecatedStFunctions[node.name]) {
            const { alternativeName } = custom_values_1.deprecatedStFunctions[node.name];
            context.diagnostics.report(exports.diagnostics.DEPRECATED_ST_FUNCTION_NAME(node.name, alternativeName), {
                node: decl,
                word: node.name,
            });
        }
    }, false);
}
function evaluateValueCall(context, parsedNode, data) {
    const { stVarOverride, value, node } = data;
    const passedThrough = context.passedThrough || [];
    const parsedArgs = value_1.strategies.args(parsedNode).map((x) => x.value);
    const varName = parsedArgs[0];
    const restArgs = parsedArgs.slice(1);
    // check var not empty
    if (!varName) {
        if (node) {
            context.diagnostics.report(exports.diagnostics.MISSING_VAR_IN_VALUE(), {
                node,
                word: (0, value_1.getStringValue)(parsedNode),
            });
        }
    }
    else if (parsedArgs.length >= 1) {
        // override with value
        if (stVarOverride?.[varName]) {
            parsedNode.resolvedValue = stVarOverride?.[varName];
            return;
        }
        // check cyclic
        const refUniqID = createUniqID(data.meta.source, varName);
        if (passedThrough.includes(refUniqID)) {
            // TODO: move diagnostic to original value usage instead of the end of the cyclic chain
            handleCyclicValues(context, passedThrough, refUniqID, data.node, value, parsedNode);
            return;
        }
        // resolve
        const resolvedSymbols = context.getResolvedSymbols(data.meta);
        const resolvedVar = resolvedSymbols.var[varName];
        const resolvedVarSymbol = resolvedVar?.symbol;
        const possibleNonSTVarSymbol = STSymbol.get(context.meta, varName);
        if (resolvedVarSymbol) {
            const { outputValue, topLevelType, typeError } = context.evaluator.evaluateValue({ ...context, passedThrough: passedThrough.concat(refUniqID) }, {
                ...data,
                value: (0, string_1.stripQuotation)(resolvedVarSymbol.text),
                args: restArgs,
                node: resolvedVarSymbol.node,
                meta: resolvedVar.meta,
                rootArgument: varName,
                initialNode: node,
            });
            // report errors
            if (node) {
                const argsAsString = parsedArgs.join(', ');
                if (!typeError && !topLevelType && parsedArgs.length > 1) {
                    context.diagnostics.report(exports.diagnostics.MULTI_ARGS_IN_VALUE(argsAsString), {
                        node,
                    });
                }
            }
            parsedNode.resolvedValue = context.evaluator.valueHook
                ? context.evaluator.valueHook(outputValue, varName, true, passedThrough)
                : outputValue;
        }
        else if (possibleNonSTVarSymbol) {
            const type = resolvedSymbols.mainNamespace[varName];
            if (type === `js`) {
                const deepResolve = resolvedSymbols.js[varName];
                const jsValue = deepResolve.symbol;
                if (typeof jsValue === 'string') {
                    parsedNode.resolvedValue = context.evaluator.valueHook
                        ? context.evaluator.valueHook(jsValue, varName, false, passedThrough)
                        : jsValue;
                }
                else if (node) {
                    // unsupported Javascript value
                    // ToDo: provide actual exported id (default/named as x)
                    context.diagnostics.report(exports.diagnostics.CANNOT_USE_JS_AS_VALUE(typeof jsValue, varName), {
                        node,
                        word: varName,
                    });
                }
            }
            else if (type) {
                // report mismatch type
                const deepResolve = resolvedSymbols[type][varName];
                let finalResolve = {
                    _kind: `css`,
                    meta: data.meta,
                    symbol: possibleNonSTVarSymbol,
                };
                if (deepResolve instanceof Array) {
                    // take the deep resolved in order to
                    // print the actual mismatched type
                    finalResolve = deepResolve[deepResolve.length - 1];
                }
                else if (deepResolve._kind === `css`) {
                    finalResolve = deepResolve;
                }
                reportUnsupportedSymbolInValue(context, varName, finalResolve, node);
            }
            else if (node) {
                // report unknown var
                context.diagnostics.report(exports.diagnostics.UNKNOWN_VAR(varName), {
                    node,
                    word: varName,
                });
            }
        }
        else if (node) {
            context.diagnostics.report(exports.diagnostics.UNKNOWN_VAR(varName), {
                node,
                word: varName,
            });
        }
    }
}
function resolveReferencedVarNames(context, initialName) {
    const refNames = new Set();
    const varsToCheck = [
        { meta: context.meta, name: initialName },
    ];
    const checked = new Set();
    while (varsToCheck.length) {
        const { meta, name } = varsToCheck.shift();
        const contextualId = meta.source + '/' + name;
        if (!checked.has(contextualId)) {
            checked.add(contextualId);
            refNames.add(name);
            const symbol = STSymbol.get(meta, name);
            switch (symbol?._kind) {
                case 'var':
                    parseVarsFromExpr(symbol.text).forEach((refName) => varsToCheck.push({
                        meta,
                        name: refName,
                    }));
                    break;
                case 'import': {
                    const resolved = context.resolver.deepResolve(symbol);
                    if (resolved?._kind === 'css' && resolved.symbol?._kind === 'var') {
                        varsToCheck.push({ meta: resolved.meta, name: resolved.symbol.name });
                    }
                    break;
                }
            }
        }
    }
    return refNames;
}
exports.resolveReferencedVarNames = resolveReferencedVarNames;
function reportUnsupportedSymbolInValue(context, name, resolve, node) {
    const symbol = resolve.symbol;
    const errorKind = symbol._kind === 'class' && symbol[`-st-root`] ? 'stylesheet' : symbol._kind;
    if (node) {
        context.diagnostics.report(exports.diagnostics.CANNOT_USE_AS_VALUE(errorKind, name), {
            node,
            word: name,
        });
    }
}
function handleCyclicValues(context, passedThrough, refUniqID, node, value, parsedNode) {
    if (node) {
        const cyclicChain = passedThrough.map((variable) => variable || '');
        cyclicChain.push(refUniqID);
        context.diagnostics.report(exports.diagnostics.CYCLIC_VALUE(cyclicChain), {
            node,
            word: refUniqID, // ToDo: check word is path+var and not var name
        });
    }
    return (0, value_1.stringifyFunction)(value, parsedNode);
}
function createUniqID(source, varName) {
    return `${source}: ${varName}`;
}
//# sourceMappingURL=st-var.js.map