"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSymbolResolverWithCache = exports.StylableResolver = exports.isValidCSSResolve = void 0;
const features_1 = require("./features");
const rule_1 = require("./helpers/rule");
const custom_values_1 = require("./custom-values");
function isValidCSSResolve(resolved) {
    return resolved.symbol !== undefined;
}
exports.isValidCSSResolve = isValidCSSResolve;
function isInPath(extendPath, { symbol: { name: name1 }, meta: { source: source1 } }) {
    return extendPath.find(({ symbol: { name }, meta: { source } }) => {
        return name1 === name && source === source1;
    });
}
// this is a safe cache key delimiter for all OS;
const safePathDelimiter = ';:';
class StylableResolver {
    constructor(fileProcessor, requireModule, moduleResolver, cache) {
        this.fileProcessor = fileProcessor;
        this.requireModule = requireModule;
        this.moduleResolver = moduleResolver;
        this.cache = cache;
    }
    getModule({ context, request, }) {
        let entity;
        let resolvedPath;
        const key = cacheKey(context, request);
        if (this.cache?.has(key)) {
            const entity = this.cache.get(key);
            if (entity.kind === 'resolve') {
                resolvedPath = entity.resolvedPath;
            }
            else {
                return entity;
            }
        }
        try {
            resolvedPath || (resolvedPath = this.moduleResolver(context, request));
        }
        catch (error) {
            entity = {
                kind: request.endsWith('css') ? 'css' : 'js',
                value: null,
                error,
                request,
                context,
                resolvedPath: undefined,
            };
            this.cache?.set(key, entity);
            return entity;
        }
        if (resolvedPath.endsWith('.css')) {
            const kind = 'css';
            try {
                entity = { kind, value: this.fileProcessor.process(resolvedPath), resolvedPath };
            }
            catch (error) {
                entity = { kind, value: null, error, request, context, resolvedPath };
            }
        }
        else {
            const kind = 'js';
            try {
                entity = { kind, value: this.requireModule(resolvedPath), resolvedPath };
            }
            catch (error) {
                entity = { kind, value: null, error, request, context, resolvedPath };
            }
        }
        this.cache?.set(key, entity);
        return entity;
    }
    analyze(filePath) {
        return this.fileProcessor.process(filePath);
    }
    resolvePath(directoryPath, request) {
        const key = cacheKey(directoryPath, request);
        let resolvedPath = this.cache?.get(key)?.resolvedPath;
        if (resolvedPath !== undefined) {
            return resolvedPath;
        }
        resolvedPath = this.moduleResolver(directoryPath, request);
        this.cache?.set(key, {
            resolvedPath,
            value: null,
            kind: 'resolve',
        });
        return resolvedPath;
    }
    resolveImported(imported, name, subtype = 'mappedSymbols') {
        const res = this.getModule(imported);
        if (res.value === null) {
            return null;
        }
        if (res.kind === 'css') {
            const { value: meta } = res;
            const namespace = subtype === `mappedSymbols`
                ? `main`
                : subtype === 'mappedKeyframes'
                    ? `keyframes`
                    : subtype;
            name = !name && namespace === `main` ? meta.root : name;
            const symbol = features_1.STSymbol.getAll(meta, namespace)[name];
            return {
                _kind: 'css',
                symbol,
                meta,
            };
        }
        else {
            const { value: jsModule } = res;
            return {
                _kind: 'js',
                symbol: !name ? jsModule.default || jsModule : jsModule[name],
                meta: null,
            };
        }
    }
    resolveImport(importSymbol) {
        const name = importSymbol.type === 'named' ? importSymbol.name : '';
        return this.resolveImported(importSymbol.import, name);
    }
    resolve(maybeImport) {
        if (!maybeImport || maybeImport._kind !== 'import') {
            if (maybeImport &&
                maybeImport._kind !== 'var' &&
                maybeImport._kind !== 'cssVar' &&
                maybeImport._kind !== 'keyframes' &&
                maybeImport._kind !== 'layer' &&
                maybeImport._kind !== 'container') {
                if (maybeImport.alias && !maybeImport[`-st-extends`]) {
                    maybeImport = maybeImport.alias;
                }
                else if (maybeImport[`-st-extends`]) {
                    maybeImport = maybeImport[`-st-extends`];
                }
                else {
                    return null;
                }
            }
            else if (maybeImport && maybeImport._kind === 'cssVar') {
                if (maybeImport.alias) {
                    maybeImport = maybeImport.alias;
                }
                else {
                    return null;
                }
            }
            else {
                return null;
            }
        }
        if (!maybeImport || maybeImport._kind !== 'import') {
            return null;
        }
        return this.resolveImport(maybeImport);
    }
    deepResolve(maybeImport, path = []) {
        let resolved = this.resolve(maybeImport);
        while (resolved &&
            resolved._kind === 'css' &&
            resolved.symbol &&
            resolved.symbol._kind === 'import') {
            resolved = this.resolve(resolved.symbol);
        }
        if (resolved && resolved.symbol && resolved.meta) {
            if (((resolved.symbol._kind === 'class' || resolved.symbol._kind === 'element') &&
                resolved.symbol.alias &&
                !resolved.symbol[`-st-extends`]) ||
                (resolved.symbol._kind === 'cssVar' && resolved.symbol.alias)) {
                if (path.includes(resolved.symbol)) {
                    return { _kind: 'css', symbol: resolved.symbol, meta: resolved.meta };
                }
                path.push(resolved.symbol);
                return this.deepResolve(resolved.symbol.alias, path);
            }
        }
        return resolved;
    }
    resolveSymbolOrigin(symbol, meta, path = []) {
        if (!symbol || !meta) {
            return null;
        }
        if (symbol._kind === 'element' || symbol._kind === 'class') {
            if (path.includes(symbol)) {
                return { meta, symbol, _kind: 'css' };
            }
            path.push(symbol);
            const isAliasOnly = symbol.alias && !symbol[`-st-extends`];
            return isAliasOnly
                ? this.resolveSymbolOrigin(symbol.alias, meta, path)
                : { meta, symbol, _kind: 'css' };
        }
        else if (symbol._kind === 'cssVar') {
            if (path.includes(symbol)) {
                return { meta, symbol, _kind: 'css' };
            }
        }
        else if (symbol._kind === 'import') {
            const resolved = this.resolveImport(symbol);
            if (resolved && resolved.symbol && resolved._kind === 'css') {
                return this.resolveSymbolOrigin(resolved.symbol, resolved.meta, path);
            }
            else {
                return null;
            }
        }
        return null;
    }
    resolveSymbols(meta, diagnostics) {
        const resolvedSymbols = {
            mainNamespace: {},
            class: {},
            element: {},
            var: {},
            js: {},
            customValues: { ...custom_values_1.stTypes },
            keyframes: {},
            layer: {},
            container: {},
            cssVar: {},
            import: {},
        };
        // resolve main namespace
        for (const [name, symbol] of Object.entries(meta.getAllSymbols())) {
            let deepResolved;
            if (symbol._kind === `import` || (symbol._kind === `cssVar` && symbol.alias)) {
                deepResolved = this.deepResolve(symbol);
                if (!deepResolved || !deepResolved.symbol) {
                    // diagnostics for unresolved imports are reported
                    // as part of st-import validateImports
                    continue;
                }
                else if (deepResolved?._kind === `js`) {
                    resolvedSymbols.js[name] = deepResolved;
                    resolvedSymbols.mainNamespace[name] = `js`;
                    const customValueSymbol = deepResolved.symbol;
                    if ((0, custom_values_1.isCustomValue)(customValueSymbol)) {
                        resolvedSymbols.customValues[name] = customValueSymbol.register(name);
                    }
                    continue;
                }
                else if (symbol._kind !== `cssVar` &&
                    (deepResolved.symbol._kind === `class` ||
                        deepResolved.symbol._kind === `element`)) {
                    // virtual alias
                    deepResolved = {
                        _kind: `css`,
                        meta,
                        symbol: features_1.CSSClass.createSymbol({ name, alias: symbol }),
                    };
                }
            }
            else {
                deepResolved = { _kind: `css`, meta, symbol };
            }
            // deepResolved symbol is resolved by this point
            switch (deepResolved.symbol._kind) {
                case `class`:
                    resolvedSymbols.class[name] = this.resolveExtends(meta, deepResolved.symbol, false, validateClassResolveExtends(meta, name, diagnostics, deepResolved));
                    break;
                case `element`:
                    resolvedSymbols.element[name] = this.resolveExtends(meta, name, true);
                    break;
                case `var`:
                    resolvedSymbols.var[name] = deepResolved;
                    break;
                case `cssVar`:
                    resolvedSymbols.cssVar[name] = deepResolved;
                    break;
            }
            resolvedSymbols.mainNamespace[name] = deepResolved.symbol._kind;
        }
        // resolve keyframes
        for (const [name, symbol] of Object.entries(features_1.CSSKeyframes.getAll(meta))) {
            const result = resolveByNamespace(meta, symbol, this, 'keyframes');
            if (result) {
                resolvedSymbols.keyframes[name] = {
                    _kind: `css`,
                    meta: result.meta,
                    symbol: result.symbol,
                };
            }
        }
        // resolve layers
        for (const [name, symbol] of Object.entries(features_1.CSSLayer.getAll(meta))) {
            const result = resolveByNamespace(meta, symbol, this, 'layer');
            if (result) {
                resolvedSymbols.layer[name] = {
                    _kind: `css`,
                    meta: result.meta,
                    symbol: result.symbol,
                };
            }
        }
        // resolve containers
        for (const [name, symbol] of Object.entries(features_1.CSSContains.getAll(meta))) {
            const result = resolveByNamespace(meta, symbol, this, 'container');
            if (result) {
                resolvedSymbols.container[name] = {
                    _kind: `css`,
                    meta: result.meta,
                    symbol: result.symbol,
                };
            }
        }
        return resolvedSymbols;
    }
    resolveExtends(meta, nameOrSymbol, isElement = false, reportError) {
        const name = typeof nameOrSymbol === `string` ? nameOrSymbol : nameOrSymbol.name;
        const symbol = typeof nameOrSymbol === `string`
            ? isElement
                ? meta.getTypeElement(nameOrSymbol)
                : meta.getClass(nameOrSymbol)
            : nameOrSymbol;
        if (!symbol) {
            return [];
        }
        let current = {
            _kind: 'css',
            symbol,
            meta,
        };
        const extendPath = [];
        while (current?.symbol) {
            if (isInPath(extendPath, current)) {
                break;
            }
            extendPath.push(current);
            const parent = current.symbol[`-st-extends`] || current.symbol.alias;
            if (parent) {
                if (parent._kind === 'import') {
                    const res = this.resolve(parent);
                    if (res &&
                        res._kind === 'css' &&
                        res.symbol &&
                        (res.symbol._kind === 'element' || res.symbol._kind === 'class')) {
                        const { _kind, meta, symbol } = res;
                        current = {
                            _kind,
                            meta,
                            symbol,
                        };
                    }
                    else {
                        if (reportError) {
                            reportError(res, parent, extendPath, meta, name, isElement);
                        }
                        break;
                    }
                }
                else {
                    current = { _kind: 'css', symbol: parent, meta: current.meta };
                }
            }
            else {
                break;
            }
        }
        return extendPath;
    }
}
exports.StylableResolver = StylableResolver;
function cacheKey(context, request) {
    return `${context}${safePathDelimiter}${request}`;
}
function validateClassResolveExtends(meta, name, diagnostics, deepResolved) {
    return (res, extend) => {
        const decl = (0, rule_1.findRule)(meta.sourceAst, '.' + name);
        if (decl) {
            // ToDo: move to STExtends
            if (res && res._kind === 'js') {
                diagnostics.report(features_1.CSSClass.diagnostics.CANNOT_EXTEND_JS(), {
                    node: decl,
                    word: decl.value,
                });
            }
            else if (res && !res.symbol) {
                diagnostics.report(features_1.CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(extend.name), {
                    node: decl,
                    word: decl.value,
                });
            }
            else {
                diagnostics.report(features_1.CSSClass.diagnostics.IMPORT_ISNT_EXTENDABLE(), {
                    node: decl,
                    word: decl.value,
                });
            }
        }
        else {
            if (deepResolved.symbol.alias) {
                meta.sourceAst.walkRules(new RegExp('\\.' + name), (rule) => {
                    diagnostics.report(features_1.CSSClass.diagnostics.UNKNOWN_IMPORT_ALIAS(name), {
                        node: rule,
                        word: name,
                    });
                    return false;
                });
            }
        }
    };
}
function createSymbolResolverWithCache(resolver, diagnostics) {
    const cache = new WeakMap();
    return (meta) => {
        let symbols = cache.get(meta);
        if (!symbols) {
            symbols = resolver.resolveSymbols(meta, diagnostics);
            cache.set(meta, symbols);
        }
        return symbols;
    };
}
exports.createSymbolResolverWithCache = createSymbolResolverWithCache;
function resolveByNamespace(meta, symbol, resolver, type) {
    const current = { meta, symbol };
    while ('import' in current.symbol && current.symbol.import) {
        const res = resolver.resolveImported(current.symbol.import, current.symbol.name, type);
        if (res?._kind === 'css' && res.symbol?._kind === type) {
            ({ meta: current.meta, symbol: current.symbol } = res);
        }
        else {
            return undefined;
        }
    }
    if (current.symbol?._kind === type) {
        return current;
    }
    return undefined;
}
//# sourceMappingURL=stylable-resolver.js.map