"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.createImportSymbol = exports.getImportStatements = exports.StylablePublicApi = exports.hooks = exports.diagnostics = exports.ImportTypeHook = exports.PseudoImportDecl = exports.PseudoImport = void 0;
const feature_1 = require("./feature");
const diagnostics_1 = require("./diagnostics");
const STSymbol = __importStar(require("./st-symbol"));
const plugable_record_1 = require("../helpers/plugable-record");
const import_1 = require("../helpers/import");
const css_custom_property_1 = require("../helpers/css-custom-property");
const path_1 = __importDefault(require("path"));
const diagnostics_2 = require("../diagnostics");
exports.PseudoImport = `:import`;
exports.PseudoImportDecl = {
    DEFAULT: `-st-default`,
    NAMED: `-st-named`,
    FROM: `-st-from`,
};
/**
 * ImportTypeHook is used as a way to cast imported symbols before resolving their actual type.
 * currently used only for `keyframes` as they are completely on a separate namespace from other symbols.
 *
 * Hooks are registered statically since the features are static and cannot be selected/disabled.
 * If the system will ever change to support picking features dynamically, this mechanism would
 * have to move into the `metaInit` hook.
 */
exports.ImportTypeHook = new Map();
const dataKey = plugable_record_1.plugableRecord.key('imports');
exports.diagnostics = {
    ...import_1.parseImportMessages,
    FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: diagnostics_1.generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR,
    NO_ST_IMPORT_IN_NESTED_SCOPE: (0, diagnostics_2.createDiagnosticReporter)('05011', 'error', () => `cannot use "@st-import" inside of nested scope`),
    NO_PSEUDO_IMPORT_IN_NESTED_SCOPE: (0, diagnostics_2.createDiagnosticReporter)('05012', 'error', () => `cannot use ":import" inside of nested scope`),
    INVALID_CUSTOM_PROPERTY_AS_VALUE: (0, diagnostics_2.createDiagnosticReporter)('05013', 'error', (name, as) => `invalid alias for custom property "${name}" as "${as}"; custom properties must be prefixed with "--" (double-dash)`),
    UNKNOWN_IMPORTED_SYMBOL: (0, diagnostics_2.createDiagnosticReporter)('05015', 'error', (name, path) => `cannot resolve imported symbol "${name}" from stylesheet "${path}"`),
    UNKNOWN_IMPORTED_FILE: (0, diagnostics_2.createDiagnosticReporter)('05016', 'error', (path, error) => `cannot resolve imported file: "${path}"${error ? `\nFailed with:\n${error}` : ''}`),
    UNKNOWN_TYPED_IMPORT: (0, diagnostics_2.createDiagnosticReporter)('05018', 'error', (type) => `Unknown type import "${type}"`),
    NO_DEFAULT_EXPORT: (0, diagnostics_2.createDiagnosticReporter)('05020', 'error', (path) => `Native CSS files have no default export. Imported file: "${path}"`),
    UNSUPPORTED_NATIVE_IMPORT: (0, diagnostics_2.createDiagnosticReporter)('05021', 'warning', () => `Unsupported @import within imported native CSS file`),
};
// HOOKS
exports.hooks = (0, feature_1.createFeature)({
    metaInit({ meta }) {
        plugable_record_1.plugableRecord.set(meta.data, dataKey, []);
    },
    analyzeInit(context) {
        const imports = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
        const dirContext = path_1.default.dirname(context.meta.source);
        // collect shallow imports
        for (const node of context.meta.sourceAst.nodes) {
            if (!isImportStatement(node)) {
                continue;
            }
            const parsedImport = node.type === `atrule`
                ? (0, import_1.parseStImport)(node, dirContext, context.diagnostics)
                : (0, import_1.parsePseudoImport)(node, dirContext, context.diagnostics);
            imports.push(parsedImport);
            addImportSymbols(parsedImport, context, dirContext);
        }
    },
    analyzeAtRule({ context, atRule }) {
        if (atRule.name === `st-import` && atRule.parent?.type !== `root`) {
            context.diagnostics.report(exports.diagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE(), {
                node: atRule,
            });
        }
        else if (atRule.name === `import` && context.meta.type === 'css') {
            context.diagnostics.report(exports.diagnostics.UNSUPPORTED_NATIVE_IMPORT(), {
                node: atRule,
            });
        }
    },
    analyzeSelectorNode({ context, rule, node }) {
        if (node.value !== `import`) {
            return;
        }
        if (rule.selector !== `:import`) {
            context.diagnostics.report(exports.diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(exports.PseudoImport), { node: rule });
            return;
        }
        if (rule.parent?.type !== `root`) {
            context.diagnostics.report(exports.diagnostics.NO_PSEUDO_IMPORT_IN_NESTED_SCOPE(), {
                node: rule,
            });
        }
    },
    prepareAST({ node, toRemove }) {
        if (isImportStatement(node)) {
            toRemove.push(node);
        }
    },
    transformInit({ context }) {
        validateImports(context);
        calcCssDepth(context);
    },
});
// API
class StylablePublicApi {
    constructor(stylable) {
        this.stylable = stylable;
    }
    analyze(meta) {
        return getImportStatements(meta).map(({ request, defaultExport, named, keyframes }) => ({
            from: request,
            default: defaultExport,
            named,
            typed: {
                keyframes,
            },
        }));
    }
}
exports.StylablePublicApi = StylablePublicApi;
function calcCssDepth(context) {
    let cssDepth = 1;
    const deepDependencies = (0, import_1.tryCollectImportsDeep)(context.resolver, context.meta, new Set(), ({ depth, request }) => {
        if (request.endsWith('.css')) {
            cssDepth = Math.max(cssDepth, depth);
        }
    }, 2);
    context.meta.transformCssDepth = { cssDepth, deepDependencies };
}
function isImportStatement(node) {
    return ((node.type === `atrule` && node.name === `st-import`) ||
        (node.type === `rule` && node.selector === `:import`));
}
function getImportStatements({ data }) {
    const state = plugable_record_1.plugableRecord.getUnsafe(data, dataKey);
    return state;
}
exports.getImportStatements = getImportStatements;
function createImportSymbol(importDef, type, name, dirContext) {
    return {
        _kind: 'import',
        type: type === 'default' ? `default` : `named`,
        name: type === `default` ? name : importDef.named[name],
        import: importDef,
        context: dirContext,
    };
}
exports.createImportSymbol = createImportSymbol;
// internal
function addImportSymbols(importDef, context, dirContext) {
    checkForInvalidAsUsage(importDef, context);
    if (importDef.defaultExport) {
        STSymbol.addSymbol({
            context,
            localName: importDef.defaultExport,
            symbol: createImportSymbol(importDef, `default`, `default`, dirContext),
            node: importDef.rule,
        });
    }
    Object.keys(importDef.named).forEach((name) => {
        STSymbol.addSymbol({
            context,
            localName: name,
            symbol: createImportSymbol(importDef, `named`, name, dirContext),
            node: importDef.rule,
        });
    });
    // import as typed symbol
    for (const [type, imports] of Object.entries(importDef.typed)) {
        const handler = exports.ImportTypeHook.get(type);
        if (handler) {
            for (const [localName, importName] of Object.entries(imports)) {
                handler(context, localName, importName, importDef);
            }
        }
        else {
            context.diagnostics.report(exports.diagnostics.UNKNOWN_TYPED_IMPORT(type), {
                node: importDef.rule,
                word: type,
            });
        }
    }
}
function checkForInvalidAsUsage(importDef, context) {
    for (const [local, imported] of Object.entries(importDef.named)) {
        if ((0, css_custom_property_1.validateCustomPropertyName)(imported) && !(0, css_custom_property_1.validateCustomPropertyName)(local)) {
            context.diagnostics.report(exports.diagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE(imported, local), { node: importDef.rule });
        }
    }
}
function validateImports(context) {
    const imports = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
    for (const importObj of imports) {
        const entity = context.resolver.getModule(importObj);
        if (!entity.value) {
            // warn about unknown imported files
            const fromDecl = importObj.rule.nodes &&
                importObj.rule.nodes.find((decl) => decl.type === 'decl' && decl.prop === exports.PseudoImportDecl.FROM);
            context.diagnostics.report(exports.diagnostics.UNKNOWN_IMPORTED_FILE(importObj.request, getErrorText(entity)), {
                node: fromDecl || importObj.rule,
                word: importObj.request,
            });
        }
        else if (entity.kind === 'css') {
            const meta = entity.value;
            // propagate some native CSS diagnostics to st-import
            if (meta.type === 'css') {
                let foundUnsupportedNativeImport = false;
                for (const report of meta.diagnostics.reports) {
                    if (report.code === '05021') {
                        foundUnsupportedNativeImport = true;
                        break;
                    }
                }
                if (foundUnsupportedNativeImport) {
                    context.diagnostics.report(exports.diagnostics.UNSUPPORTED_NATIVE_IMPORT(), {
                        node: importObj.rule,
                        word: importObj.defaultExport,
                    });
                }
            }
            // report unsupported native CSS default import
            if (meta.type !== 'stylable' && importObj.defaultExport) {
                context.diagnostics.report(exports.diagnostics.NO_DEFAULT_EXPORT(importObj.request), {
                    node: importObj.rule,
                    word: importObj.defaultExport,
                });
            }
            // warn about unknown named imported symbols
            for (const name in importObj.named) {
                const origName = importObj.named[name];
                const resolvedSymbol = context.resolver.resolveImported(importObj, origName);
                if (resolvedSymbol === null || !resolvedSymbol.symbol) {
                    const namedDecl = importObj.rule.nodes &&
                        importObj.rule.nodes.find((decl) => decl.type === 'decl' && decl.prop === exports.PseudoImportDecl.NAMED);
                    context.diagnostics.report(exports.diagnostics.UNKNOWN_IMPORTED_SYMBOL(origName, importObj.request), { node: namedDecl || importObj.rule, word: origName });
                }
            }
        }
        else if (entity.kind === 'js') {
            // TODO: add diagnostics for JS imports (typeof checks)
        }
    }
}
function getErrorText(res) {
    if ('error' in res) {
        const { error } = res;
        if (typeof error === 'object' && error) {
            return 'details' in error
                ? String(error.details)
                : 'message' in error
                    ? String(error.message)
                    : String(error);
        }
        return String(error);
    }
    return '';
}
//# sourceMappingURL=st-import.js.map