"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _cluster = _interopRequireWildcard(require("cluster"));
var _events = require("events");
var _types = require("../../types");
var _messages = require("../messages");
var _utils = require("../utils");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const FORK_RETRIES = 5;
class Pool extends _events.EventEmitter {
  workers = [];
  queue = [];
  forcedStop = false;
  get isRunning() {
    return this.workers.length !== this.freeWorkers.length;
  }
  constructor(config, browser) {
    super();
    this.browser = browser;
    this.failFast = config.failFast;
    this.maxRetries = config.maxRetries;
    this.config = config.browsers[browser];
  }
  async init() {
    const poolSize = this.config.limit || 1;
    this.workers = (await Promise.all(Array.from({
      length: poolSize
    }).map(() => this.forkWorker()))).filter(workerOrError => workerOrError instanceof _cluster.Worker);
    if (this.workers.length != poolSize) throw new Error(`Can't instantiate workers for ${this.browser} due many errors`);
    this.workers.forEach(worker => this.exitHandler(worker));
  }
  start(tests) {
    if (this.isRunning) return false;
    this.queue = tests.map(({
      id,
      path
    }) => ({
      id,
      path,
      retries: 0
    }));
    this.process();
    return true;
  }
  stop() {
    if (!this.isRunning) {
      this.emit('stop');
      return;
    }
    this.forcedStop = true;
    this.queue = [];
  }
  process() {
    const worker = this.getFreeWorker();
    const [test] = this.queue;
    if (this.queue.length == 0 && this.workers.length === this.freeWorkers.length) {
      this.forcedStop = false;
      this.emit('stop');
      return;
    }
    if (!worker || !test) return;
    worker.isRunning = true;
    const {
      id
    } = test;
    this.queue.shift();
    this.sendStatus({
      id,
      status: 'running'
    });
    this.subscribe(worker, test);
    (0, _messages.sendTestMessage)(worker, {
      type: 'start',
      payload: test
    });
    this.process();
  }
  sendStatus(message) {
    this.emit('test', message);
  }
  getFreeWorker() {
    return this.freeWorkers[Math.floor(Math.random() * this.freeWorkers.length)];
  }
  get aliveWorkers() {
    return this.workers.filter(worker => !worker.exitedAfterDisconnect);
  }
  get freeWorkers() {
    return this.aliveWorkers.filter(worker => !worker.isRunning);
  }
  async forkWorker(retry = 0) {
    _cluster.default.setupPrimary({
      args: ['--browser', this.browser, ...process.argv.slice(2)]
    });
    const worker = _cluster.default.fork();
    const message = await new Promise(resolve => {
      const readyHandler = message => {
        if (!(0, _types.isWorkerMessage)(message)) return;
        worker.off('message', readyHandler);
        resolve(message);
      };
      worker.on('message', readyHandler);
    });
    if (message.type != 'error') return worker;
    this.gracefullyKill(worker);
    if (retry == FORK_RETRIES) return message.payload;
    return this.forkWorker(retry + 1);
  }
  exitHandler(worker) {
    // eslint-disable-next-line @typescript-eslint/no-misused-promises
    worker.once('exit', async () => {
      if (_utils.isShuttingDown.current) return;
      const workerOrError = await this.forkWorker();
      if (!(workerOrError instanceof _cluster.Worker)) throw new Error(`Can't instantiate worker for ${this.browser} due many errors`);
      this.exitHandler(workerOrError);
      this.workers[this.workers.indexOf(worker)] = workerOrError;
      this.process();
    });
  }
  gracefullyKill(worker) {
    const timeout = setTimeout(() => worker.kill(), 10000);
    worker.on('exit', () => clearTimeout(timeout));
    (0, _messages.sendShutdownMessage)(worker);
  }
  shouldRetry(test) {
    return test.retries < this.maxRetries && !this.forcedStop;
  }
  handleTestResult(worker, test, result) {
    const shouldRetry = result.status == 'failed' && this.shouldRetry(test);
    if (shouldRetry) {
      test.retries += 1;
      this.queue[this.failFast ? 'unshift' : 'push'](test);
    }
    this.sendStatus({
      id: test.id,
      status: shouldRetry ? 'retrying' : result.status,
      result
    });
    worker.isRunning = false;
    setImmediate(() => this.process());
  }
  subscribe(worker, test) {
    const subscriptions = [(0, _messages.subscribeOnWorker)(worker, 'worker', message => {
      if (message.type != 'error') return;
      subscriptions.forEach(unsubscribe => unsubscribe());
      this.gracefullyKill(worker);
      this.handleTestResult(worker, test, {
        status: 'failed',
        ...message.payload
      });
    }), (0, _messages.subscribeOnWorker)(worker, 'test', message => {
      if (message.type != 'end') return;
      subscriptions.forEach(unsubscribe => unsubscribe());
      this.handleTestResult(worker, test, message.payload);
    })];
  }
}
exports.default = Pool;