odoo.define('mail/static/src/env/test_env.js', function (require) {
'use strict';

const { makeDeferred } = require('mail/static/src/utils/deferred/deferred.js');
const { nextTick } = require('mail/static/src/utils/utils.js');

const { Store } = owl;
const { EventBus } = owl.core;

/**
 * @param {Object} [providedEnv={}]
 * @returns {Object}
 */
function addMessagingToEnv(providedEnv = {}) {
    const env = Object.assign(providedEnv);

    /**
     * Messaging store
     */
    const store = new Store({
        env,
        state: {
            messagingRevNumber: 0,
        },
    });

    /**
     * Registry of models.
     */
    env.models = {};
    /**
     * Environment keys used in messaging.
     */
    Object.assign(env, {
        autofetchPartnerImStatus: false,
        browser: Object.assign({
            innerHeight: 1080,
            innerWidth: 1920,
            Notification: Object.assign({
                permission: 'denied',
                async requestPermission() {
                    return this.permission;
                },
            }, (env.browser && env.browser.Notification) || {}),
        }, env.browser),
        destroyMessaging() {
            if (env.modelManager) {
                env.modelManager.deleteAll();
                env.messaging = undefined;
            }
        },
        disableAnimation: true,
        isMessagingInitialized() {
            if (!this.messaging) {
                return false;
            }
            return this.messaging.isInitialized;
        },
        /**
         * States whether the environment is in QUnit test or not.
         *
         * Useful to prevent some behaviour in QUnit tests, like applying
         * style of attachment that uses url.
         */
        isQUnitTest: true,
        loadingBaseDelayDuration: providedEnv.loadingBaseDelayDuration || 0,
        messaging: undefined,
        messagingCreatedPromise: makeDeferred(),
        messagingInitializedDeferred: makeDeferred(),
        messagingBus: new EventBus(),
        modelManager: undefined,
        store,
    });

    return env;
}

/**
 * @param {Object} [providedEnv={}]
 * @returns {Object}
 */
function addTimeControlToEnv(providedEnv = {}) {

    let env = Object.assign({}, providedEnv);

    if (!env.browser) {
        env.browser = {};
    }
    // list of timeout ids that have timed out.
    let timedOutIds = [];
    // key: timeoutId, value: func + remaining duration
    const timeouts = new Map();
    Object.assign(env.browser, {
        clearTimeout: id => {
            timeouts.delete(id);
            timedOutIds = timedOutIds.filter(i => i !== id);
        },
        setTimeout: (func, duration) => {
            const timeoutId = _.uniqueId('timeout_');
            const timeout = {
                id: timeoutId,
                isTimedOut: false,
                func,
                duration,
            };
            timeouts.set(timeoutId, timeout);
            if (duration === 0) {
                timedOutIds.push(timeoutId);
                timeout.isTimedOut = true;
            }
            return timeoutId;
        },
    });
    if (!env.testUtils) {
        env.testUtils = {};
    }
    Object.assign(env.testUtils, {
        advanceTime: async duration => {
            await nextTick();
            for (const id of timeouts.keys()) {
                const timeout = timeouts.get(id);
                if (timeout.isTimedOut) {
                    continue;
                }
                timeout.duration = Math.max(timeout.duration - duration, 0);
                if (timeout.duration === 0) {
                    timedOutIds.push(id);
                }
            }
            while (timedOutIds.length > 0) {
                const id = timedOutIds.shift();
                const timeout = timeouts.get(id);
                timeouts.delete(id);
                timeout.func();
                await nextTick();
            }
            await nextTick();
        },
    });
    return env;
}

return {
    addMessagingToEnv,
    addTimeControlToEnv,
};

});
