import { serviceCapabilities, serviceDependencies, isStartable } from '@libp2p/interface';
import { defaultLogger } from '@libp2p/logger';
import { MissingServiceError, UnmetServiceDependenciesError } from './errors.js';
class DefaultComponents {
    components = {};
    _started = false;
    constructor(init = {}) {
        this.components = {};
        for (const [key, value] of Object.entries(init)) {
            this.components[key] = value;
        }
        if (this.components.logger == null) {
            this.components.logger = defaultLogger();
        }
    }
    isStarted() {
        return this._started;
    }
    async _invokeStartableMethod(methodName) {
        await Promise.all(Object.values(this.components)
            .filter(obj => isStartable(obj))
            .map(async (startable) => {
            await startable[methodName]?.();
        }));
    }
    async beforeStart() {
        await this._invokeStartableMethod('beforeStart');
    }
    async start() {
        await this._invokeStartableMethod('start');
        this._started = true;
    }
    async afterStart() {
        await this._invokeStartableMethod('afterStart');
    }
    async beforeStop() {
        await this._invokeStartableMethod('beforeStop');
    }
    async stop() {
        await this._invokeStartableMethod('stop');
        this._started = false;
    }
    async afterStop() {
        await this._invokeStartableMethod('afterStop');
    }
}
const OPTIONAL_SERVICES = [
    'metrics',
    'connectionProtector',
    'dns'
];
const NON_SERVICE_PROPERTIES = [
    'components',
    'isStarted',
    'beforeStart',
    'start',
    'afterStart',
    'beforeStop',
    'stop',
    'afterStop',
    'then',
    '_invokeStartableMethod'
];
export function defaultComponents(init = {}) {
    const components = new DefaultComponents(init);
    const proxy = new Proxy(components, {
        get(target, prop, receiver) {
            if (typeof prop === 'string' && !NON_SERVICE_PROPERTIES.includes(prop)) {
                const service = components.components[prop];
                if (service == null && !OPTIONAL_SERVICES.includes(prop)) {
                    throw new MissingServiceError(`${prop} not set`);
                }
                return service;
            }
            return Reflect.get(target, prop, receiver);
        },
        set(target, prop, value) {
            if (typeof prop === 'string') {
                components.components[prop] = value;
            }
            else {
                Reflect.set(target, prop, value);
            }
            return true;
        }
    });
    // @ts-expect-error component keys are proxied
    return proxy;
}
export function checkServiceDependencies(components) {
    const serviceCapabilities = {};
    for (const service of Object.values(components.components)) {
        for (const capability of getServiceCapabilities(service)) {
            serviceCapabilities[capability] = true;
        }
    }
    for (const service of Object.values(components.components)) {
        for (const capability of getServiceDependencies(service)) {
            if (serviceCapabilities[capability] !== true) {
                throw new UnmetServiceDependenciesError(`Service "${getServiceName(service)}" required capability "${capability}" but it was not provided by any component, you may need to add additional configuration when creating your node.`);
            }
        }
    }
}
function getServiceCapabilities(service) {
    if (Array.isArray(service?.[serviceCapabilities])) {
        return service[serviceCapabilities];
    }
    return [];
}
function getServiceDependencies(service) {
    if (Array.isArray(service?.[serviceDependencies])) {
        return service[serviceDependencies];
    }
    return [];
}
function getServiceName(service) {
    return service?.[Symbol.toStringTag] ?? service?.toString() ?? 'unknown';
}
//# sourceMappingURL=components.js.map