"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleResolveError = handleResolveError;
const common_1 = require("@inversifyjs/common");
const BindingType_1 = require("../../binding/models/BindingType");
const isStackOverflowError_1 = require("../../error/calculations/isStackOverflowError");
const InversifyCoreError_1 = require("../../error/models/InversifyCoreError");
const InversifyCoreErrorKind_1 = require("../../error/models/InversifyCoreErrorKind");
const isPlanServiceRedirectionBindingNode_1 = require("./isPlanServiceRedirectionBindingNode");
const INDEX_NOT_FOUND = -1;
function handleResolveError(params, error) {
    if ((0, isStackOverflowError_1.isStackOverflowError)(error)) {
        const stringifiedCircularDependencies = stringifyServiceIdentifierTrace(extractLikelyCircularDependency(params));
        throw new InversifyCoreError_1.InversifyCoreError(InversifyCoreErrorKind_1.InversifyCoreErrorKind.planning, `Circular dependency found: ${stringifiedCircularDependencies}`, { cause: error });
    }
    throw error;
}
function extractLikelyCircularDependency(params) {
    const root = params.planResult.tree.root;
    const stack = [];
    function depthFirstSearch(node) {
        const existingIndex = stack.indexOf(node);
        if (existingIndex !== INDEX_NOT_FOUND) {
            const cycleNodes = [
                ...stack.slice(existingIndex),
                node,
            ];
            return cycleNodes.map((n) => n.serviceIdentifier);
        }
        stack.push(node);
        try {
            for (const child of getChildServiceNodes(node)) {
                const result = depthFirstSearch(child);
                if (result !== undefined) {
                    return result;
                }
            }
        }
        finally {
            stack.pop();
        }
        return undefined;
    }
    const result = depthFirstSearch(root);
    return result ?? [];
}
function getChildServiceNodes(serviceNode) {
    const children = [];
    const bindings = serviceNode.bindings;
    if (bindings === undefined) {
        return children;
    }
    const processBindingNode = (bindingNode) => {
        if ((0, isPlanServiceRedirectionBindingNode_1.isPlanServiceRedirectionBindingNode)(bindingNode)) {
            for (const redirection of bindingNode.redirections) {
                processBindingNode(redirection);
            }
            return;
        }
        switch (bindingNode.binding.type) {
            case BindingType_1.bindingTypeValues.Instance: {
                const instanceNode = bindingNode;
                for (const ctorParam of instanceNode.constructorParams) {
                    if (ctorParam !== undefined) {
                        children.push(ctorParam);
                    }
                }
                for (const propParam of instanceNode.propertyParams.values()) {
                    children.push(propParam);
                }
                break;
            }
            case BindingType_1.bindingTypeValues.ResolvedValue: {
                const resolvedValueNode = bindingNode;
                for (const param of resolvedValueNode.params) {
                    children.push(param);
                }
                break;
            }
            default:
                break;
        }
    };
    if (Array.isArray(bindings)) {
        for (const bindingNode of bindings) {
            processBindingNode(bindingNode);
        }
    }
    else {
        processBindingNode(bindings);
    }
    return children;
}
function stringifyServiceIdentifierTrace(serviceIdentifiers) {
    const serviceIdentifiersArray = [...serviceIdentifiers];
    if (serviceIdentifiersArray.length === 0) {
        return '(No dependency trace)';
    }
    return serviceIdentifiersArray.map(common_1.stringifyServiceIdentifier).join(' -> ');
}
//# sourceMappingURL=handleResolveError.js.map