"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _path = _interopRequireDefault(require("path"));
var _fs = require("fs");
var _util = require("util");
var _events = require("events");
var _types = require("../../types");
var _pool = _interopRequireDefault(require("./pool"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const copyFileAsync = (0, _util.promisify)(_fs.copyFile);
const mkdirAsync = (0, _util.promisify)(_fs.mkdir);
class Runner extends _events.EventEmitter {
  pools = {};
  tests = {};
  get isRunning() {
    return Object.values(this.pools).some(pool => pool.isRunning);
  }
  constructor(config) {
    super();
    this.failFast = config.failFast;
    this.screenDir = config.screenDir;
    this.reportDir = config.reportDir;
    this.browsers = Object.keys(config.browsers);
    this.browsers.map(browser => this.pools[browser] = new _pool.default(config, browser)).map(pool => pool.on('test', this.handlePoolMessage));
  }
  handlePoolMessage = message => {
    const {
      id,
      status,
      result
    } = message;
    const test = this.tests[id];
    if (!test) return;
    const {
      browser,
      testName,
      storyPath,
      storyId
    } = test;
    // TODO Handle 'retrying' status
    test.status = status == 'retrying' ? 'failed' : status;
    if (!result) {
      this.sendUpdate({
        tests: {
          [id]: {
            id,
            browser,
            testName,
            storyPath,
            status: test.status,
            storyId
          }
        }
      });
      return;
    }
    if (!test.results) {
      test.results = [];
    }
    test.results.push(result);
    this.sendUpdate({
      tests: {
        [id]: {
          id,
          browser,
          testName,
          storyPath,
          status: test.status,
          results: [result],
          storyId
        }
      }
    });
    if (this.failFast && status == 'failed') this.stop();
  };
  handlePoolStop = () => {
    if (!this.isRunning) {
      this.sendUpdate({
        isRunning: false
      });
      this.emit('stop');
    }
  };
  async init() {
    await Promise.all(Object.values(this.pools).map(pool => pool.init()));
  }
  updateTests(testsDiff) {
    const tests = {};
    const removedTests = [];
    Object.entries(testsDiff).forEach(([id, newTest]) => {
      const oldTest = this.tests[id];
      if (newTest) {
        if (oldTest) {
          this.tests[id] = {
            ...newTest,
            retries: oldTest.retries,
            results: oldTest.results,
            approved: oldTest.approved
          };
        } else this.tests[id] = newTest;

        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const {
          story,
          fn,
          ...restTest
        } = newTest;
        tests[id] = {
          ...restTest,
          status: 'unknown'
        };
      } else if (oldTest) {
        const {
          id,
          browser,
          testName,
          storyPath,
          storyId
        } = oldTest;
        removedTests.push({
          id,
          browser,
          testName,
          storyPath,
          storyId
        });
        delete this.tests[id];
      }
    });
    this.sendUpdate({
      tests,
      removedTests
    });
  }
  start(ids) {
    if (this.isRunning) return;
    const testsToStart = ids.map(id => this.tests[id]).filter(_types.isDefined).filter(test => !test.skip);
    if (testsToStart.length == 0) return;
    this.sendUpdate({
      isRunning: true,
      tests: testsToStart.reduce((update, {
        id,
        storyId,
        browser,
        testName,
        storyPath
      }) => ({
        ...update,
        [id]: {
          id,
          browser,
          testName,
          storyPath,
          status: 'pending',
          storyId
        }
      }), {})
    });
    const testsByBrowser = testsToStart.reduce((tests, test) => {
      const {
        id,
        browser,
        testName,
        storyPath
      } = test;
      const restPath = [...storyPath, testName].filter(_types.isDefined);
      test.status = 'pending';
      return {
        ...tests,
        [browser]: [...(tests[browser] || []), {
          id,
          path: restPath
        }]
      };
    }, {});
    this.browsers.forEach(browser => {
      const pool = this.pools[browser];
      const tests = testsByBrowser[browser];
      if (tests && tests.length > 0 && pool.start(tests)) {
        pool.once('stop', this.handlePoolStop);
      }
    });
  }
  stop() {
    if (!this.isRunning) return;
    this.browsers.forEach(browser => this.pools[browser].stop());
  }
  get status() {
    const tests = {};
    Object.values(this.tests).filter(_types.isDefined)
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    .forEach(({
      story,
      fn,
      ...test
    }) => tests[test.id] = test);
    return {
      isRunning: this.isRunning,
      tests,
      browsers: this.browsers
    };
  }
  async approve({
    id,
    retry,
    image
  }) {
    const test = this.tests[id];
    if (!test || !test.results) return;
    const result = test.results[retry];
    if (!result || !result.images) return;
    const images = result.images[image];
    if (!images) return;
    if (!test.approved) {
      test.approved = {};
    }
    const {
      browser,
      testName,
      storyPath
    } = test;
    const restPath = [...storyPath, testName].filter(_types.isDefined);
    const testPath = _path.default.join(...restPath, image == browser ? '' : browser);
    const srcImagePath = _path.default.join(this.reportDir, testPath, images.actual);
    const dstImagePath = _path.default.join(this.screenDir, testPath, `${image}.png`);
    await mkdirAsync(_path.default.join(this.screenDir, testPath), {
      recursive: true
    });
    await copyFileAsync(srcImagePath, dstImagePath);
    test.approved[image] = retry;
    this.sendUpdate({
      tests: {
        [id]: {
          id,
          browser,
          testName,
          storyPath,
          approved: {
            [image]: retry
          },
          storyId: test.storyId
        }
      }
    });
  }
  sendUpdate(data) {
    this.emit('update', data);
  }
}
exports.default = Runner;