import { objMerge } from '../utils.js';
import { RegistryType, } from './IRegistry.js';
/**
 * A registry that accepts multiple sub-registries.
 * Read methods are performed on all sub-registries and the results are merged.
 * Write methods are performed on all sub-registries.
 * Can be created manually or by calling `.merge()` on an existing registry.
 */
export class MergedRegistry {
    type = RegistryType.Merged;
    uri = '__merged_registry__';
    registries;
    logger;
    constructor({ registries, logger }) {
        if (!registries.length)
            throw new Error('At least one registry URI is required');
        this.registries = registries;
        // @ts-ignore
        this.logger = logger || console;
    }
    getUri() {
        throw new Error('getUri method not applicable to MergedRegistry');
    }
    async listRegistryContent() {
        const results = await this.multiRegistryRead((r) => r.listRegistryContent());
        return results.reduce((acc, content) => objMerge(acc, content), {
            chains: {},
            deployments: {
                warpRoutes: {},
                warpDeployConfig: {},
            },
        });
    }
    async getChains() {
        return Object.keys(await this.getMetadata());
    }
    async getMetadata() {
        const results = await this.multiRegistryRead((r) => r.getMetadata());
        return results.reduce((acc, content) => objMerge(acc, content), {});
    }
    async getChainMetadata(chainName) {
        return (await this.getMetadata())[chainName] || null;
    }
    async getAddresses() {
        const results = await this.multiRegistryRead((r) => r.getAddresses());
        return results.reduce((acc, content) => objMerge(acc, content), {});
    }
    async getChainAddresses(chainName) {
        return (await this.getAddresses())[chainName] || null;
    }
    async getChainLogoUri(chainName) {
        const results = await this.multiRegistryRead((r) => r.getChainLogoUri(chainName));
        return results.find((uri) => !!uri) || null;
    }
    async addChain(chain) {
        return this.multiRegistryWrite(async (registry) => await registry.addChain(chain), 'addChain', `adding chain ${chain.chainName}`);
    }
    async updateChain(chain) {
        return this.multiRegistryWrite(async (registry) => await registry.updateChain(chain), 'updateChain', `updating chain ${chain.chainName}`);
    }
    async removeChain(chain) {
        return this.multiRegistryWrite(async (registry) => await registry.removeChain(chain), 'removeChain', `removing chain ${chain}`);
    }
    async getWarpRoute(id) {
        const results = await this.multiRegistryRead((r) => r.getWarpRoute(id));
        return results.find((r) => !!r) || null;
    }
    async getWarpDeployConfig(id) {
        const results = await this.multiRegistryRead((r) => r.getWarpDeployConfig(id));
        return results.find((r) => !!r) || null;
    }
    async getWarpRoutes(filter) {
        const results = await this.multiRegistryRead((r) => r.getWarpRoutes(filter));
        return results.reduce((acc, content) => objMerge(acc, content), {});
    }
    async getWarpDeployConfigs(filter) {
        const results = await this.multiRegistryRead((r) => r.getWarpDeployConfigs(filter));
        return results.reduce((acc, content) => objMerge(acc, content), {});
    }
    async addWarpRoute(config, options) {
        return this.multiRegistryWrite(async (registry) => await registry.addWarpRoute(config, options), 'addWarpRoute', 'adding warp route');
    }
    async addWarpRouteConfig(config, options) {
        return this.multiRegistryWrite(async (registry) => await registry.addWarpRouteConfig(config, options), 'addWarpRouteConfig', 'adding warp route deploy config');
    }
    multiRegistryRead(readFn) {
        return Promise.all(this.registries.map(readFn));
    }
    async multiRegistryWrite(writeFn, methodName, logMsg) {
        for (const registry of this.registries) {
            if (registry.unimplementedMethods?.has(methodName)) {
                this.logger.warn(`Skipping ${logMsg} at ${registry.type} registry (not supported)`);
                continue;
            }
            try {
                this.logger.info(`Now ${logMsg} at ${registry.type} registry at ${registry.uri}`);
                await writeFn(registry);
                this.logger.info(`Done ${logMsg} at ${registry.type} registry`);
            }
            catch (error) {
                // To prevent loss of artifacts, MergedRegistry write methods are failure tolerant
                this.logger.error(`Failure ${logMsg} at ${registry.type} registry`, error);
            }
        }
    }
    merge(otherRegistry) {
        return new MergedRegistry({
            registries: [...this.registries, otherRegistry],
            logger: this.logger,
        });
    }
}
