"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.infer = exports.InferenceKind = void 0;
var paperclip_utils_1 = require("paperclip-utils");
// TODO - this should be built in rust
var InferenceKind;
(function (InferenceKind) {
    InferenceKind[InferenceKind["Shape"] = 0] = "Shape";
    InferenceKind[InferenceKind["Array"] = 1] = "Array";
    InferenceKind[InferenceKind["Any"] = 2] = "Any";
})(InferenceKind = exports.InferenceKind || (exports.InferenceKind = {}));
var createShapeInference = function (properties, fromSpread) {
    if (properties === void 0) { properties = {}; }
    if (fromSpread === void 0) { fromSpread = false; }
    return ({ kind: InferenceKind.Shape, fromSpread: fromSpread, properties: properties });
};
var createAnyInference = function () { return ({ kind: InferenceKind.Any }); };
var ANY_INFERENCE = createAnyInference();
var SPREADED_SHAPE_INFERENCE = createShapeInference({}, true);
var addShapeInferenceProperty = function (part, value, shape) {
    var _a;
    var _b, _c;
    return (__assign(__assign({}, shape), { properties: __assign(__assign({}, shape.properties), (_a = {}, _a[part.name] = {
            value: value,
            optional: ((_b = shape.properties[part.name]) === null || _b === void 0 ? void 0 : _b.optional) === false
                ? (_c = shape.properties[part.name]) === null || _c === void 0 ? void 0 : _c.optional : part.optional
        }, _a)) }));
};
var mergeShapeInference = function (existing, extended) {
    if (extended.kind === InferenceKind.Any) {
        return existing;
    }
    if (extended.kind === InferenceKind.Array) {
        console.error("Conflict: can't access properties of arra");
        // ERRROR!
        return existing;
    }
    return __assign(__assign({}, existing), { properties: __assign(__assign({}, existing.properties), extended.properties) });
};
var addInferenceProperty = function (path, value, owner, _index) {
    var _a, _b;
    if (_index === void 0) { _index = 0; }
    if (path.length === 0) {
        return owner;
    }
    if (owner.kind === InferenceKind.Any) {
        owner = createShapeInference();
    }
    var part = path[_index];
    if (owner.kind === InferenceKind.Shape) {
        if (_index < path.length - 1) {
            var childValue = ((_a = owner.properties[part.name]) === null || _a === void 0 ? void 0 : _a.value) || createShapeInference();
            childValue = addInferenceProperty(path, value, childValue, _index + 1);
            owner = addShapeInferenceProperty(part, childValue, owner);
        }
        else {
            var existingInference = ((_b = owner.properties[part.name]) === null || _b === void 0 ? void 0 : _b.value) || ANY_INFERENCE;
            if (existingInference.kind === InferenceKind.Shape) {
                value = mergeShapeInference(existingInference, value);
            }
            owner = addShapeInferenceProperty(part, value, owner);
        }
    }
    if (owner.kind === InferenceKind.Array) {
        owner = __assign(__assign({}, owner), { value: addInferenceProperty(path, value, owner.value, _index) });
    }
    return owner;
};
var unfurlScopePath = function (path, context) {
    var cpath = path;
    if (!context.scope[path[0].name]) {
        return path;
    }
    var entirePath = path;
    while (true) {
        var property = cpath[0].name;
        var newCPath = context.scope[property];
        // if exists, but empty, then the scope is created within the template
        if (newCPath) {
            if (newCPath.length === 0) {
                return [];
            }
        }
        else {
            break;
        }
        entirePath = __spreadArrays(newCPath, entirePath.slice(1));
        cpath = newCPath;
    }
    return entirePath;
};
var addContextInferenceProperty = function (path, value, context) { return (__assign(__assign({}, context), { inference: addInferenceProperty(unfurlScopePath(path, context), value, context.inference) })); };
exports.infer = function (ast) {
    return inferNode(ast, true, {
        scope: {},
        inference: createShapeInference()
    }).inference;
};
var inferNode = function (ast, isRoot, context) {
    switch (ast.kind) {
        case paperclip_utils_1.NodeKind.Element:
            return inferElement(ast, isRoot, context);
        case paperclip_utils_1.NodeKind.Slot:
            return inferSlot(ast, context);
        case paperclip_utils_1.NodeKind.Fragment:
            return inferFragment(ast, context);
    }
    return context;
};
var inferElement = function (element, isRoot, context) {
    for (var _i = 0, _a = element.attributes; _i < _a.length; _i++) {
        var atttribute = _a[_i];
        context = inferAttribute(atttribute, context);
    }
    context = inferChildren(element.children, context);
    return context;
};
var inferAttribute = function (attribute, context) {
    switch (attribute.kind) {
        case paperclip_utils_1.AttributeKind.KeyValueAttribute: {
            if (attribute.value &&
                attribute.value.attrValueKind === paperclip_utils_1.AttributeValueKind.Slot) {
                context = inferStatement(attribute.value.script, context);
            }
            if (attribute.value &&
                attribute.value.attrValueKind === paperclip_utils_1.AttributeValueKind.DyanmicString) {
                for (var _i = 0, _a = attribute.value.values; _i < _a.length; _i++) {
                    var part = _a[_i];
                    if (part.partKind === paperclip_utils_1.DynamicStringAttributeValuePartKind.Slot) {
                        context = inferStatement(part, context);
                    }
                }
            }
            break;
        }
        case paperclip_utils_1.AttributeKind.PropertyBoundAttribute: {
            context = addContextInferenceProperty([{ name: attribute.bindingName, optional: true }], ANY_INFERENCE, context);
            break;
        }
        case paperclip_utils_1.AttributeKind.ShorthandAttribute: {
            context = inferStatement(attribute.reference, context);
            break;
        }
        case paperclip_utils_1.AttributeKind.SpreadAttribute: {
            context = inferStatement(attribute.script, context, SPREADED_SHAPE_INFERENCE);
            break;
        }
    }
    return context;
};
var inferSlot = function (slot, context) {
    return inferStatement(slot.script, context);
};
var inferFragment = function (fragment, context) {
    return inferChildren(fragment.children, context);
};
var inferChildren = function (children, context) {
    return children.reduce(function (context, child) { return inferNode(child, false, context); }, context);
};
var inferStatement = function (statement, context, defaultInference) {
    if (defaultInference === void 0) { defaultInference = ANY_INFERENCE; }
    switch (statement.jsKind) {
        case paperclip_utils_1.StatementKind.Reference: {
            context = addContextInferenceProperty(statement.path, defaultInference, context);
            break;
        }
        case paperclip_utils_1.StatementKind.Node: {
            context = inferNode(statement, false, context);
            break;
        }
        case paperclip_utils_1.StatementKind.Object: {
            for (var _i = 0, _a = statement.properties; _i < _a.length; _i++) {
                var property = _a[_i];
                context = inferStatement(property.value, context, defaultInference);
            }
            break;
        }
        case paperclip_utils_1.StatementKind.Array: {
            for (var _b = 0, _c = statement.values; _b < _c.length; _b++) {
                var value = _c[_b];
                context = inferStatement(value, context, defaultInference);
            }
            break;
        }
    }
    return context;
};
