// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { WebRtcClient } from "@luxonis/remote-connection";
import { BinaryOpcode } from "@foxglove/ws-protocol";
import { WebRtcCommsProxy } from "./comms-proxy";
import { ArtificialWorker } from "../../utils/artificial-worker.js";
import { Logger } from "../../utils/logger";
import { parseFoxgloveMessage } from "../foxglove-protocol.js";
const logger = Logger.getLogger();
const MESSAGE_CHANNEL_NAME = "message";
const SYSTEM_CHANNEL_NAME = "system";
const COMMS_CHANNEL_NAME = "comms";
function isInternalChannel(channelName) {
    return (channelName === MESSAGE_CHANNEL_NAME ||
        channelName === SYSTEM_CHANNEL_NAME ||
        channelName === COMMS_CHANNEL_NAME);
}
export class WebRtcBridge extends ArtificialWorker {
    automaticTokenRefresh;
    #worker;
    #webrtc = null;
    #messageChannel = null;
    #systemChannel = null;
    commsProxy = null;
    #workerMessageListener = null;
    #mainMessageListener = null;
    constructor(automaticTokenRefresh) {
        super();
        this.automaticTokenRefresh = automaticTokenRefresh;
        this.#worker = new Worker(new URL("./worker", import.meta.url), {
            type: "module",
        });
        this.#setupArtificialWorker();
    }
    terminate() {
        this.#cleanupWorkerListeners();
        this.#cleanupMainListeners();
        this.#worker.terminate();
        this.commsProxy?.handleClose();
        this.#webrtc?.dispose("Connection terminated");
        this.#webrtc = null;
        this.#messageChannel = null;
        this.#systemChannel = null;
    }
    #cleanupWorkerListeners() {
        if (this.#workerMessageListener) {
            this.#worker.removeEventListener("message", this.#workerMessageListener);
            this.#workerMessageListener = null;
        }
    }
    #cleanupMainListeners() {
        if (this.#mainMessageListener) {
            this.removeEventListenerFromMain("message", this.#mainMessageListener);
            this.#mainMessageListener = null;
        }
    }
    #setupArtificialWorker() {
        this.#mainMessageListener = (event) => {
            switch (event.data.type) {
                case "open": {
                    if ("config" in event.data.data) {
                        this.#createWebRtc({
                            withFragmentation: true,
                            ...event.data.data.config,
                        });
                    }
                    break;
                }
                case "close": {
                    this.#webrtc?.dispose("Client requested connection close");
                    this.terminate();
                    break;
                }
                case "call_service": {
                    this.#worker.postMessage({
                        kind: "serviceCall",
                        data: {
                            message: event.data.data,
                        },
                    });
                    break;
                }
                case "unsubscribe":
                case "subscribe": {
                    this.#worker.postMessage({
                        kind: event.data.type,
                        data: { message: { topics: event.data.data } },
                    });
                    break;
                }
            }
        };
        this.addEventListenerFromMain("message", this.#mainMessageListener);
    }
    #createWebRtc(config) {
        this.#webrtc = new WebRtcClient(config);
        this.#handleWebRtc(this.#webrtc);
        this.#handleMessages();
    }
    #handleWebRtc(client) {
        client.on("error", (error) => {
            logger.error("WebRtc Client Failed", error);
            if (error === 'signaling_rejected') {
                this.#worker.postMessage({ kind: "disconnect", error });
            }
        });
        client.on("connection_established", (connection) => {
            connection.on("data_channel", (channel) => {
                this.#handleWebRtcDataChannelClose(channel);
                switch (channel.label) {
                    case MESSAGE_CHANNEL_NAME:
                        this.#handleWebRtcMessageChannel(channel);
                        break;
                    case SYSTEM_CHANNEL_NAME:
                        this.#handleWebRtcSystemChannel(channel);
                        break;
                    case COMMS_CHANNEL_NAME:
                        this.#handleWebRtcCommsChannel(channel);
                        break;
                }
                this.#handleWebRtcDataChannelOpenNotification(channel);
                // TODO: Maybe wait for comms as well? But this would break older versions of the app -> Perhaps do it later
                if (this.#systemChannel?.raw.readyState === "open" &&
                    this.#messageChannel?.raw.readyState === "open") {
                    this.postMessageToMain({ type: "open" });
                }
            });
            connection.on("connection_closed", () => {
                this.#worker.postMessage({
                    kind: "connection",
                    data: { channel: null, ready: false },
                });
                this.postMessageToMain({ type: "close" });
            });
        });
    }
    #handleWebRtcDataChannelClose(channel) {
        channel.on("close", () => {
            if (!isInternalChannel(channel.label)) {
                return;
            }
            this.#worker.postMessage({
                kind: "connection",
                data: {
                    channel: channel.label,
                    ready: false,
                },
            });
            this.postMessageToMain({ type: "close" });
        });
    }
    #handleWebRtcMessageChannel(channel) {
        logger.debug("[DEBUG] Message channel opened!");
        this.#messageChannel = channel;
        channel.on("message:binary", async (view) => {
            const message = parseFoxgloveMessage(view);
            if (!message) {
                return;
            }
            if (message.op === BinaryOpcode.MESSAGE_DATA) {
                this.#worker.postMessage({
                    kind: "genericMessage",
                    data: { message },
                }, [message.data.buffer]);
            }
            else {
                this.#worker.postMessage({
                    kind: "systemMessage",
                    data: {
                        message: {
                            op: "serviceResponse",
                            callId: message.callId,
                            data: message.data,
                        },
                    },
                }, [message.data.buffer]);
            }
        });
    }
    #handleWebRtcSystemChannel(channel) {
        logger.debug("[DEBUG] System channel opened!");
        this.#systemChannel = channel;
        channel.on("message:json", async (data) => {
            this.#worker.postMessage({
                kind: "systemMessage",
                data: { message: data },
            });
        });
    }
    #handleWebRtcCommsChannel(channel) {
        logger.debug("[DEBUG] Comms channel opened!");
        this.commsProxy = new WebRtcCommsProxy(channel, this.automaticTokenRefresh);
    }
    #handleWebRtcDataChannelOpenNotification(channel) {
        if (!isInternalChannel(channel.label)) {
            return;
        }
        this.#worker.postMessage({
            kind: "connection",
            data: {
                channel: channel.label,
                ready: true,
            },
        });
    }
    #handleMessages() {
        this.#workerMessageListener = ((event) => {
            switch (event.data.type) {
                case "send": {
                    const channel = event.data.data.channel === "message" ? this.#messageChannel : this.#systemChannel;
                    if (channel) {
                        channel.sendMessage(event.data.data.data);
                    }
                    break;
                }
                default: {
                    this.postMessageToMain(event.data);
                }
            }
        });
        this.#worker.addEventListener("message", this.#workerMessageListener);
    }
}
//# sourceMappingURL=bridge.js.map