"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = worker;
var _util = require("util");
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _chai = _interopRequireDefault(require("chai"));
var _chalk = _interopRequireDefault(require("chalk"));
var _mocha = _interopRequireDefault(require("mocha"));
var _seleniumWebdriver = require("selenium-webdriver");
var _types = require("../../types");
var _messages = require("../messages");
var _chaiImage = _interopRequireDefault(require("./chai-image"));
var _selenium = require("../selenium");
var _reporter = require("./reporter");
var _helpers = require("./helpers");
var _logger = require("../logger");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const statAsync = (0, _util.promisify)(_fs.default.stat);
const readdirAsync = (0, _util.promisify)(_fs.default.readdir);
const readFileAsync = (0, _util.promisify)(_fs.default.readFile);
const writeFileAsync = (0, _util.promisify)(_fs.default.writeFile);
const mkdirAsync = (0, _util.promisify)(_fs.default.mkdir);
async function getStat(filePath) {
  try {
    return await statAsync(filePath);
  } catch (error) {
    if (typeof error == 'object' && error && error.code === 'ENOENT') {
      return null;
    }
    throw error;
  }
}
async function getLastImageNumber(imageDir, imageName) {
  const actualImagesRegexp = new RegExp(`${imageName}-actual-(\\d+)\\.png`);
  try {
    return (await readdirAsync(imageDir)).map(filename => filename.replace(actualImagesRegexp, '$1')).map(Number).filter(x => !isNaN(x)).sort((a, b) => b - a)[0] ?? 0;
  } catch (_error) {
    return 0;
  }
}

// FIXME browser options hotfix
async function worker(config, options) {
  var _await$browser$getSes;
  let retries = 0;
  let images = {};
  let error = undefined;
  const screenshots = [];
  const testScope = [];
  function runHandler(failures) {
    if (failures > 0 && (error || Object.values(images).some(image => (image === null || image === void 0 ? void 0 : image.error) != null))) {
      const isTimeout = hasTimeout(error) || Object.values(images).some(image => hasTimeout(image === null || image === void 0 ? void 0 : image.error));
      const payload = {
        status: 'failed',
        images,
        error
      };
      isTimeout ? (0, _messages.emitWorkerMessage)({
        type: 'error',
        payload: {
          error: error ?? 'Unknown error'
        }
      }) : (0, _messages.emitTestMessage)({
        type: 'end',
        payload
      });
    } else {
      (0, _messages.emitTestMessage)({
        type: 'end',
        payload: {
          status: 'success',
          images
        }
      });
    }
  }
  async function saveImages(imageDir, images) {
    await mkdirAsync(imageDir, {
      recursive: true
    });
    for (const {
      name,
      data
    } of images) {
      await writeFileAsync(_path.default.join(imageDir, name), data);
    }
  }
  async function getExpected(assertImageName) {
    // context => [kind, story, test, browser]
    // rootSuite -> kindSuite -> storyTest -> [browsers.png]
    // rootSuite -> kindSuite -> storySuite -> test -> [browsers.png]
    const testPath = [...testScope];
    const imageName = assertImageName ?? testPath.pop();
    const imagesMeta = [];
    const reportImageDir = _path.default.join(config.reportDir, ...testPath);
    const imageNumber = (await getLastImageNumber(reportImageDir, imageName)) + 1;
    const actualImageName = `${imageName}-actual-${imageNumber}.png`;
    const image = images[imageName] = images[imageName] ?? {
      actual: actualImageName
    };
    const onCompare = async (actual, expect, diff) => {
      imagesMeta.push({
        name: image.actual,
        data: actual
      });
      if (diff && expect) {
        image.expect = `${imageName}-expect-${imageNumber}.png`;
        image.diff = `${imageName}-diff-${imageNumber}.png`;
        imagesMeta.push({
          name: image.expect,
          data: expect
        });
        imagesMeta.push({
          name: image.diff,
          data: diff
        });
      }
      if (options.saveReport) {
        await saveImages(reportImageDir, imagesMeta);
      }
    };
    const expectImageDir = _path.default.join(config.screenDir, ...testPath);
    const expectImageStat = await getStat(_path.default.join(expectImageDir, `${imageName}.png`));
    if (!expectImageStat) return {
      expected: null,
      onCompare
    };
    const expected = await readFileAsync(_path.default.join(expectImageDir, `${imageName}.png`));
    return {
      expected,
      onCompare
    };
  }
  const mochaOptions = {
    timeout: 30000,
    reporter: process.env.TEAMCITY_VERSION ? _reporter.TeamcityReporter : options.reporter || _reporter.CreeveyReporter,
    reporterOptions: {
      reportDir: config.reportDir,
      topLevelSuite: options.browser,
      get willRetry() {
        return retries < config.maxRetries;
      },
      get images() {
        return images;
      },
      get sessionId() {
        return sessionId;
      }
    }
  };
  const mocha = new _mocha.default(mochaOptions);
  mocha.cleanReferencesAfterRun(false);
  _chai.default.use((0, _chaiImage.default)(getExpected, config.diffOptions));
  if ((await (0, _selenium.getBrowser)(config, options)) == null) return;
  await (0, _helpers.addTestsFromStories)(mocha.suite, config, {
    browser: options.browser,
    watch: options.ui,
    debug: options.debug,
    port: options.port
  });
  try {
    var _await$getBrowser;
    await ((_await$getBrowser = await (0, _selenium.getBrowser)(config, options)) === null || _await$getBrowser === void 0 ? void 0 : _await$getBrowser.getCurrentUrl());
  } catch (_) {
    await (0, _selenium.closeBrowser)();
  }
  const browser = await (0, _selenium.getBrowser)(config, options);
  const sessionId = (_await$browser$getSes = await (browser === null || browser === void 0 ? void 0 : browser.getSession())) === null || _await$browser$getSes === void 0 ? void 0 : _await$browser$getSes.getId();
  if (browser == null) return;
  const interval = setInterval(() => void browser.getCurrentUrl().then(url => {
    if (options.debug) _logger.logger.debug(`${options.browser}:${_chalk.default.gray(sessionId)}`, 'current url', _chalk.default.magenta(url));
  }), 10 * 1000);
  (0, _messages.subscribeOn)('shutdown', () => clearInterval(interval));
  mocha.suite.beforeAll(function () {
    this.config = config;
    this.browser = browser;
    this.until = _seleniumWebdriver.until;
    this.keys = _seleniumWebdriver.Key;
    this.expect = _chai.default.expect;
    this.browserName = options.browser;
    this.testScope = testScope;
    this.screenshots = screenshots;
  });
  mocha.suite.beforeEach(_selenium.switchStory);
  (0, _messages.subscribeOn)('test', message => {
    if (message.type != 'start') return;
    const test = message.payload;
    const testPath = test.path.join(' ').replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&');
    images = {};
    error = undefined;
    retries = test.retries;
    mocha.grep(new RegExp(`^${testPath}$`));
    const runner = mocha.run(runHandler);

    // TODO How handle browser corruption?
    runner.on('fail', (_test, reason) => {
      if (!(reason instanceof Error)) {
        error = reason;
      } else if (!(0, _types.isImageError)(reason)) {
        error = reason.stack ?? reason.message;
      } else if (typeof reason.images == 'string') {
        const image = images[testScope.slice(-1)[0]];
        if (image) image.error = reason.images;
      } else {
        const imageErrors = reason.images;
        Object.keys(imageErrors).forEach(imageName => {
          const image = images[imageName];
          if (image) image.error = imageErrors[imageName];
        });
      }
    });
  });
  _logger.logger.info(`${options.browser}:${_chalk.default.gray(sessionId)} is ready`);
  (0, _messages.emitWorkerMessage)({
    type: 'ready'
  });
}
function hasTimeout(str) {
  return str != null && str.toLowerCase().includes('timeout');
}