/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

ChromeUtils.defineESModuleGetters(this, {
  AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs",
  CustomizableUITestUtils:
    "resource://testing-common/CustomizableUITestUtils.sys.mjs",
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
  FormHistoryTestUtils:
    "resource://testing-common/FormHistoryTestUtils.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  SearchService: "moz-src:///toolkit/components/search/SearchService.sys.mjs",
  SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
  SearchUITestUtils: "resource://testing-common/SearchUITestUtils.sys.mjs",
  SearchUtils: "moz-src:///toolkit/components/search/SearchUtils.sys.mjs",
  sinon: "resource://testing-common/Sinon.sys.mjs",
  TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
  UrlbarSearchUtils:
    "moz-src:///browser/components/urlbar/UrlbarSearchUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
  const { UrlbarTestUtils: module } = ChromeUtils.importESModule(
    "resource://testing-common/UrlbarTestUtils.sys.mjs"
  );
  module.init(this);
  return module;
});

let gCUITestUtils = new CustomizableUITestUtils(window);

AddonTestUtils.initMochitest(this);
SearchTestUtils.init(this);
SearchUITestUtils.init(this);

/**
 * Recursively compare two objects and check that every property of expectedObj has the same value
 * on actualObj.
 *
 * @param {object} expectedObj
 *        The expected object to find.
 * @param {object} actualObj
 *        The object to inspect.
 * @param {string} name
 *        The name of the engine, used for test detail logging.
 */
function isSubObjectOf(expectedObj, actualObj, name) {
  for (let prop in expectedObj) {
    if (typeof expectedObj[prop] == "function") {
      continue;
    }
    if (expectedObj[prop] instanceof Object) {
      is(
        actualObj[prop].length,
        expectedObj[prop].length,
        name + "[" + prop + "]"
      );
      isSubObjectOf(
        expectedObj[prop],
        actualObj[prop],
        name + "[" + prop + "]"
      );
    } else {
      is(actualObj[prop], expectedObj[prop], name + "[" + prop + "]");
    }
  }
}

function getLocale() {
  return Services.locale.requestedLocale || undefined;
}

function promiseEvent(aTarget, aEventName, aPreventDefault) {
  function cancelEvent(event) {
    if (aPreventDefault) {
      event.preventDefault();
    }

    return true;
  }

  return BrowserTestUtils.waitForEvent(aTarget, aEventName, false, cancelEvent);
}

/**
 * Get an array of the one-off buttons.
 */
async function getOneOffs() {
  /** @type {Element[]} */
  let oneOffs = [];
  let searchPopup = document.getElementById("PopupSearchAutoComplete");
  if (searchPopup.oneOffButtons._rebuilding) {
    await promiseEvent(searchPopup.oneOffButtons, "rebuild");
  }
  let oneOffsContainer = searchPopup.searchOneOffsContainer;
  let oneOff = oneOffsContainer.querySelector(".search-panel-one-offs");

  for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
    if (oneOff.nodeType == Node.ELEMENT_NODE) {
      oneOffs.push(oneOff);
    }
  }
  return oneOffs;
}

async function typeInSearchField(browser, text, fieldName) {
  await SpecialPowers.spawn(
    browser,
    [[fieldName, text]],
    async function ([contentFieldName, contentText]) {
      // Put the focus on the search box.
      let searchInput = content.document.getElementById(contentFieldName);
      searchInput.focus();
      searchInput.value = contentText;
    }
  );
}

async function searchInSearchbar(
  inputText,
  win = window,
  popupAlreadyOpen = false
) {
  await new Promise(r => waitForFocus(r, win));
  let sb = win.document.getElementById("searchbar");
  // Write the search query in the searchbar.
  sb.focus();
  sb.value = inputText;
  sb.textbox.controller.startSearch(inputText);
  if (!popupAlreadyOpen) {
    // Wait for the popup to show.
    await BrowserTestUtils.waitForEvent(sb.textbox.popup, "popupshown");
  }
  // And then for the search to complete.
  await TestUtils.waitForCondition(
    () =>
      sb.textbox.controller.searchStatus >=
      Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH,
    "The search in the searchbar must complete."
  );
  return sb.textbox.popup;
}

function clearSearchbarHistory() {
  info("cleanup the search history");
  return FormHistory.update({ op: "remove", fieldname: "searchbar-history" });
}

registerCleanupFunction(async () => {
  await PlacesUtils.history.clear();
});

/**
 * Fills a text field ensuring to cause expected edit events.
 *
 * @param {string} id
 *        id of the text field
 * @param {string} text
 *        text to fill in
 * @param {object} win
 *        dialog window
 */
function fillTextField(id, text, win) {
  let elt = win.document.getElementById(id);
  elt.focus();
  elt.select();
  EventUtils.synthesizeKey("a", { metaKey: true }, win);
  EventUtils.synthesizeKey("KEY_Backspace", {}, win);

  for (let c of text.split("")) {
    EventUtils.synthesizeKey(c, {}, win);
  }
}

/**
 * Wait for the user's default search engine to change.
 *
 * @param {XULBrowser} browser
 * @param {Function} searchEngineChangeFn
 *   A function that is called to change the search engine.
 */
async function promiseContentSearchChange(browser, searchEngineChangeFn) {
  // Add an event listener manually then perform the action, rather than using
  // BrowserTestUtils.addContentEventListener as that doesn't add the listener
  // early enough.
  await SpecialPowers.spawn(browser, [], async () => {
    // Store the results in a temporary place.
    content._searchDetails = {
      defaultEnginesList: [],
      listener: event => {
        if (event.detail.type == "CurrentEngine") {
          content._searchDetails.defaultEnginesList.push(
            content.wrappedJSObject.gContentSearchController.defaultEngine.name
          );
        }
      },
    };

    // Listen using the system group to ensure that it fires after
    // the default behaviour.
    content.addEventListener(
      "ContentSearchService",
      content._searchDetails.listener,
      { mozSystemGroup: true }
    );
  });

  let expectedEngineName = await searchEngineChangeFn();

  await SpecialPowers.spawn(
    browser,
    [expectedEngineName],
    async expectedEngineNameChild => {
      await ContentTaskUtils.waitForCondition(
        () =>
          content._searchDetails.defaultEnginesList &&
          content._searchDetails.defaultEnginesList[
            content._searchDetails.defaultEnginesList.length - 1
          ] == expectedEngineNameChild,
        `Waiting for ${expectedEngineNameChild} to be set`
      );
      content.removeEventListener(
        "ContentSearchService",
        content._searchDetails.listener,
        { mozSystemGroup: true }
      );
      delete content._searchDetails;
    }
  );
}
