"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var node_1 = tslib_1.__importDefault(require("./node"));
var selector_1 = tslib_1.__importDefault(require("./selector"));
var mixin_definition_1 = tslib_1.__importDefault(require("./mixin-definition"));
var default_1 = tslib_1.__importDefault(require("../functions/default"));
var MixinCall = function (elements, args, index, currentFileInfo, important) {
    this.selector = new selector_1.default(elements);
    this.arguments = args || [];
    this._index = index;
    this._fileInfo = currentFileInfo;
    this.important = important;
    this.allowRoot = true;
    this.setParent(this.selector, this);
};
MixinCall.prototype = Object.assign(new node_1.default(), {
    type: 'MixinCall',
    accept: function (visitor) {
        if (this.selector) {
            this.selector = visitor.visit(this.selector);
        }
        if (this.arguments.length) {
            this.arguments = visitor.visitArray(this.arguments);
        }
    },
    eval: function (context) {
        var mixins;
        var mixin;
        var mixinPath;
        var args = [];
        var arg;
        var argValue;
        var rules = [];
        var match = false;
        var i;
        var m;
        var f;
        var isRecursive;
        var isOneFound;
        var candidates = [];
        var candidate;
        var conditionResult = [];
        var defaultResult;
        var defFalseEitherCase = -1;
        var defNone = 0;
        var defTrue = 1;
        var defFalse = 2;
        var count;
        var originalRuleset;
        var noArgumentsFilter;
        this.selector = this.selector.eval(context);
        function calcDefGroup(mixin, mixinPath) {
            var f, p, namespace;
            for (f = 0; f < 2; f++) {
                conditionResult[f] = true;
                default_1.default.value(f);
                for (p = 0; p < mixinPath.length && conditionResult[f]; p++) {
                    namespace = mixinPath[p];
                    if (namespace.matchCondition) {
                        conditionResult[f] = conditionResult[f] && namespace.matchCondition(null, context);
                    }
                }
                if (mixin.matchCondition) {
                    conditionResult[f] = conditionResult[f] && mixin.matchCondition(args, context);
                }
            }
            if (conditionResult[0] || conditionResult[1]) {
                if (conditionResult[0] != conditionResult[1]) {
                    return conditionResult[1] ?
                        defTrue : defFalse;
                }
                return defNone;
            }
            return defFalseEitherCase;
        }
        for (i = 0; i < this.arguments.length; i++) {
            arg = this.arguments[i];
            argValue = arg.value.eval(context);
            if (arg.expand && Array.isArray(argValue.value)) {
                argValue = argValue.value;
                for (m = 0; m < argValue.length; m++) {
                    args.push({ value: argValue[m] });
                }
            }
            else {
                args.push({ name: arg.name, value: argValue });
            }
        }
        noArgumentsFilter = function (rule) { return rule.matchArgs(null, context); };
        for (i = 0; i < context.frames.length; i++) {
            if ((mixins = context.frames[i].find(this.selector, null, noArgumentsFilter)).length > 0) {
                isOneFound = true;
                // To make `default()` function independent of definition order we have two "subpasses" here.
                // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
                // and build candidate list with corresponding flags. Then, when we know all possible matches,
                // we make a final decision.
                for (m = 0; m < mixins.length; m++) {
                    mixin = mixins[m].rule;
                    mixinPath = mixins[m].path;
                    isRecursive = false;
                    for (f = 0; f < context.frames.length; f++) {
                        if ((!(mixin instanceof mixin_definition_1.default)) && mixin === (context.frames[f].originalRuleset || context.frames[f])) {
                            isRecursive = true;
                            break;
                        }
                    }
                    if (isRecursive) {
                        continue;
                    }
                    if (mixin.matchArgs(args, context)) {
                        candidate = { mixin: mixin, group: calcDefGroup(mixin, mixinPath) };
                        if (candidate.group !== defFalseEitherCase) {
                            candidates.push(candidate);
                        }
                        match = true;
                    }
                }
                default_1.default.reset();
                count = [0, 0, 0];
                for (m = 0; m < candidates.length; m++) {
                    count[candidates[m].group]++;
                }
                if (count[defNone] > 0) {
                    defaultResult = defFalse;
                }
                else {
                    defaultResult = defTrue;
                    if ((count[defTrue] + count[defFalse]) > 1) {
                        throw { type: 'Runtime',
                            message: "Ambiguous use of `default()` found when matching for `" + this.format(args) + "`",
                            index: this.getIndex(), filename: this.fileInfo().filename };
                    }
                }
                for (m = 0; m < candidates.length; m++) {
                    candidate = candidates[m].group;
                    if ((candidate === defNone) || (candidate === defaultResult)) {
                        try {
                            mixin = candidates[m].mixin;
                            if (!(mixin instanceof mixin_definition_1.default)) {
                                originalRuleset = mixin.originalRuleset || mixin;
                                mixin = new mixin_definition_1.default('', [], mixin.rules, null, false, null, originalRuleset.visibilityInfo());
                                mixin.originalRuleset = originalRuleset;
                            }
                            var newRules = mixin.evalCall(context, args, this.important).rules;
                            this._setVisibilityToReplacement(newRules);
                            Array.prototype.push.apply(rules, newRules);
                        }
                        catch (e) {
                            throw { message: e.message, index: this.getIndex(), filename: this.fileInfo().filename, stack: e.stack };
                        }
                    }
                }
                if (match) {
                    return rules;
                }
            }
        }
        if (isOneFound) {
            throw { type: 'Runtime',
                message: "No matching definition was found for `" + this.format(args) + "`",
                index: this.getIndex(), filename: this.fileInfo().filename };
        }
        else {
            throw { type: 'Name',
                message: this.selector.toCSS().trim() + " is undefined",
                index: this.getIndex(), filename: this.fileInfo().filename };
        }
    },
    _setVisibilityToReplacement: function (replacement) {
        var i, rule;
        if (this.blocksVisibility()) {
            for (i = 0; i < replacement.length; i++) {
                rule = replacement[i];
                rule.addVisibilityBlock();
            }
        }
    },
    format: function (args) {
        return this.selector.toCSS().trim() + "(" + (args ? args.map(function (a) {
            var argValue = '';
            if (a.name) {
                argValue += a.name + ":";
            }
            if (a.value.toCSS) {
                argValue += a.value.toCSS();
            }
            else {
                argValue += '???';
            }
            return argValue;
        }).join(', ') : '') + ")";
    }
});
exports.default = MixinCall;
//# sourceMappingURL=mixin-call.js.map