const defaults = {
  delay: 100,
  timeout: 4000,
};

/**
 * Waits for specified function to resolve to a truthy value.
 *
 * @param {Function} fn function to be evaluated until truthy
 * @param {*} arg optional argument to pass to `fn`
 * @param {Object} options optional parameters
 * @returns {Promise} promise which resolves to function result
 */
export function waitForFunction(fn, arg, options = {}) {
  const settings = {
    ...defaults,
    ...options,
  };

  return new Promise((resolve, reject) => {
    let timeElapsed = 0;

    const int = setInterval(() => {
      let result;

      try {
        result = fn(arg);
      } catch (e) {
        // Continue...
      }

      if (result) {
        clearInterval(int);
        resolve(result);
      }

      timeElapsed += settings.delay;

      if (timeElapsed >= settings.timeout) {
        const msg = `waitForFunction did not return a truthy value (${
          settings.timeout
        } ms): ${fn.toString()}\n`;

        reject(msg);
      }
    }, settings.delay);
  });
}

/**
 * Waits for specified CSS selector to be located in the DOM
 *
 * @param {String} cssSelector CSS selector to query for
 * @param {Object} options optional parameters
 * @returns {Promise} promise which resolves to first matching element
 */
export function waitForSelector(cssSelector, options = {}) {
  const settings = {
    ...defaults,
    ...options,
  };

  return new Promise((resolve, reject) => {
    let timeElapsed = 0;

    const int = setInterval(() => {
      const elm = document.querySelector(cssSelector);
      if (elm) {
        clearInterval(int);
        resolve(elm);
      }

      timeElapsed += settings.delay;

      if (timeElapsed >= settings.timeout) {
        const msg = `waitForSelector did not match CSS selector '${cssSelector}' (${settings.timeout} ms)`;

        reject(msg);
      }
    }, settings.delay);
  });
}

/**
 * Waits for specified CSS selector to contain text content
 *
 * @param {String} cssSelector CSS selector to query for
 * @param {String} text text to match
 * @param {Object} options optional parameters
 * @returns {Promise} promise which resolves to first matching element that contains specified text
 */
export function waitForText(cssSelector, text, options = {}) {
  const settings = {
    ...defaults,
    ...options,
  };

  return new Promise((resolve, reject) => {
    let timeElapsed = 0;

    waitForSelector(cssSelector, settings)
      .then(elm => {
        const int = setInterval(() => {
          const isMatch = elm.textContent.includes(text);

          if (isMatch) {
            clearInterval(int);
            resolve(true);
          }

          timeElapsed += settings.delay;

          if (timeElapsed >= settings.timeout) {
            const msg = `waitForText did not find '${text}' in CSS selector '${cssSelector}' (${settings.timeout} ms): '${elm.textContent}'`;

            reject(msg);
          }
        }, settings.delay);
      })
      .catch(() => {
        const msg = `waitForText did not match CSS selector '${cssSelector}' (${settings.timeout} ms)`;

        reject(msg);
      });
  });
}
