"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.getAll = exports.get = exports.getKeyframesStatements = exports.hooks = exports.diagnostics = exports.reservedKeyFrames = void 0;
const feature_1 = require("./feature");
const STSymbol = __importStar(require("./st-symbol"));
const STImport = __importStar(require("./st-import"));
const plugable_record_1 = require("../helpers/plugable-record");
const rule_1 = require("../helpers/rule");
const namespace_1 = require("../helpers/namespace");
const global_1 = require("../helpers/global");
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
const diagnostics_1 = require("../diagnostics");
exports.reservedKeyFrames = [
    'none',
    'inherited',
    'initial',
    'unset',
    /* single-timing-function */
    'linear',
    'ease',
    'ease-in',
    'ease-in-out',
    'ease-out',
    'step-start',
    'step-end',
    'start',
    'end',
    /* single-animation-iteration-count */
    'infinite',
    /* single-animation-direction */
    'normal',
    'reverse',
    'alternate',
    'alternate-reverse',
    /* single-animation-fill-mode */
    'forwards',
    'backwards',
    'both',
    /* single-animation-play-state */
    'running',
    'paused',
];
exports.diagnostics = {
    ILLEGAL_KEYFRAMES_NESTING: (0, diagnostics_1.createDiagnosticReporter)('02001', 'error', () => `illegal nested "@keyframes"`),
    MISSING_KEYFRAMES_NAME: (0, diagnostics_1.createDiagnosticReporter)('02002', 'error', () => '"@keyframes" missing parameter'),
    MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL: (0, diagnostics_1.createDiagnosticReporter)('02003', 'error', () => `"@keyframes" missing parameter inside "${global_1.GLOBAL_FUNC}()"`),
    KEYFRAME_NAME_RESERVED: (0, diagnostics_1.createDiagnosticReporter)('02004', 'error', (name) => `keyframes "${name}" is reserved`),
    UNKNOWN_IMPORTED_KEYFRAMES: (0, diagnostics_1.createDiagnosticReporter)('02005', 'error', (name, path) => `cannot resolve imported keyframes "${name}" from stylesheet "${path}"`),
};
const dataKey = plugable_record_1.plugableRecord.key('keyframes');
// HOOKS
STImport.ImportTypeHook.set(`keyframes`, (context, localName, importName, importDef) => {
    addKeyframes({
        context,
        name: localName,
        importName,
        ast: importDef.rule,
        importDef,
    });
});
exports.hooks = (0, feature_1.createFeature)({
    metaInit({ meta }) {
        plugable_record_1.plugableRecord.set(meta.data, dataKey, { statements: [], paths: {}, imports: [] });
    },
    analyzeAtRule({ context, atRule }) {
        let { params: name } = atRule;
        // check nesting validity
        if (!(0, rule_1.isInConditionalGroup)(atRule, true)) {
            context.diagnostics.report(exports.diagnostics.ILLEGAL_KEYFRAMES_NESTING(), { node: atRule });
            return;
        }
        // save keyframes declarations
        const { statements: keyframesAsts } = plugable_record_1.plugableRecord.getUnsafe(context.meta.data, dataKey);
        keyframesAsts.push(atRule);
        // validate name
        if (!name) {
            context.diagnostics.report(exports.diagnostics.MISSING_KEYFRAMES_NAME(), { node: atRule });
            return;
        }
        //
        const isStylable = context.meta.type === 'stylable';
        let global;
        const globalName = isStylable ? (0, global_1.globalValue)(name) : undefined;
        if (globalName !== undefined) {
            name = globalName;
            global = true;
        }
        if (name === '') {
            context.diagnostics.report(exports.diagnostics.MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL(), {
                node: atRule,
            });
            return;
        }
        if (exports.reservedKeyFrames.includes(name)) {
            context.diagnostics.report(exports.diagnostics.KEYFRAME_NAME_RESERVED(name), {
                node: atRule,
                word: name,
            });
        }
        addKeyframes({
            context,
            name,
            importName: name,
            ast: atRule,
            global: isStylable ? global : true,
        });
    },
    transformResolve({ context }) {
        const symbols = STSymbol.getAllByType(context.meta, `keyframes`);
        const resolved = {
            record: {},
            locals: new Set(),
        };
        const resolvedSymbols = context.getResolvedSymbols(context.meta);
        for (const [name, symbol] of Object.entries(symbols)) {
            const res = resolvedSymbols.keyframes[name];
            if (res) {
                resolved.record[name] = res;
                if (res.meta === context.meta) {
                    resolved.locals.add(name);
                }
            }
            else if (symbol.import) {
                context.diagnostics.report(exports.diagnostics.UNKNOWN_IMPORTED_KEYFRAMES(symbol.name, symbol.import.request), {
                    node: symbol.import.rule,
                    word: symbol.name,
                });
            }
        }
        return resolved;
    },
    transformAtRuleNode({ context, atRule, resolved }) {
        const globalName = context.meta.type === 'stylable' ? (0, global_1.globalValue)(atRule.params) : undefined;
        const name = globalName ?? atRule.params;
        if (!name) {
            return;
        }
        const resolve = resolved.record[name];
        /* js keyframes mixins won't have resolved keyframes */
        atRule.params = resolve
            ? getTransformedName(resolve)
            : globalName ?? (0, namespace_1.namespace)(name, context.meta.namespace);
    },
    transformDeclaration({ decl, resolved }) {
        const parsed = (0, postcss_value_parser_1.default)(decl.value);
        // ToDo: improve by correctly parse & identify `animation-name`
        // ToDo: handle symbols from js mixin
        parsed.nodes.forEach((node) => {
            const resolve = resolved.record[node.value];
            const scoped = resolve && getTransformedName(resolve);
            if (scoped) {
                node.value = scoped;
            }
        });
        decl.value = parsed.toString();
    },
    transformJSExports({ exports, resolved }) {
        for (const name of resolved.locals) {
            exports.keyframes[name] = getTransformedName(resolved.record[name]);
        }
    },
});
// API
function getKeyframesStatements({ data }) {
    const { statements } = plugable_record_1.plugableRecord.getUnsafe(data, dataKey);
    return statements;
}
exports.getKeyframesStatements = getKeyframesStatements;
function get(meta, name) {
    return STSymbol.get(meta, name, `keyframes`);
}
exports.get = get;
function getAll(meta) {
    return STSymbol.getAllByType(meta, `keyframes`);
}
exports.getAll = getAll;
function addKeyframes({ context, name, importName, ast, global, importDef, }) {
    /**
     * keyframes are safe to redeclare in case they are unique within their context (applied
     * in different times/cases), for example 2 keyframes statements can override each other
     * if 1 is applied on the root (always) and the other in @media (on some condition).
     *
     * > in case keyframes are imported, then no local keyframes
     * > are allowed to override them (will report a warning).
     */
    const isFirstInPath = addKeyframesDeclaration(context.meta, name, ast, !!importDef);
    // first must not be `safeRedeclare`
    const safeRedeclare = isFirstInPath && !!STSymbol.get(context.meta, name, `keyframes`);
    // fields are confusing in this symbol:
    // name: the import name if imported OR the local name
    // alias: the local name
    STSymbol.addSymbol({
        context,
        node: ast,
        localName: name,
        symbol: {
            _kind: 'keyframes',
            alias: name,
            name: importName,
            global,
            import: importDef,
        },
        safeRedeclare,
    });
}
function addKeyframesDeclaration(meta, name, origin, isImported) {
    let path = ``;
    let current = origin.parent;
    while (current) {
        if (current.type === `rule`) {
            path += ` -> ` + current.selector;
        }
        else if (current.type === `atrule`) {
            path +=
                ` -> ` +
                    current.name +
                    ` ` +
                    current.params;
        }
        current = current.parent;
    }
    const { paths, imports } = plugable_record_1.plugableRecord.getUnsafe(meta.data, dataKey);
    if (!paths[path]) {
        paths[path] = [];
    }
    const isFirstInPath = !paths[path].includes(name);
    const isImportedBefore = imports.includes(name);
    paths[path].push(name);
    if (isImported) {
        imports.push(name);
    }
    return isFirstInPath && !isImportedBefore;
}
function getTransformedName({ symbol, meta }) {
    return symbol.global ? symbol.alias : (0, namespace_1.namespace)(symbol.alias, meta.namespace);
}
//# sourceMappingURL=css-keyframes.js.map