import { BroadcastChannel, createLeaderElection } from 'broadcast-channel';
import { v4 as uuidv4 } from 'uuid';
import { Socket } from './Socket.js';
const isV5Enabled = window.BRIGHTSPOT?.ui?.cms?.enableV5UI === true;
const autoUpdatesEnabled = window.BRIGHTSPOT?.ui?.cms?.enableAutomaticUpdates === true;
// Events that will be blocked when automatic updates are disabled in V5 UI.
// All other events will continue to be processed normally.
const BLOCKED_EVENTS = [
    'com.psddev.cms.tool.page.content.PublishBroadcast',
    'com.psddev.assign.AssignmentRtcBroadcast',
    'com.psddev.cms.conversation.PostBroadcast',
    'brightspot.liveblog.LiveBlogPostRtcBroadcast',
];
let rtc;
if (window.DISABLE_RTC || !document.documentElement.dataset.userId) {
    // Fake methods that do nothing when the RTC is disabled.
    rtc = {
        initialize: function () { },
        restore: function () { },
        receive: function () { },
        receiveOnce: function () { },
        execute: function () { },
    };
}
else {
    const BROADCAST = 'broadcast';
    const CALLBACK = 'callback';
    const CONNECT = 'connect';
    const DISCONNECT = 'connect';
    const EXECUTE = 'execute';
    const RESTORE = 'restore';
    // End restoring state by triggering callbacks.
    const broadcastCallbacks = {};
    const triggerBroadcastCallbacks = (broadcast, data) => {
        if (isV5Enabled && !autoUpdatesEnabled && BLOCKED_EVENTS.includes(broadcast)) {
            // Skip this blocked event when automatic updates are disabled
            return;
        }
        for (const callback of broadcastCallbacks[broadcast] ?? []) {
            callback(data);
        }
    };
    const endRestore = (restoreCallback, broadcasts) => {
        for (const { broadcast, data } of broadcasts ?? []) {
            triggerBroadcastCallbacks(broadcast, data);
        }
        if (restoreCallback) {
            restoreCallback(broadcasts);
        }
    };
    // Start restoring state when not a leader.
    const callbackIdPrefix = `${uuidv4()}-`;
    let callbackIdIndex = 0;
    const channel = new BroadcastChannel('brightspot-rtc');
    const startRestore = (message, callback) => {
        const newCallbackId = callbackIdPrefix + ++callbackIdIndex;
        // [OC] Set up to trigger callbacks when the state restore is done.
        const listener = ({ type, callbackId, broadcasts, }) => {
            if (type === CALLBACK && callbackId === newCallbackId) {
                channel.removeEventListener('message', listener);
                endRestore(callback, broadcasts);
            }
        };
        channel.addEventListener('message', listener);
        // Trigger a send on the leader at [LS].
        channel.postMessage({ type: RESTORE, message, callbackId: newCallbackId });
    };
    // Receive messages from the leader.
    const restores = [];
    const broadcastOnceCallbacks = {};
    channel.addEventListener('message', ({ type, broadcast, data }) => {
        if (type === CONNECT) {
            // [OR] When the leader connects, restore states.
            for (const { message, callback } of restores) {
                startRestore(message, callback);
            }
        }
        else if (type === BROADCAST) {
            // [OB] Trigger broadcast callbacks.
            triggerBroadcastCallbacks(broadcast, data);
        }
    });
    // Make sure that there's only one connection to the server.
    let socket;
    const restore = (message, callback) => {
        socket.send(message, (broadcasts) => {
            endRestore(callback, broadcasts);
        });
    };
    createLeaderElection(channel)
        .awaitLeadership()
        .then(() => {
        socket = new Socket(`${window.ROOT_PATH}/_rtc`);
        // [LS] Send a message on behalf of other windows.
        channel.addEventListener('message', ({ type, message, callbackId }) => {
            if (type === DISCONNECT || type === EXECUTE) {
                socket.send(message);
            }
            else if (type === RESTORE) {
                socket.send(message, broadcasts => {
                    // Trigger a callback on other windows at [OC].
                    channel.postMessage({ type: CALLBACK, callbackId, broadcasts });
                });
            }
        });
        socket.connect({
            onConnect: () => {
                // Restore states on the other windows at [OR].
                channel.postMessage({ type: CONNECT });
                // Restore states on the leader.
                for (const { message, callback } of restores) {
                    restore(message, callback);
                }
            },
            onReceive: ({ broadcast, data }) => {
                if (broadcast && data) {
                    // Broadcast to the other windows at [OB].
                    channel.postMessage({ type: BROADCAST, broadcast, data });
                    // Trigger broadcast callbacks on the leader.
                    triggerBroadcastCallbacks(broadcast, data);
                    // Check if this event should be blocked when automatic updates are disabled in V5 UI
                    if (!(isV5Enabled && !autoUpdatesEnabled && BLOCKED_EVENTS.includes(broadcast))) {
                        for (const callback of broadcastOnceCallbacks[broadcast] ?? []) {
                            callback(data);
                        }
                    }
                }
            },
        });
    });
    rtc = {
        initialize: function (state, data, callback) {
            rtc.restore(state, data, callback);
            window.addEventListener('unload', () => {
                const message = {
                    type: 'disconnect',
                    className: state,
                    data: data,
                };
                if (socket) {
                    socket.send(message, callback, true);
                }
                else {
                    // Send the action to the leader at [LS].
                    channel.postMessage({ type: DISCONNECT, message });
                }
            });
        },
        restore: function (state, data, callback) {
            const message = { type: 'restore', className: state, data };
            restores.push({ type: RESTORE, message, callback });
            if (socket) {
                restore(message, callback);
            }
            else {
                startRestore(message, callback);
            }
        },
        receive: function (broadcast, callback) {
            if (callback) {
                ;
                (broadcastCallbacks[broadcast] ??= []).push(callback);
            }
        },
        receiveOnce: function (broadcast, callback) {
            if (callback) {
                ;
                (broadcastOnceCallbacks[broadcast] ??= []).push(callback);
            }
        },
        execute: function (action, data) {
            const message = { type: 'execute', className: action, data };
            if (socket) {
                socket.send(message);
            }
            else {
                // Send the action to the leader at [LS].
                channel.postMessage({ type: EXECUTE, message });
            }
        },
    };
}
export default rtc;
//# sourceMappingURL=rtc.js.map