"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addServiceNodeBindingIfContextFree = addServiceNodeBindingIfContextFree;
const BindingConstraintsImplementation_1 = require("../../binding/models/BindingConstraintsImplementation");
const isStackOverflowError_1 = require("../../error/calculations/isStackOverflowError");
const InversifyCoreError_1 = require("../../error/models/InversifyCoreError");
const InversifyCoreErrorKind_1 = require("../../error/models/InversifyCoreErrorKind");
const LazyPlanServiceNode_1 = require("../models/LazyPlanServiceNode");
const curryBuildServiceNodeBindings_1 = require("./curryBuildServiceNodeBindings");
const curryLazyBuildPlanServiceNodeFromClassElementMetadata_1 = require("./curryLazyBuildPlanServiceNodeFromClassElementMetadata");
const curryLazyBuildPlanServiceNodeFromResolvedValueElementMetadata_1 = require("./curryLazyBuildPlanServiceNodeFromResolvedValueElementMetadata");
const currySubplan_1 = require("./currySubplan");
const plan_1 = require("./plan");
const subplan = (0, currySubplan_1.currySubplan)(plan_1.buildPlanServiceNodeFromClassElementMetadata, plan_1.buildPlanServiceNodeFromResolvedValueElementMetadata, circularLazyBuildPlanServiceNodeFromClassElementMetadata, circularLazyBuildPlanServiceNodeFromResolvedValueElementMetadata);
const buildServiceNodeBindings = (0, curryBuildServiceNodeBindings_1.curryBuildServiceNodeBindings)(subplan);
const lazyBuildPlanServiceNodeFromClassElementMetadata = (0, curryLazyBuildPlanServiceNodeFromClassElementMetadata_1.curryLazyBuildPlanServiceNodeFromClassElementMetadata)(buildServiceNodeBindings);
const lazyBuildPlanServiceNodeFromResolvedValueElementMetadata = (0, curryLazyBuildPlanServiceNodeFromResolvedValueElementMetadata_1.curryLazyBuildPlanServiceNodeFromResolvedValueElementMetadata)(buildServiceNodeBindings);
function circularLazyBuildPlanServiceNodeFromClassElementMetadata(params, bindingConstraintsList, elementMetadata) {
    return lazyBuildPlanServiceNodeFromClassElementMetadata(params, bindingConstraintsList, elementMetadata);
}
function circularLazyBuildPlanServiceNodeFromResolvedValueElementMetadata(params, bindingConstraintsList, elementMetadata) {
    return lazyBuildPlanServiceNodeFromResolvedValueElementMetadata(params, bindingConstraintsList, elementMetadata);
}
/**
 * Attach a binding to a service node if the binding is context-free.
 * @param params The plan parameters.
 * @param serviceNode The service node to attach the binding to.
 * @param binding The binding to attach.
 * @param bindingConstraintsList The list of binding constraints.
 * @param chainedBindings Whether the bindings are chained.
 * @returns True if the binding requires ancestor metadata, false otherwise.
 */
function addServiceNodeBindingIfContextFree(params, serviceNode, binding, bindingConstraintsList, chainedBindings) {
    if (LazyPlanServiceNode_1.LazyPlanServiceNode.is(serviceNode) && !serviceNode.isExpanded()) {
        return {
            isContextFreeBinding: true,
            shouldInvalidateServiceNode: false,
        };
    }
    const bindingConstraints = new BindingConstraintsImplementation_1.BindingConstraintsImplementation(bindingConstraintsList.last);
    if (!binding.isSatisfiedBy(bindingConstraints) ||
        bindingConstraintsList.last.elem.getAncestorsCalled) {
        return {
            isContextFreeBinding: !bindingConstraintsList.last.elem.getAncestorsCalled,
            shouldInvalidateServiceNode: false,
        };
    }
    return addServiceNodeSatisfiedBindingIfContextFree(params, serviceNode, binding, bindingConstraintsList, chainedBindings);
}
function addServiceNodeSatisfiedBindingIfContextFree(params, serviceNode, binding, bindingConstraintsList, chainedBindings) {
    let serviceNodeBinding;
    try {
        [serviceNodeBinding] = buildServiceNodeBindings(params, bindingConstraintsList, [binding], serviceNode, chainedBindings);
    }
    catch (error) {
        if ((0, isStackOverflowError_1.isStackOverflowError)(error)) {
            /**
             * We could potentially detect if we managed to traverse at least one iteration of the circular dependency loop.
             * If so, the binding is context free if and only if bindingConstraintsList.last.elem.getAncestorsCalled is false.
             *
             * Having said that, computing this does not solve an underlying issue with circular dependencies: further cache
             * refreshes are likely to encounter the same issue again and again. Recovering from stack overflow errors constantly
             * is not feasible, so we prefer to declare the binding as non context free, asking for a more aggressive cache
             * invalidation strategy, which is likely to be a cache clear.
             */
            return {
                isContextFreeBinding: false,
                shouldInvalidateServiceNode: true,
            };
        }
        throw error;
    }
    return addServiceNodeBindingNodeIfContextFree(serviceNode, serviceNodeBinding);
}
function addServiceNodeBindingNodeIfContextFree(serviceNode, serviceNodeBinding) {
    if (Array.isArray(serviceNode.bindings)) {
        serviceNode.bindings.push(serviceNodeBinding);
    }
    else {
        if (serviceNode.bindings === undefined) {
            serviceNode.bindings = serviceNodeBinding;
        }
        else {
            if (!LazyPlanServiceNode_1.LazyPlanServiceNode.is(serviceNode)) {
                throw new InversifyCoreError_1.InversifyCoreError(InversifyCoreErrorKind_1.InversifyCoreErrorKind.planning, 'Unexpected non-lazy plan service node. This is likely a bug in the planning logic. Please, report this issue');
            }
            return {
                isContextFreeBinding: true,
                shouldInvalidateServiceNode: true,
            };
        }
    }
    return {
        isContextFreeBinding: true,
        shouldInvalidateServiceNode: false,
    };
}
//# sourceMappingURL=addServiceNodeBindingIfContextFree.js.map