import { Browser, Capability, WebDriver } from 'selenium-webdriver';
import { isNoRequireJet } from './no-require-jet';

export const TEST_OBJ_NAME = '__ojwebdrivertest_proxy';

let _BROWSER_NAME: string;

/**
 * Run a remote script on the browser utilizing the requested modules. If JET runtime modules are
 * needed, test adapters should only use this script to access them. Never do direct imports/requires
 * in remote scripts.
 *
 * Only modules made available by ojs/ojwebdrivertest-proxy may be used.
 *
 * @param {WebDriver} driver
 * @param {string[]} moduleNames An array of strings of the modules to be made available
 * @param {any[]} scriptArgs An array of arguments to pass to the script. Pass blank array if no args
 * @param {string} script The script to be executed. The script should define a function that takes the
 * arguments containg: each requested module as separate parameters, and each script argument as
 * separate parameters after the modules.
 * @return {Promise<T>} Anything returned from the script
 * @template T
 */
export async function executeWithModules<T>(
  driver: WebDriver,
  moduleNames: string[],
  scriptArgs: any[] = [],
  script: string
) {
  const testProxyScript = getTestProxyScript();
  const hasProxy = !(await driver.executeAsyncScript(testProxyScript));
  if (!hasProxy) {
    throw new Error('executeWithModules called on a non-JET page');
  }
  if (!_BROWSER_NAME) {
    const caps = await driver.getCapabilities();
    _BROWSER_NAME = caps.get(Capability.BROWSER_NAME);
  }
  if (_BROWSER_NAME === Browser.FIREFOX) {
    return _executeWithModulesFirefox<T>(driver, moduleNames, scriptArgs, script);
  }

  const s = `
    const args = Array.from(arguments);

    console.log('${TEST_OBJ_NAME} importing modules ${moduleNames}');
    return ${TEST_OBJ_NAME}.getProxy(...${JSON.stringify(moduleNames)}).then((modules) => {
      console.log('${TEST_OBJ_NAME} running script');
      return (${script})(...modules.concat(args));
    })
  `;
  return driver.executeScript<T>(s, ...scriptArgs);
}

/**
 * Workaround https://github.com/mozilla/geckodriver/issues/1701 where properties defined via a getter are not returned
 * a) via executeAsyncScript
 * b) via a Promise returned from executeScript
 *
 * @param driver
 * @param moduleNames
 * @param scriptArgs
 * @param script
 * @returns
 */
async function _executeWithModulesFirefox<T>(
  driver: WebDriver,
  moduleNames: string[],
  scriptArgs: any[] = [],
  script: string
) {
  const s1 = `
    console.log('${TEST_OBJ_NAME} importing modules ${moduleNames}');
    // We don't actually need the returned module values here, we just want to
    // trigger the imports.  Throw away the return values so that we don't run
    // into problems trying to encode them
    return ${TEST_OBJ_NAME}.getProxy(...${JSON.stringify(moduleNames)}).then(() => {});
  `;
  await driver.executeScript<T>(s1);
  const s2 = `
    const args = Array.from(arguments);
    const modules = ${TEST_OBJ_NAME}.getCachedModules(...${JSON.stringify(moduleNames)});
    console.log('${TEST_OBJ_NAME} running script');
    return (${script})(...modules.concat(args));
  `;
  return driver.executeScript<T>(s2, ...scriptArgs);
}

function getTestProxyScript() {
  if (isNoRequireJet()) {
    return `
      const done = arguments[0];
      (function pollForProxy() {
        if ('${TEST_OBJ_NAME}' in window) {
          done();
        }
        setTimeout(pollForProxy, 100);
      })();
    `;
  } else {
    return `
      const done = arguments[0];
      function success() {
        done();
      }
      function failure() {
        done('Unable to load ojs/ojcore-base via RequireJS');
      }
      if (typeof require === 'function') {
        // Try sync require first
        try {
          require('ojs/ojcore-base');
          success();
        } catch (ex) {
          require(['ojs/ojcore-base'], success, failure);
          return;
        }
      } else {
        failure();
      }
    `;
  }
}
