"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBroadcastChannel = createBroadcastChannel;
exports.createDistributedMethod = createDistributedMethod;
const node_os_1 = require("node:os");
const resolver_1 = require("../../app/resolver");
const logger_1 = require("../../services/logger");
const redis_1 = require("./redis");
let sharedBroadcastChannel;
function getSharedBroadcastChannel() {
    sharedBroadcastChannel ??= createSharedBroadcastChannel();
    return sharedBroadcastChannel;
}
function createSharedBroadcastChannel() {
    const logger = (0, resolver_1.r)(logger_1.Logger).scoped('Broadcast');
    const { prefix, client: publishClient } = (0, redis_1.createRedis)('BROADCAST');
    const { client: subscribeClient } = (0, redis_1.createRedis)('BROADCAST');
    const channel = `${prefix}:broadcast`;
    const localInstanceKey = `${(0, node_os_1.hostname)()}/${process.pid}`;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const listeners = new Map();
    subscribeClient.subscribe(channel);
    subscribeClient.on('message', (_, message) => {
        try {
            const { instanceKey, eventName, data } = JSON.parse(message);
            if (instanceKey === localInstanceKey)
                return;
            const listenersForEvent = listeners.get(eventName);
            if (!listenersForEvent)
                return;
            for (const listener of listenersForEvent) {
                try {
                    listener(data);
                }
                catch (err) {
                    logger.error(`Failed to handle broadcast message`, err, { eventName });
                }
            }
        }
        catch (err) {
            logger.error('Failed to parse broadcast message', err, message);
        }
    });
    return {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        subscribe: (eventName, fn) => {
            const listenersForEvent = listeners.get(eventName) ?? new Set();
            listenersForEvent.add(fn);
            listeners.set(eventName, listenersForEvent);
        },
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        publish: (eventName, data) => {
            publishClient.publish(channel, JSON.stringify({ instanceKey: localInstanceKey, eventName, data }));
        }
    };
}
function createBroadcastChannel(eventName, _type) {
    const channel = getSharedBroadcastChannel();
    return {
        subscribe: (fn) => {
            channel.subscribe(eventName, data => {
                // todo: figure out type validation
                // assert<T>(data, undefined, type);
                fn(data);
            });
        },
        publish: (data) => {
            channel.publish(eventName, data);
        }
    };
}
function createDistributedMethod(options, fn, type) {
    const getLogger = options.logger ?? (() => (0, resolver_1.r)(logger_1.Logger).scoped(`Distributed:${options.name}`));
    const channel = createBroadcastChannel(options.name, type);
    const wrappedFn = async (data) => {
        try {
            await fn(data);
        }
        catch (err) {
            getLogger().error(`Error executing ${options.name} distributed method`, err);
        }
    };
    // invoke locally when remotely requested
    channel.subscribe(wrappedFn);
    // publish & invoke locally when locally executed
    return (data) => {
        channel.publish(data);
        return wrappedFn(data);
    };
}
//# sourceMappingURL=broadcast.js.map