"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BindingManager = void 0;
const common_1 = require("@inversifyjs/common");
const core_1 = require("@inversifyjs/core");
const isBindingIdentifier_1 = require("../../binding/calculations/isBindingIdentifier");
const BindingFluentSyntaxImplementation_1 = require("../../binding/models/BindingFluentSyntaxImplementation");
const getFirstIterableResult_1 = require("../../common/calculations/getFirstIterableResult");
const InversifyContainerError_1 = require("../../error/models/InversifyContainerError");
const InversifyContainerErrorKind_1 = require("../../error/models/InversifyContainerErrorKind");
class BindingManager {
    #deactivationParams;
    #defaultScope;
    #planResultCacheManager;
    #serviceReferenceManager;
    constructor(deactivationParams, defaultScope, planResultCacheManager, serviceReferenceManager) {
        this.#deactivationParams = deactivationParams;
        this.#defaultScope = defaultScope;
        this.#planResultCacheManager = planResultCacheManager;
        this.#serviceReferenceManager = serviceReferenceManager;
    }
    bind(serviceIdentifier) {
        return new BindingFluentSyntaxImplementation_1.BindToFluentSyntaxImplementation((binding) => {
            this.#setBinding(binding);
        }, undefined, this.#defaultScope, serviceIdentifier);
    }
    isBound(serviceIdentifier, options) {
        const bindings = this.#serviceReferenceManager.bindingService.get(serviceIdentifier);
        return this.#isAnyValidBinding(serviceIdentifier, bindings, options);
    }
    isCurrentBound(serviceIdentifier, options) {
        const bindings = this.#serviceReferenceManager.bindingService.getNonParentBindings(serviceIdentifier);
        return this.#isAnyValidBinding(serviceIdentifier, bindings, options);
    }
    async rebind(serviceIdentifier) {
        await this.unbind(serviceIdentifier);
        return this.bind(serviceIdentifier);
    }
    rebindSync(serviceIdentifier) {
        this.unbindSync(serviceIdentifier);
        return this.bind(serviceIdentifier);
    }
    async unbind(identifier) {
        await this.#unbind(identifier);
    }
    async unbindAll() {
        await this.#unbindAll();
    }
    unbindAllSync() {
        const result = this.#unbindAll();
        if (result !== undefined) {
            throw new InversifyContainerError_1.InversifyContainerError(InversifyContainerErrorKind_1.InversifyContainerErrorKind.invalidOperation, 'Unexpected asynchronous deactivation when unbinding all services. Consider using Container.unbindAll() instead.');
        }
    }
    unbindSync(identifier) {
        const result = this.#unbind(identifier);
        if (result !== undefined) {
            this.#throwUnexpectedAsyncUnbindOperation(identifier);
        }
    }
    #setBinding(binding) {
        this.#serviceReferenceManager.bindingService.set(binding);
        this.#planResultCacheManager.invalidateService({
            binding: binding,
            kind: core_1.CacheBindingInvalidationKind.bindingAdded,
        });
    }
    #throwUnexpectedAsyncUnbindOperation(identifier) {
        let errorMessage;
        if ((0, isBindingIdentifier_1.isBindingIdentifier)(identifier)) {
            const bindingsById = this.#serviceReferenceManager.bindingService.getById(identifier.id);
            const bindingServiceIdentifier = (0, getFirstIterableResult_1.getFirstIterableResult)(bindingsById)?.serviceIdentifier;
            if (bindingServiceIdentifier === undefined) {
                errorMessage =
                    'Unexpected asynchronous deactivation when unbinding binding identifier. Consider using Container.unbind() instead.';
            }
            else {
                errorMessage = `Unexpected asynchronous deactivation when unbinding "${(0, common_1.stringifyServiceIdentifier)(bindingServiceIdentifier)}" binding. Consider using Container.unbind() instead.`;
            }
        }
        else {
            errorMessage = `Unexpected asynchronous deactivation when unbinding "${(0, common_1.stringifyServiceIdentifier)(identifier)}" service. Consider using Container.unbind() instead.`;
        }
        throw new InversifyContainerError_1.InversifyContainerError(InversifyContainerErrorKind_1.InversifyContainerErrorKind.invalidOperation, errorMessage);
    }
    #unbind(identifier) {
        if ((0, isBindingIdentifier_1.isBindingIdentifier)(identifier)) {
            return this.#unbindBindingIdentifier(identifier);
        }
        return this.#unbindServiceIdentifier(identifier);
    }
    #unbindBindingIdentifier(identifier) {
        const bindingsIterable = this.#serviceReferenceManager.bindingService.getById(identifier.id);
        const bindings = bindingsIterable === undefined ? undefined : [...bindingsIterable];
        const result = (0, core_1.resolveBindingsDeactivations)(this.#deactivationParams, bindingsIterable);
        if (result === undefined) {
            this.#clearAfterUnbindBindingIdentifier(bindings, identifier);
        }
        else {
            return result.then(() => {
                this.#clearAfterUnbindBindingIdentifier(bindings, identifier);
            });
        }
    }
    #clearAfterUnbindBindingIdentifier(bindings, identifier) {
        this.#serviceReferenceManager.bindingService.removeById(identifier.id);
        if (bindings !== undefined) {
            for (const binding of bindings) {
                this.#planResultCacheManager.invalidateService({
                    binding,
                    kind: core_1.CacheBindingInvalidationKind.bindingRemoved,
                });
            }
        }
    }
    #unbindAll() {
        const nonParentBoundServiceIds = [
            ...this.#serviceReferenceManager.bindingService.getNonParentBoundServices(),
        ];
        const deactivationResults = nonParentBoundServiceIds.map((serviceId) => (0, core_1.resolveServiceDeactivations)(this.#deactivationParams, serviceId));
        const hasAsyncDeactivations = deactivationResults.some((result) => (0, common_1.isPromise)(result));
        if (hasAsyncDeactivations) {
            // eslint-disable-next-line @typescript-eslint/await-thenable
            return Promise.all(deactivationResults).then(() => {
                this.#clearAfterUnbindAll(nonParentBoundServiceIds);
            });
        }
        this.#clearAfterUnbindAll(nonParentBoundServiceIds);
    }
    #clearAfterUnbindAll(serviceIds) {
        /*
         * Removing service related objects here so unbindAll is deterministic.
         *
         * Removing service related objects as soon as resolveModuleDeactivations takes
         * effect leads to module deactivations not triggering previously deleted
         * deactivations, introducing non determinism depending in the order in which
         * services are deactivated.
         */
        for (const serviceId of serviceIds) {
            this.#serviceReferenceManager.activationService.removeAllByServiceId(serviceId);
            this.#serviceReferenceManager.bindingService.removeAllByServiceId(serviceId);
            this.#serviceReferenceManager.deactivationService.removeAllByServiceId(serviceId);
        }
        this.#serviceReferenceManager.planResultCacheService.clearCache();
    }
    #unbindServiceIdentifier(identifier) {
        const bindingsIterable = this.#serviceReferenceManager.bindingService.get(identifier);
        const bindings = bindingsIterable === undefined ? undefined : [...bindingsIterable];
        const result = (0, core_1.resolveBindingsDeactivations)(this.#deactivationParams, bindingsIterable);
        if (result === undefined) {
            this.#clearAfterUnbindServiceIdentifier(identifier, bindings);
        }
        else {
            return result.then(() => {
                this.#clearAfterUnbindServiceIdentifier(identifier, bindings);
            });
        }
    }
    #clearAfterUnbindServiceIdentifier(identifier, bindings) {
        this.#serviceReferenceManager.activationService.removeAllByServiceId(identifier);
        this.#serviceReferenceManager.bindingService.removeAllByServiceId(identifier);
        this.#serviceReferenceManager.deactivationService.removeAllByServiceId(identifier);
        if (bindings !== undefined) {
            for (const binding of bindings) {
                this.#planResultCacheManager.invalidateService({
                    binding,
                    kind: core_1.CacheBindingInvalidationKind.bindingRemoved,
                });
            }
        }
    }
    #isAnyValidBinding(serviceIdentifier, bindings, options) {
        if (bindings === undefined) {
            return false;
        }
        const bindingConstraints = {
            getAncestor: () => undefined,
            name: options?.name,
            serviceIdentifier,
            tags: new Map(),
        };
        if (options?.tag !== undefined) {
            bindingConstraints.tags.set(options.tag.key, options.tag.value);
        }
        for (const binding of bindings) {
            if (binding.isSatisfiedBy(bindingConstraints)) {
                return true;
            }
        }
        return false;
    }
}
exports.BindingManager = BindingManager;
//# sourceMappingURL=BindingManager.js.map