"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.Container = void 0;
exports.createContainer = createContainer;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const reflection_1 = require("../reflection");
const types_1 = require("../types");
const errors_1 = require("./errors");
const provider_1 = require("./provider");
const module_1 = require("./module");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":2,\"typeName\":\"Token\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"T\"}]},{\"kind\":2,\"typeName\":\"T\"},{\"kind\":2,\"typeName\":\"Token\"},{\"kind\":7},{\"kind\":8},{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Token\"}},{\"kind\":2},{\"kind\":2,\"typeName\":\"ClassType\"},{\"kind\":14,\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"token\",\"type\":{\"kind\":2,\"typeName\":\"Token\"},\"optional\":false},{\"kind\":20,\"name\":\"provider\",\"type\":{\"kind\":2,\"typeName\":\"Provider\"},\"optional\":false},{\"kind\":20,\"name\":\"moduleId\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"exported\",\"type\":{\"kind\":8},\"optional\":false}]}},{\"kind\":18,\"typeName\":\"ModuleDefinition\",\"types\":[{\"kind\":20,\"name\":\"config\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"ClassType\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"defaultConfig\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"Partial\",\"utilityType\":\"Partial\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}],\"utilityKeys\":[],\"types\":[]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"imports\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"ModuleDefinition\",\"types\":[{\"kind\":20,\"name\":\"config\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"ClassType\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"defaultConfig\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"Partial\",\"utilityType\":\"Partial\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}],\"utilityKeys\":[],\"types\":[]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"imports\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"ModuleDefinition\"},{\"kind\":2,\"typeName\":\"ClassType\"},{\"kind\":18,\"typeName\":\"ModuleInstance\",\"types\":[{\"kind\":20,\"name\":\"definition\",\"type\":{\"kind\":2,\"typeName\":\"ModuleDefinition\"},\"optional\":false}]}],\"typeName\":\"ModuleLike\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"exports\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Token\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"providers\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Provider\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"controllers\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"listeners\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"commands\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true}]},{\"kind\":2,\"typeName\":\"ClassType\"},{\"kind\":18,\"typeName\":\"ModuleInstance\",\"types\":[{\"kind\":20,\"name\":\"definition\",\"type\":{\"kind\":18,\"typeName\":\"ModuleDefinition\",\"types\":[{\"kind\":20,\"name\":\"config\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"ClassType\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"defaultConfig\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"Partial\",\"utilityType\":\"Partial\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"C\"}],\"utilityKeys\":[],\"types\":[]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"imports\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"ModuleDefinition\"},{\"kind\":2,\"typeName\":\"ClassType\"},{\"kind\":2,\"typeName\":\"ModuleInstance\"}],\"typeName\":\"ModuleLike\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"exports\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Token\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"providers\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Provider\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"controllers\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"listeners\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"commands\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true}]},\"optional\":false}]}],\"typeName\":\"ModuleLike\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"exports\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Token\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"providers\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"Provider\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"controllers\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"listeners\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"commands\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"ClassType\"}},{\"kind\":4}]},\"optional\":true}]}]]", []);
let _ContainerMetadata_1;
class Container {
    modules = [];
    rootProviders = new Map();
    exportedProviders = new Map();
    singletons = new Map();
    constructor(root) {
        this.compile(root);
    }
    createRequestContext() {
        return { instances: new Map() };
    }
    get(token, context) {
        return this.resolve(token, undefined, context, []);
    }
    has(token, requestingModuleId) {
        return !!this.findRegisteredProvider(token, requestingModuleId);
    }
    resolve(token, requestingModuleId, context, chain = [], singletonInChain = false, injectionTarget) {
        if (chain.includes(token)) {
            throw new errors_1.CyclicDependencyError([...chain, token]);
        }
        if (context?.instances.has(token)) {
            if (singletonInChain)
                throw new errors_1.ScopeMismatchError(token, chain);
            return context.instances.get(token);
        }
        const registered = this.findRegisteredProvider(token, requestingModuleId);
        if (!registered) {
            throw new errors_1.ProviderNotFoundError(token, chain);
        }
        const scope = (0, provider_1.getProviderScope)(registered.provider);
        if (scope === 'singleton' && this.singletons.has(token))
            return this.singletons.get(token);
        if (scope === 'request') {
            if (singletonInChain)
                throw new errors_1.ScopeMismatchError(token, chain);
            if (!context)
                throw new errors_1.RequestScopeError(token);
            if (context.instances.has(token))
                return context.instances.get(token);
        }
        const value = this.instantiate(registered, context, [...chain, token], singletonInChain || scope === 'singleton', injectionTarget);
        if (scope === 'singleton')
            this.singletons.set(token, value);
        if (scope === 'request')
            context.instances.set(token, value);
        return value;
    }
    listProviders() {
        return [
            ...new Set([...this.rootProviders.keys(), ...this.exportedProviders.keys(), ...this.modules.flatMap(m => [...m.localProviders.keys()])])
        ];
    }
    listExports() {
        return [...this.exportedProviders.keys()];
    }
    listRegisteredProviders() {
        const result = [];
        const seen = new Set();
        const providers = [
            ...this.rootProviders.values(),
            ...this.exportedProviders.values(),
            ...this.modules.flatMap(module => [...module.localProviders.values()])
        ];
        for (const registered of providers) {
            if (seen.has(registered))
                continue;
            seen.add(registered);
            result.push({
                token: (0, provider_1.getProviderToken)(registered.provider),
                provider: registered.provider,
                moduleId: registered.moduleId,
                exported: registered.exported
            });
        }
        return result;
    }
    compile(root) {
        const rootRecord = this.addModule(root);
        for (const provider of [...(root.providers ?? []), ...(root.listeners ?? [])]) {
            this.registerProvider(rootRecord, provider);
            this.registerRootProvider(provider, rootRecord);
        }
        for (const imported of root.imports ?? []) {
            const importedRecord = this.compileImportedModule((0, module_1.normalizeModule)(imported), true);
            this.addImportedExports(rootRecord, importedRecord);
        }
    }
    compileImportedModule(definition, exposeExportsGlobally) {
        const record = this.addModule(definition);
        for (const provider of [...(definition.providers ?? []), ...(definition.listeners ?? [])]) {
            this.registerProvider(record, provider);
        }
        for (const imported of definition.imports ?? []) {
            const importedRecord = this.compileImportedModule((0, module_1.normalizeModule)(imported), false);
            this.addImportedExports(record, importedRecord);
        }
        for (const token of definition.exports ?? []) {
            const registered = record.localProviders.get(token) ?? record.importedProviders.get(token);
            if (!registered)
                throw new errors_1.ProviderNotFoundError(token);
            const exported = { ...registered, exported: true };
            record.exportedProviders.set(token, exported);
            if (exposeExportsGlobally)
                this.registerExport(token, exported);
        }
        return record;
    }
    addModule(definition) {
        const record = {
            id: this.modules.length,
            definition,
            localProviders: new Map(),
            importedProviders: new Map(),
            exportedProviders: new Map()
        };
        this.modules.push(record);
        return record;
    }
    addImportedExports(record, importedRecord) {
        for (const [token, provider] of importedRecord.exportedProviders) {
            if (!record.importedProviders.has(token))
                record.importedProviders.set(token, provider);
        }
    }
    registerProvider(moduleRecord, provider) {
        const token = (0, provider_1.getProviderToken)(provider);
        moduleRecord.localProviders.set(token, {
            provider,
            moduleId: moduleRecord.id,
            exported: false
        });
    }
    registerRootProvider(provider, moduleRecord) {
        const token = (0, provider_1.getProviderToken)(provider);
        this.rootProviders.set(token, { provider, moduleId: moduleRecord.id, exported: true });
    }
    registerExport(token, provider) {
        if (this.exportedProviders.has(token))
            throw new errors_1.DuplicateProviderError(token, 'global exports');
        this.exportedProviders.set(token, { ...provider, exported: true });
    }
    findRegisteredProvider(token, requestingModuleId) {
        if (requestingModuleId !== undefined) {
            const module = this.modules[requestingModuleId];
            const local = module?.localProviders.get(token);
            if (local)
                return local;
            const imported = module?.importedProviders.get(token);
            if (imported)
                return imported;
        }
        return this.rootProviders.get(token) ?? this.exportedProviders.get(token);
    }
    instantiate(registered, context, chain, singletonInChain, injectionTarget) {
        const provider = registered.provider;
        if (!(0, provider_1.isStructuredProvider)(provider)) {
            if (!(0, types_1.isClass)(provider))
                throw new Error(`Provider ${provider.name || '<anonymous>'} is not constructable`);
            return this.instantiateClass(provider, registered.moduleId, context, chain, singletonInChain);
        }
        if ('useValue' in provider)
            return provider.useValue;
        if ('useExisting' in provider)
            return this.resolve(provider.useExisting, registered.moduleId, context, chain, singletonInChain, injectionTarget);
        if ('useFactory' in provider) {
            const deps = provider.deps ?? [];
            const args = deps.map(dep => this.resolve(dep, registered.moduleId, context, chain, singletonInChain, injectionTarget));
            return provider.useFactory(...args);
        }
        if ('useTargetFactory' in provider) {
            const deps = provider.deps ?? [];
            const args = deps.map(dep => this.resolve(dep, registered.moduleId, context, chain, singletonInChain, injectionTarget));
            return provider.useTargetFactory(injectionTarget ?? getInjectionTarget(chain), ...args);
        }
        if ('useClass' in provider) {
            return this.instantiateClass(provider.useClass, registered.moduleId, context, chain, singletonInChain);
        }
        throw new errors_1.ProviderNotFoundError((0, provider_1.getProviderToken)(provider), chain);
    }
    instantiateClass(classType, moduleId, context, chain, singletonInChain) {
        const deps = this.getConstructorDependencies(classType).map(dep => {
            if (!dep.token)
                return undefined;
            if (dep.optional && !this.findRegisteredProvider(dep.token, moduleId))
                return undefined;
            return this.resolve(dep.token, moduleId, context, chain, singletonInChain, classType);
        });
        return new classType(...deps);
    }
    getConstructorDependencies(classType) {
        let reflection;
        try {
            reflection = reflection_1.ReflectionClass.from(classType);
        }
        catch (error) {
            if (classType.length === 0 && error instanceof Error && error.message.includes('No runtime type metadata'))
                return [];
            throw error;
        }
        const constructor = reflection.getConstructorOrUndefined();
        if (!constructor)
            return [];
        return constructor.getParameters().map(parameter => {
            const type = parameter.getType();
            const optional = parameter.isOptional() || parameter.hasDefault();
            const token = tokenFromType(type);
            if (!token && !optional) {
                throw new Error(`Cannot resolve constructor parameter ${parameter.getName()} of ${classType.name}; use an explicit factory provider`);
            }
            return { token, optional };
        });
    }
    static __tsfType = _ContainerMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"Container\",\"typeName\":\"Container\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[{\"name\":\"createRequestContext\",\"parameters\":[],\"returnType\":{\"kind\":18,\"typeName\":\"RequestContext\",\"types\":[{\"kind\":20,\"name\":\"instances\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":1},\"typeArguments\":[{\"kind\":2,\"typeName\":\"Token\"},{\"kind\":2}]},\"optional\":false}]}},{\"name\":\"get\",\"parameters\":[{\"name\":\"token\",\"type\":{\"$tsfType\":0},\"optional\":false,\"default\":false},{\"name\":\"context\",\"type\":{\"kind\":18,\"typeName\":\"RequestContext\",\"types\":[{\"kind\":20,\"name\":\"instances\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":1},\"typeArguments\":[{\"kind\":2,\"typeName\":\"Token\"},{\"kind\":2}]},\"optional\":false}]},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"has\",\"parameters\":[{\"name\":\"token\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false},{\"name\":\"requestingModuleId\",\"type\":{\"$tsfType\":3},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":4}},{\"name\":\"resolve\",\"parameters\":[{\"name\":\"token\",\"type\":{\"$tsfType\":0},\"optional\":false,\"default\":false},{\"name\":\"requestingModuleId\",\"type\":{\"$tsfType\":3},\"optional\":true,\"default\":false},{\"name\":\"context\",\"type\":{\"kind\":18,\"typeName\":\"RequestContext\",\"types\":[{\"kind\":20,\"name\":\"instances\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":1},\"typeArguments\":[{\"kind\":2,\"typeName\":\"Token\"},{\"kind\":2}]},\"optional\":false}]},\"optional\":true,\"default\":false},{\"name\":\"chain\",\"type\":{\"$tsfType\":5},\"optional\":true,\"default\":true},{\"name\":\"singletonInChain\",\"type\":{\"$tsfType\":6},\"optional\":true,\"default\":true},{\"name\":\"injectionTarget\",\"type\":{\"$tsfType\":7},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"listProviders\",\"parameters\":[],\"returnType\":{\"$tsfType\":5}},{\"name\":\"listExports\",\"parameters\":[],\"returnType\":{\"$tsfType\":5}},{\"name\":\"listRegisteredProviders\",\"parameters\":[],\"returnType\":{\"$tsfType\":8}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"root\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false}]}]", [() => Container, () => (typeof Map !== "undefined" ? Map : (typeof exports !== "undefined" ? exports.Map : undefined))], __tsf_metadata_type);
}
exports.Container = Container;
_ContainerMetadata_1.classType = Container;
Container.__tsfType = _ContainerMetadata_1;
function getInjectionTarget(chain) {
    for (let i = chain.length - 2; i >= 0; i--) {
        const token = chain[i];
        if (typeof token === 'function' && token.prototype)
            return token;
    }
}
function tokenFromType(type) {
    const concrete = unwrapOptionalType(type);
    if (concrete.kind === reflection_1.ReflectionKind.class)
        return concrete.classType;
    return undefined;
}
function unwrapOptionalType(type) {
    if (type.kind !== reflection_1.ReflectionKind.union)
        return type;
    const concrete = type.types.filter(t => t.kind !== reflection_1.ReflectionKind.undefined && t.kind !== reflection_1.ReflectionKind.null);
    return concrete.length === 1 ? concrete[0] : type;
}
function createContainer(definition) {
    return new Container(definition);
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"RequestContext\":{\"kind\":18,\"typeName\":\"RequestContext\",\"types\":[{\"kind\":20,\"name\":\"instances\",\"type\":{\"kind\":16,\"typeName\":\"Map\",\"classType\":{\"$tsf\":0},\"typeArguments\":[{\"kind\":2,\"typeName\":\"Token\"},{\"kind\":2}]},\"optional\":false}]}}]", [() => (typeof Map !== "undefined" ? Map : (typeof exports !== "undefined" ? exports.Map : undefined))], __tsf_metadata_type);
//# sourceMappingURL=container.js.map