/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

// Functions for extensions to use, so that we avoid repeating ourselves.

function assertDeepEqual(
  expected,
  actual,
  description = "Values should be equal",
  options = {}
) {
  let ok;
  let strict = !!options?.strict;
  try {
    ok = assertDeepEqualNested(expected, actual, strict);
  } catch (e) {
    ok = false;
  }
  if (!ok) {
    browser.test.fail(
      `Deep equal test. \n Expected value: ${JSON.stringify(
        expected
      )} \n Actual value: ${JSON.stringify(actual)},
      ${description}`
    );
  }
}

function assertDeepEqualNested(expected, actual, strict) {
  if (expected === null) {
    browser.test.assertTrue(actual === null);
    return actual === null;
  }

  if (expected === undefined) {
    browser.test.assertTrue(actual === undefined);
    return actual === undefined;
  }

  if (["boolean", "number", "string"].includes(typeof expected)) {
    browser.test.assertEq(typeof expected, typeof actual);
    browser.test.assertEq(expected, actual);
    return typeof expected == typeof actual && expected == actual;
  }

  if (Array.isArray(expected)) {
    browser.test.assertTrue(Array.isArray(actual));
    browser.test.assertEq(expected.length, actual.length);
    let ok = 0;
    let all = 0;
    for (let i = 0; i < expected.length; i++) {
      all++;
      if (assertDeepEqualNested(expected[i], actual[i], strict)) {
        ok++;
      }
    }
    return (
      Array.isArray(actual) && expected.length == actual.length && all == ok
    );
  }

  let expectedKeys = Object.keys(expected);
  let actualKeys = Object.keys(actual);
  // Ignore any extra keys on the actual object in non-strict mode (default).
  let lengthOk = strict
    ? expectedKeys.length == actualKeys.length
    : expectedKeys.length <= actualKeys.length;
  browser.test.assertTrue(lengthOk);

  let ok = 0;
  let all = 0;
  for (let key of expectedKeys) {
    all++;
    browser.test.assertTrue(actualKeys.includes(key), `Key ${key} exists`);
    if (assertDeepEqualNested(expected[key], actual[key], strict)) {
      ok++;
    }
  }
  return all == ok && lengthOk;
}

function waitForMessage() {
  return waitForEvent("test.onMessage");
}

function waitForEvent(eventName) {
  let [namespace, name] = eventName.split(".");
  return new Promise(resolve => {
    browser[namespace][name].addListener(function listener(...args) {
      browser[namespace][name].removeListener(listener);
      resolve(args);
    });
  });
}

async function waitForCondition(condition, msg, interval = 100, maxTries = 50) {
  let conditionPassed = false;
  let tries = 0;
  for (; tries < maxTries && !conditionPassed; tries++) {
    await new Promise(resolve =>
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      window.setTimeout(resolve, interval)
    );
    try {
      conditionPassed = await condition();
    } catch (e) {
      throw Error(`${msg} - threw exception: ${e}`);
    }
  }
  if (conditionPassed) {
    browser.test.succeed(
      `waitForCondition succeeded after ${tries} retries - ${msg}`
    );
  } else {
    browser.test.fail(`${msg} - timed out after ${maxTries} retries`);
  }
}

function sendMessage(...args) {
  let replyPromise = waitForMessage();
  browser.test.sendMessage(...args);
  return replyPromise;
}
