/* @odoo-module */

import { DiscussClientAction } from "@mail/core/web/discuss_client_action";

import { fileUploadService } from "@web/core/file_upload/file_upload_service";
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import { session } from "@web/session";
import { makeMockXHR, mocks } from "@web/../tests/helpers/mock_services";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { createWebClient } from "@web/../tests/webclient/helpers";

const ROUTES_TO_IGNORE = [
    "/web/webclient/load_menus",
    "/web/dataset/call_kw/res.users/load_views",
    "/web/dataset/call_kw/res.users/systray_get_activities",
    "/hr_attendance/attendance_user_data",
];
const WEBCLIENT_PARAMETER_NAMES = new Set(["mockRPC", "serverData", "target", "webClientClass"]);
const SERVICES_PARAMETER_NAMES = new Set([
    "legacyServices",
    "loadingBaseDelayDuration",
    "messagingBus",
    "services",
]);

/**
 * @param {import("@web/core/registry").Registry} source
 * @param {import("@web/core/registry").Registry} target
 */
export function copyRegistry(source, target) {
    for (const [name, service] of source.getEntries()) {
        target.add(name, service);
    }
}

// Copy registries before they are cleared by the test setup in
// order to restore them during `getWebClientReady`.
const mailServicesRegistry = registry.category("mail.services");
const webServicesRegistry = registry.category("services");

const mailMainComponentsRegistry = registry.category("mail.main_components");
const webMainComponentsRegistry = registry.category("main_components");

const mailSystrayRegistry = registry.category("mail.systray");
const webSystrayRegistry = registry.category("systray");

QUnit.begin(() => {
    copyRegistry(webServicesRegistry, mailServicesRegistry);
    copyRegistry(webMainComponentsRegistry, mailMainComponentsRegistry);
    copyRegistry(webSystrayRegistry, mailSystrayRegistry);
});

/**
 * @returns function that returns an `XMLHttpRequest`-like object whose response
 * is computed by the given mock server.
 */
function getCreateXHR() {
    const mockedXHR = makeMockXHR();
    return function () {
        const xhr = mockedXHR();
        let response = "";
        let route = "";
        const self = this;
        xhr.status = 200;
        patch(xhr, {
            open(method, dest) {
                route = dest;
                return super.open(method, dest);
            },
            async send(data) {
                await new Promise(setTimeout);
                response = JSON.stringify(
                    await self.env.services.rpc(route, { body: data, method: "POST" })
                );
                return super.send(data);
            },
            upload: new EventTarget(),
            abort() {
                if (this._errorListener) {
                    this._errorListener();
                }
            },
            get response() {
                return response;
            },
        });
        return xhr;
    };
}

export const setupManager = {
    /**
     * Add required components to the main component registry.
     */
    setupMainComponentRegistry() {
        for (const [name, component] of mailMainComponentsRegistry.getEntries()) {
            webMainComponentsRegistry.add(name, component);
        }
        if (!registry.category("actions").contains("mail.action_discuss")) {
            registry.category("actions").add("mail.action_discuss", DiscussClientAction);
        }
    },
    /**
     * Add required components to the systray registry.
     */
    setupSystrayRegistry() {
        for (const [name, component] of mailSystrayRegistry.getEntries()) {
            if (!webSystrayRegistry.contains(name)) {
                webSystrayRegistry.add(name, component);
            }
        }
    },
    /**
     * Setup both legacy and new service registries.
     *
     * @param {Object} param0
     * @param {Object} [param0.services]
     * @param {number} [param0.loadingBaseDelayDuration=0]
     * @param {EventBus} [param0.messagingBus]
     * @param {Function} [param0.mockRPC]
     * @returns {LegacyRegistry} The registry containing all the legacy services that will be passed
     * to the webClient as a legacy parameter.
     */
    setupServiceRegistries({ loadingBaseDelayDuration = 0, messagingBus, services = {} } = {}) {
        const OriginalAudio = window.Audio;
        patchWithCleanup(window, {
            Audio: function () {
                const audio = new OriginalAudio();
                audio.preload = "none";
                audio.play = () => {};
                return audio;
            },
        });
        patchWithCleanup(session, { show_effect: true });
        services["messagingValues"] = services["messagingValues"] ?? {
            start() {
                return {
                    isInQUnitTest: true,
                    disableAnimation: true,
                    loadingBaseDelayDuration,
                    messagingBus,
                    userNotificationManager: { canPlayAudio: false },
                };
            },
        };
        if (!webServicesRegistry.contains("file_upload")) {
            webServicesRegistry.add("file_upload", {
                ...fileUploadService,
                start(env, ...args) {
                    this.env = env;
                    return fileUploadService.start.call(this, env, ...args);
                },
                createXhr: getCreateXHR(),
            });
        }
        for (const [name, service] of Object.entries(services)) {
            webServicesRegistry.add(name, service);
        }
        for (const [name, service] of mailServicesRegistry.getEntries()) {
            if (!mocks[name] && !name.includes("legacy_") && !webServicesRegistry.contains(name)) {
                webServicesRegistry.add(name, service);
            }
        }
    },
};

/**
 * Creates a properly configured instance of WebClient, with the messaging service and all it's
 * dependencies initialized.
 *
 * @param {Object} param0
 * @param {Object} [param0.serverData]
 * @param {Object} [param0.services]
 * @param {Object} [param0.loadingBaseDelayDuration]
 * @param {EventBus} [param0.messagingBus] The event bus to be used by messaging.
 * @returns {WebClient}
 */
async function getWebClientReady(param0) {
    setupManager.setupMainComponentRegistry();

    const servicesParameters = {};
    const param0Entries = Object.entries(param0);
    for (const [parameterName, value] of param0Entries) {
        if (SERVICES_PARAMETER_NAMES.has(parameterName)) {
            servicesParameters[parameterName] = value;
        }
    }
    setupManager.setupServiceRegistries(servicesParameters);
    setupManager.setupSystrayRegistry();

    const webClientParameters = {};
    for (const [parameterName, value] of param0Entries) {
        if (WEBCLIENT_PARAMETER_NAMES.has(parameterName)) {
            webClientParameters[parameterName] = value;
        }
    }
    return createWebClient(webClientParameters);
}

export { getWebClientReady, ROUTES_TO_IGNORE };
