/** @odoo-module **/

import { WebsocketWorker } from "@bus/workers/websocket_worker";
import { browser } from "@web/core/browser/browser";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { registerCleanup } from "@web/../tests/helpers/cleanup";

class WebSocketMock extends EventTarget {
    constructor() {
        super();
        this.readyState = 0;

        queueMicrotask(() => {
            this.readyState = 1;
            const openEv = new Event("open");
            this.onopen(openEv);
            this.dispatchEvent(openEv);
        });
    }

    close(code = 1000, reason) {
        this.readyState = 3;
        const closeEv = new CloseEvent("close", {
            code,
            reason,
            wasClean: code === 1000,
        });
        this.onclose(closeEv);
        this.dispatchEvent(closeEv);
    }

    onclose(closeEv) {}
    onerror(errorEv) {}
    onopen(openEv) {}

    send(data) {
        if (this.readyState !== 1) {
            const errorEv = new Event("error");
            this.onerror(errorEv);
            this.dispatchEvent(errorEv);
            throw new DOMException("Failed to execute 'send' on 'WebSocket': State is not OPEN");
        }
    }
}

class SharedWorkerMock extends EventTarget {
    constructor(websocketWorker) {
        super();
        this._websocketWorker = websocketWorker;
        this._messageChannel = new MessageChannel();
        this.port = this._messageChannel.port1;
        // port 1 should be started by the service itself.
        this._messageChannel.port2.start();
        this._websocketWorker.registerClient(this._messageChannel.port2);
    }
}

class WorkerMock extends SharedWorkerMock {
    constructor(websocketWorker) {
        super(websocketWorker);
        this.port.start();
        this.postMessage = this.port.postMessage.bind(this.port);
    }
}

let websocketWorker;
/**
 * @param {*} params Parameters used to patch the websocket worker.
 * @returns {WebsocketWorker} Instance of the worker which will run during the
 * test. Usefull to interact with the worker in order to test the
 * websocket behavior.
 */
export function patchWebsocketWorkerWithCleanup(params = {}) {
    patchWithCleanup(window, {
        WebSocket: function () {
            return new WebSocketMock();
        },
    });
    patchWithCleanup(websocketWorker || WebsocketWorker.prototype, params);
    websocketWorker = websocketWorker || new WebsocketWorker();
    websocketWorker.INITIAL_RECONNECT_DELAY = 0;
    websocketWorker.RECONNECT_JITTER = 0;
    patchWithCleanup(browser, {
        SharedWorker: function () {
            const sharedWorker = new SharedWorkerMock(websocketWorker);
            registerCleanup(() => {
                sharedWorker._messageChannel.port1.close();
                sharedWorker._messageChannel.port2.close();
            });
            return sharedWorker;
        },
        Worker: function () {
            const worker = new WorkerMock(websocketWorker);
            registerCleanup(() => {
                worker._messageChannel.port1.close();
                worker._messageChannel.port2.close();
            });
            return worker;
        },
    });
    registerCleanup(() => {
        if (websocketWorker) {
            clearTimeout(websocketWorker.connectTimeout);
            websocketWorker = null;
        }
    });
    return websocketWorker;
}
