"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeRender = void 0;
const logger_1 = require("@applitools/logger");
const throat_1 = __importDefault(require("throat"));
const utils = __importStar(require("@applitools/utils"));
function makeRender(_a) {
    var _b;
    var { requests, concurrency = (_b = utils.general.getEnvValue('CONCURRENT_RENDERS_PER_TEST', 'number')) !== null && _b !== void 0 ? _b : 1, timeout = 60 * 60 * 1000, batchingTimeout = 300, pollingTimeout = 500, logger: mainLogger, } = _a;
    const startRenderWithBatching = utils.general.batchify(startRenders, { timeout: batchingTimeout });
    const checkRenderResultWithBatching = utils.general.batchify(checkRenderResults, { timeout: batchingTimeout });
    const throttles = new Map();
    const renderWithConcurrency = utils.general.wrap(render, (render, options) => {
        let throttle = throttles.get(options.settings.environmentId);
        if (!throttle)
            throttles.set(options.settings.environmentId, (throttle = (0, throat_1.default)(concurrency)));
        return throttle(render, options);
    });
    return renderWithConcurrency;
    async function render({ target, settings, signal, logger = mainLogger, }) {
        logger = logger.extend(mainLogger, { tags: [`render-${utils.general.shortid()}`] });
        const timedOutAt = Date.now() + timeout;
        const render = await startRenderWithBatching({ target, settings, logger });
        return checkRenderResultWithBatching({ render, signal, timedOutAt, logger });
    }
    async function startRenders(batch) {
        const logger = (0, logger_1.mergeLoggers)(...batch.map(([{ logger }]) => logger));
        try {
            const renders = await requests.startRenders({ requests: batch.map(([request]) => request), logger });
            renders.forEach((render, index) => {
                const [, { resolve, reject }] = batch[index];
                if (render.status === 'need-more-resources') {
                    logger.error(`Got unexpected status ${render.status} in start render response`);
                    reject(new Error(`Got unexpected status ${render.status} in start render response`));
                }
                else {
                    resolve(render);
                }
            });
        }
        catch (err) {
            batch.forEach(([, { reject }]) => reject(err));
        }
    }
    async function checkRenderResults(batch) {
        const logger = (0, logger_1.mergeLoggers)(...batch.map(([{ logger }]) => logger));
        try {
            batch = batch.filter(([{ render, signal, timedOutAt }, { reject }]) => {
                if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
                    logger.warn(`Render with id "${render.renderId}" aborted`);
                    reject(new Error(`Render with id "${render.renderId}" aborted`));
                    return false;
                }
                else if (Date.now() >= timedOutAt) {
                    logger.error(`Render with id "${render.renderId}" timed out`);
                    reject(new Error(`Render with id "${render.renderId}" timed out`));
                    return false;
                }
                else {
                    return true;
                }
            });
            const results = await requests.checkRenderResults({ renders: batch.map(([{ render }]) => render), logger });
            results.forEach(async (result, index) => {
                const [options, { resolve, reject }] = batch[index];
                if (result.status === 'error') {
                    logger.error(`Render with id "${options.render.renderId}" failed due to an error - ${result.error}`);
                    reject(new Error(`Render with id "${options.render.renderId}" failed due to an error - ${result.error}`));
                }
                else if (result.status === 'rendered') {
                    resolve(result);
                }
                else {
                    // NOTE: this may create a long promise chain
                    await utils.general.sleep(pollingTimeout);
                    checkRenderResultWithBatching(options).then(resolve, reject);
                }
            });
        }
        catch (err) {
            batch.forEach(([, { reject }]) => reject(err));
        }
    }
}
exports.makeRender = makeRender;
