"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryCollectImportsDeep = exports.parsePseudoImportNamed = exports.parsePseudoImport = exports.parseStImport = exports.parseModuleImportStatement = exports.ensureModuleImport = exports.createAtImportProps = exports.ensureImportsMessages = exports.parseImportMessages = void 0;
const path_1 = __importDefault(require("path"));
const imports_parser_1 = require("@tokey/imports-parser");
const diagnostics_1 = require("../diagnostics");
const postcss_1 = require("postcss");
const string_1 = require("../helpers/string");
const selector_1 = require("./selector");
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
exports.parseImportMessages = {
    ST_IMPORT_STAR: (0, diagnostics_1.createDiagnosticReporter)('05001', 'error', () => '@st-import * is not supported'),
    INVALID_ST_IMPORT_FORMAT: (0, diagnostics_1.createDiagnosticReporter)('05002', 'error', (errors) => `Invalid @st-import format:\n - ${errors.join('\n - ')}`),
    ST_IMPORT_EMPTY_FROM: (0, diagnostics_1.createDiagnosticReporter)('05003', 'error', () => '@st-import must specify a valid "from" string value'),
    EMPTY_IMPORT_FROM: (0, diagnostics_1.createDiagnosticReporter)('05004', 'error', () => '"-st-from" cannot be empty'),
    MULTIPLE_FROM_IN_IMPORT: (0, diagnostics_1.createDiagnosticReporter)('05005', 'warning', () => `cannot define multiple "-st-from" declarations in a single import`),
    DEFAULT_IMPORT_IS_LOWER_CASE: (0, diagnostics_1.createDiagnosticReporter)('05006', 'warning', () => 'Default import of a Stylable stylesheet must start with an upper-case letter'),
    ILLEGAL_PROP_IN_IMPORT: (0, diagnostics_1.createDiagnosticReporter)('05007', 'warning', (propName) => `"${propName}" css attribute cannot be used inside :import block`),
    FROM_PROP_MISSING_IN_IMPORT: (0, diagnostics_1.createDiagnosticReporter)('05008', 'error', () => `"-st-from" is missing in :import block`),
    INVALID_NAMED_IMPORT_AS: (0, diagnostics_1.createDiagnosticReporter)('05009', 'error', (name) => `Invalid named import "as" with name "${name}"`),
    INVALID_NESTED_KEYFRAMES: (0, diagnostics_1.createDiagnosticReporter)('05010', 'error', (name) => `Invalid nested keyframes import "${name}"`),
    INVALID_NESTED_TYPED_IMPORT: (0, diagnostics_1.createDiagnosticReporter)('05019', 'warning', (type, name) => `Invalid nested ${type} import "${name}"`),
};
exports.ensureImportsMessages = {
    ATTEMPT_OVERRIDE_SYMBOL: (0, diagnostics_1.createDiagnosticReporter)('16001', 'error', (kind, origin, override) => `Attempt to override existing ${kind} import symbol. ${origin} -> ${override}`),
    PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE: (0, diagnostics_1.createDiagnosticReporter)('16002', 'error', () => `Attempt to insert new a import in newImport "none" mode`),
};
function createAtImportProps(importObj) {
    const named = Object.entries(importObj.named || {});
    const keyframes = Object.entries(importObj.keyframes || {});
    let params = '';
    if (importObj.defaultExport) {
        params += importObj.defaultExport;
    }
    if (importObj.defaultExport && (named.length || keyframes.length)) {
        params += ', ';
    }
    if (named.length || keyframes.length) {
        params += '[';
        const namedParts = getNamedImportParts(named);
        const keyFramesParts = getNamedImportParts(keyframes);
        params += namedParts.join(', ');
        if (keyFramesParts.length) {
            if (namedParts.length) {
                params += ', ';
            }
            params += `keyframes(${keyFramesParts.join(', ')})`;
        }
        params += ']';
    }
    params += ` from ${JSON.stringify(importObj.request || '')}`;
    return { name: 'st-import', params };
}
exports.createAtImportProps = createAtImportProps;
function ensureModuleImport(ast, importPatches, options, diagnostics = new diagnostics_1.Diagnostics()) {
    const patches = createImportPatches(ast, importPatches, options, diagnostics);
    if (!diagnostics.reports.length) {
        for (const patch of patches) {
            patch();
        }
    }
    return { diagnostics };
}
exports.ensureModuleImport = ensureModuleImport;
function createImportPatches(ast, importPatches, { newImport }, diagnostics) {
    const patches = [];
    const handled = new Set();
    for (const node of ast.nodes) {
        if (node.type === 'atrule' && node.name === 'st-import') {
            const imported = parseStImport(node, '*', diagnostics);
            processImports(imported, importPatches, handled, diagnostics);
            patches.push(() => node.assign(createAtImportProps(imported)));
        }
        else if (node.type === 'rule' && node.selector === ':import') {
            const imported = parsePseudoImport(node, '*', diagnostics);
            processImports(imported, importPatches, handled, diagnostics);
            patches.push(() => {
                const named = generateNamedValue(imported);
                const { defaultDecls, namedDecls } = patchDecls(node, named, imported);
                if (imported.defaultExport) {
                    ensureSingleDecl(defaultDecls, node, '-st-default', imported.defaultExport);
                }
                if (named.length) {
                    ensureSingleDecl(namedDecls, node, '-st-named', named.join(', '));
                }
            });
        }
    }
    if (newImport === 'none') {
        if (handled.size !== importPatches.length) {
            diagnostics.report(exports.ensureImportsMessages.PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE(), { node: ast });
        }
        return patches;
    }
    if (handled.size === importPatches.length) {
        return patches;
    }
    for (const item of importPatches) {
        if (handled.has(item)) {
            continue;
        }
        if (!hasDefinitions(item)) {
            continue;
        }
        if (newImport === 'st-import') {
            patches.push(() => {
                ast.prepend((0, postcss_1.atRule)(createAtImportProps({
                    defaultExport: item.defaultExport || '',
                    keyframes: item.keyframes || {},
                    named: item.named || {},
                    request: item.request,
                })));
            });
        }
        else {
            patches.push(() => {
                ast.prepend((0, postcss_1.rule)(createPseudoImportProps(item)));
            });
        }
    }
    return patches;
}
function setImportObjectFrom(importPath, dirPath, importObj) {
    if (!path_1.default.isAbsolute(importPath) && !importPath.startsWith('.')) {
        importObj.request = importPath;
        importObj.from = importPath;
    }
    else {
        importObj.request = importPath;
        importObj.from =
            path_1.default.posix && path_1.default.posix.isAbsolute(dirPath) // browser has no posix methods
                ? path_1.default.posix.resolve(dirPath, importPath)
                : path_1.default.resolve(dirPath, importPath);
    }
}
function parseModuleImportStatement(node, context, diagnostics) {
    if (node.type === 'atrule') {
        return parseStImport(node, context, diagnostics);
    }
    else {
        return parsePseudoImport(node, context, diagnostics);
    }
}
exports.parseModuleImportStatement = parseModuleImportStatement;
function parseStImport(atRule, context, diagnostics) {
    var _a;
    const keyframes = {};
    const importObj = {
        defaultExport: '',
        from: '',
        request: '',
        named: {},
        rule: atRule,
        context,
        keyframes,
        typed: {
            keyframes,
        },
    };
    const imports = (0, imports_parser_1.parseImports)(`import ${atRule.params}`, '[', ']', true)[0];
    if (imports && imports.star) {
        diagnostics.report(exports.parseImportMessages.ST_IMPORT_STAR(), { node: atRule });
    }
    else {
        setImportObjectFrom(imports.from || '', context, importObj);
        importObj.defaultExport = imports.defaultName || '';
        if (importObj.defaultExport &&
            !(0, selector_1.isCompRoot)(importObj.defaultExport) &&
            importObj.from.endsWith(`.css`)) {
            diagnostics.report(exports.parseImportMessages.DEFAULT_IMPORT_IS_LOWER_CASE(), {
                node: atRule,
                word: importObj.defaultExport,
            });
        }
        if (imports.tagged) {
            for (const [kind, namedTyped] of Object.entries(imports.tagged)) {
                if (!namedTyped) {
                    continue;
                }
                for (const [impName, impAsName] of namedTyped) {
                    (_a = importObj.typed)[kind] ?? (_a[kind] = {});
                    importObj.typed[kind][impAsName] = impName;
                }
            }
        }
        if (imports.named) {
            for (const [impName, impAsName] of imports.named) {
                importObj.named[impAsName] = impName;
            }
        }
        if (imports.errors.length) {
            diagnostics.report(exports.parseImportMessages.INVALID_ST_IMPORT_FORMAT(imports.errors), {
                node: atRule,
            });
        }
        else if (!imports.from?.trim()) {
            diagnostics.report(exports.parseImportMessages.ST_IMPORT_EMPTY_FROM(), { node: atRule });
        }
    }
    return importObj;
}
exports.parseStImport = parseStImport;
function parsePseudoImport(rule, context, diagnostics) {
    let fromExists = false;
    const keyframes = {};
    const importObj = {
        defaultExport: '',
        from: '',
        request: '',
        named: {},
        keyframes,
        typed: {
            keyframes,
        },
        rule,
        context,
    };
    rule.walkDecls((decl) => {
        switch (decl.prop) {
            case `-st-from`: {
                const importPath = (0, string_1.stripQuotation)(decl.value);
                if (!importPath.trim()) {
                    diagnostics.report(exports.parseImportMessages.EMPTY_IMPORT_FROM(), { node: decl });
                }
                if (fromExists) {
                    diagnostics.report(exports.parseImportMessages.MULTIPLE_FROM_IN_IMPORT(), {
                        node: rule,
                    });
                }
                setImportObjectFrom(importPath, context, importObj);
                fromExists = true;
                break;
            }
            case `-st-default`:
                importObj.defaultExport = decl.value;
                if (!(0, selector_1.isCompRoot)(importObj.defaultExport) && importObj.from.endsWith(`.css`)) {
                    diagnostics.report(exports.parseImportMessages.DEFAULT_IMPORT_IS_LOWER_CASE(), {
                        node: decl,
                        word: importObj.defaultExport,
                    });
                }
                break;
            case `-st-named`:
                {
                    const { typedMap, namedMap } = parsePseudoImportNamed(decl.value, decl, diagnostics);
                    importObj.named = namedMap;
                    importObj.keyframes = typedMap.keyframes || {};
                    importObj.typed = typedMap;
                }
                break;
            default:
                diagnostics.report(exports.parseImportMessages.ILLEGAL_PROP_IN_IMPORT(decl.prop), {
                    node: decl,
                    word: decl.prop,
                });
                break;
        }
    });
    if (!importObj.from) {
        diagnostics.report(exports.parseImportMessages.FROM_PROP_MISSING_IN_IMPORT(), {
            node: rule,
        });
    }
    return importObj;
}
exports.parsePseudoImport = parsePseudoImport;
function parsePseudoImportNamed(value, node, diagnostics) {
    const namedMap = {};
    const typedMap = {};
    if (value) {
        handleNamedTokens((0, postcss_value_parser_1.default)(value), namedMap, typedMap, node, diagnostics);
    }
    return { namedMap, typedMap };
}
exports.parsePseudoImportNamed = parsePseudoImportNamed;
function createPseudoImportProps(item) {
    const nodes = [];
    const named = generateNamedValue(item);
    if (item.request) {
        nodes.push((0, postcss_1.decl)({ prop: '-st-from', value: JSON.stringify(item.request) }));
    }
    if (item.defaultExport) {
        nodes.push((0, postcss_1.decl)({
            prop: '-st-default',
            value: item.defaultExport,
        }));
    }
    if (named.length) {
        nodes.push((0, postcss_1.decl)({
            prop: '-st-named',
            value: named.join(', '),
        }));
    }
    return {
        selector: ':import',
        nodes,
    };
}
function patchDecls(node, named, pseudoImport) {
    const namedDecls = [];
    const defaultDecls = [];
    for (const decl of node.nodes) {
        if (decl.type !== 'decl') {
            continue;
        }
        if (decl.prop === '-st-named') {
            decl.assign({ value: named.join(', ') });
            namedDecls.push(decl);
        }
        else if (decl.prop === '-st-default') {
            decl.assign({ value: pseudoImport.defaultExport });
            defaultDecls.push(decl);
        }
    }
    return { defaultDecls, namedDecls };
}
function ensureSingleDecl(decls, node, prop, value) {
    if (!decls.length) {
        node.append((0, postcss_1.decl)({ prop, value }));
    }
    else if (decls.length > 1) {
        // remove duplicates keep last one
        for (let i = 0; i < decls.length - 1; i++) {
            decls[i].remove();
        }
    }
}
function getNamedImportParts(named) {
    const parts = [];
    for (const [as, name] of named) {
        if (as === name) {
            parts.push(name);
        }
        else {
            parts.push(`${name} as ${as}`);
        }
    }
    return parts;
}
function generateNamedValue({ named = {}, keyframes = {}, }) {
    const namedParts = getNamedImportParts(Object.entries(named));
    const keyframesParts = getNamedImportParts(Object.entries(keyframes));
    if (keyframesParts.length) {
        namedParts.push(`keyframes(${keyframesParts.join(', ')})`);
    }
    return namedParts;
}
function hasDefinitions({ named = {}, keyframes = {}, defaultExport, }) {
    return defaultExport || Object.keys(named).length || Object.keys(keyframes).length;
}
function processImports(imported, importPatches, handled, diagnostics) {
    const ops = ['named', 'keyframes'];
    for (const patch of importPatches) {
        if (handled.has(patch)) {
            continue;
        }
        if (imported.request === patch.request) {
            for (const op of ops) {
                const patchBucket = patch[op];
                if (!patchBucket) {
                    continue;
                }
                for (const [asName, symbol] of Object.entries(patchBucket)) {
                    const currentSymbol = imported[op][asName];
                    if (currentSymbol === symbol) {
                        continue;
                    }
                    else if (currentSymbol) {
                        diagnostics.report(exports.ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL(op, currentSymbol === asName
                            ? currentSymbol
                            : `${currentSymbol} as ${asName}`, symbol === asName ? symbol : `${symbol} as ${asName}`), {
                            node: imported.rule,
                        });
                    }
                    else {
                        imported[op][asName] = symbol;
                    }
                }
            }
            if (patch.defaultExport) {
                if (!imported.defaultExport) {
                    imported.defaultExport = patch.defaultExport;
                }
                else if (imported.defaultExport !== patch.defaultExport) {
                    diagnostics.report(exports.ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL('default', imported.defaultExport, patch.defaultExport), {
                        node: imported.rule,
                    });
                }
            }
            handled.add(patch);
        }
    }
}
function handleNamedTokens(tokens, mainBucket, typedBuckets, node, diagnostics) {
    var _a;
    const { nodes } = tokens;
    for (let i = 0; i < nodes.length; i++) {
        const token = nodes[i];
        if (token.type === 'word') {
            const space = nodes[i + 1];
            const as = nodes[i + 2];
            const spaceAfter = nodes[i + 3];
            const asName = nodes[i + 4];
            if (isImportAs(space, as)) {
                if (spaceAfter?.type === 'space' && asName?.type === 'word') {
                    mainBucket[asName.value] = token.value;
                    i += 4; //ignore next 4 tokens
                }
                else {
                    i += !asName ? 3 : 2;
                    diagnostics.report(exports.parseImportMessages.INVALID_NAMED_IMPORT_AS(token.value), {
                        node,
                    });
                    continue;
                }
            }
            else {
                mainBucket[token.value] = token.value;
            }
        }
        else if (token.type === 'function') {
            if (!typedBuckets) {
                diagnostics.report(exports.parseImportMessages.INVALID_NESTED_TYPED_IMPORT(token.value, postcss_value_parser_1.default.stringify(token)), { node });
            }
            else {
                typedBuckets[_a = token.value] ?? (typedBuckets[_a] = {});
                handleNamedTokens(token, typedBuckets[token.value], null, node, diagnostics);
            }
        }
    }
}
function isImportAs(space, as) {
    return space?.type === 'space' && as?.type === 'word' && as?.value === 'as';
}
function tryCollectImportsDeep(resolver, meta, imports = new Set(), onImport = undefined, depth = 1, origin = meta.source) {
    for (const { context, request } of meta.getImportStatements()) {
        try {
            const resolved = resolver.resolvePath(context, request);
            if (resolved === origin) {
                continue;
            }
            onImport?.({ context, request, resolved, depth });
            if (!imports.has(resolved)) {
                imports.add(resolved);
                if (resolved.endsWith('.st.css')) {
                    tryCollectImportsDeep(resolver, resolver.analyze(resolved), imports, onImport, depth + 1, origin);
                }
            }
        }
        catch {
            /** */
        }
    }
    return imports;
}
exports.tryCollectImportsDeep = tryCollectImportsDeep;
//# sourceMappingURL=import.js.map