/* 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/. */

import * as folderDisplayHelper from "resource://testing-common/mail/FolderDisplayHelpers.sys.mjs";

import * as wh from "resource://testing-common/mail/WindowHelpers.sys.mjs";

import { Assert } from "resource://testing-common/Assert.sys.mjs";
import { TestUtils } from "resource://testing-common/TestUtils.sys.mjs";

var FAST_TIMEOUT = 1000;
var FAST_INTERVAL = 100;

var mc = folderDisplayHelper.mc;

/**
 * Opens a content tab with the given URL.
 *
 * @param {string} aURL - The URL to load.
 * @param {string} [aLinkHandler=null] - See specialTabs.contentTabType.openTab.
 * @param {boolean} [aBackground=false] Whether the tab is opened in the background.
 *
 * @returns {object} The newly-opened tab.
 */
export async function open_content_tab_with_url(
  aURL,
  aLinkHandler = null,
  aBackground = false
) {
  const tabmail = mc.document.getElementById("tabmail");
  const preCount = tabmail.tabContainer.allTabs.length;
  tabmail.openTab("contentTab", {
    url: aURL,
    background: aBackground,
    linkHandler: aLinkHandler,
  });
  await TestUtils.waitForCondition(
    () => tabmail.tabContainer.allTabs.length == preCount + 1,
    "Timeout waiting for the content tab to open with URL: " + aURL,
    FAST_TIMEOUT,
    FAST_INTERVAL
  );

  // We append new tabs at the end, so check the last one.
  const expectedNewTab = tabmail.tabInfo[preCount];
  folderDisplayHelper.assert_selected_tab(expectedNewTab);
  await promise_content_tab_load(expectedNewTab, aURL);
  return expectedNewTab;
}

/**
 * Opens a content tab with a click on the given element. The tab is expected to
 * be opened in the foreground.
 *
 * @param {Element} aElem - The element to click or a function that causes the tab to open.
 * @param {string} aExpectedURL - The URL that is expected to be opened.
 * @param {string} [aTabType] - Optional tab type to expect.
 * @returns {TabInfo} The newly-opened tab.
 */
export async function open_content_tab_with_click(
  aElem,
  aExpectedURL,
  aTabType = "contentTab"
) {
  const preCount =
    mc.document.getElementById("tabmail").tabContainer.allTabs.length;
  if (typeof aElem != "function") {
    EventUtils.synthesizeMouseAtCenter(aElem, {}, aElem.ownerGlobal);
  } else {
    aElem();
  }

  await TestUtils.waitForCondition(
    () =>
      mc.document.getElementById("tabmail").tabContainer.allTabs.length ==
      preCount + 1,
    "Timeout waiting for the content tab to open",
    FAST_TIMEOUT,
    FAST_INTERVAL
  );

  // We append new tabs at the end, so check the last one.
  const expectedNewTab =
    mc.document.getElementById("tabmail").tabInfo[preCount];
  folderDisplayHelper.assert_selected_tab(expectedNewTab);
  folderDisplayHelper.assert_tab_mode_name(expectedNewTab, aTabType);
  await promise_content_tab_load(expectedNewTab, aExpectedURL);
  return expectedNewTab;
}

/**
 * Call this before triggering a page load that you are going to wait for using
 * |promise_content_tab_load|. This ensures that if a page is already displayed
 * in the given tab that state is sufficiently cleaned up so it doesn't trick us
 * into thinking that there is no need to wait.
 *
 * @param [aTab] optional tab, defaulting to the current tab.
 */
export function plan_for_content_tab_load(aTab) {
  if (aTab === undefined) {
    aTab = mc.document.getElementById("tabmail").currentTabInfo;
  }
  aTab.pageLoaded = false;
}

/**
 * Waits for the given content tab to load completely with the given URL. This
 * is expected to be accompanied by a |plan_for_content_tab_load| right before
 * the action triggering the page load takes place.
 *
 * Note that you cannot call |plan_for_content_tab_load| if you're opening a new
 * tab. That is fine, because pageLoaded is initially false.
 *
 * @param [aTab]      Optional tab, defaulting to the current tab.
 * @param aURL        The URL being loaded in the tab.
 * @param [aTimeout]  Optional time to wait for the load.
 */
export async function promise_content_tab_load(aTab, aURL, aTimeout) {
  if (aTab === undefined) {
    aTab = mc.document.getElementById("tabmail").currentTabInfo;
  }

  function isLoadedChecker() {
    // Require that the progress listener think that the page is loaded.
    if (!aTab.pageLoaded) {
      return false;
    }
    // Also require that our tab infrastructure thinks that the page is loaded.
    return !aTab.busy;
  }

  await TestUtils.waitForCondition(
    isLoadedChecker,
    "Timeout waiting for the content tab page to load.",
    aTimeout
  );
  // The above may return immediately, meaning the event queue might not get a
  // chance. Give it a chance now.
  await TestUtils.waitForTick();
  // Finally, require that the tab's browser thinks that no page is being loaded.
  await wh.wait_for_browser_load(aTab.browser, aURL);
}

/**
 * Gets the element with the given ID from the content tab's displayed page.
 */
export function content_tab_e(aTab, aId) {
  return aTab.browser.contentDocument.getElementById(aId);
}

/**
 * Assert that the given content tab has the given URL loaded as a favicon.
 */
export function assert_content_tab_has_favicon(aTab, aURL) {
  Assert.equal(aTab.favIconUrl, aURL, "Checking tab favicon");
}

/**
 * Returns the current "display" style property of an element.
 */
export function get_content_tab_element_display(aTab, aElem) {
  const style = aTab.browser.contentWindow.getComputedStyle(aElem);
  return style.getPropertyValue("display");
}

/**
 * Asserts that the given element is hidden from view on the page.
 */
export function assert_content_tab_element_hidden(aTab, aElem) {
  const display = get_content_tab_element_display(aTab, aElem);
  Assert.equal(display, "none", "Element should be hidden");
}

/**
 * Asserts that the given element is visible on the page.
 */
export function assert_content_tab_element_visible(aTab, aElem) {
  const display = get_content_tab_element_display(aTab, aElem);
  Assert.notEqual(display, "none", "Element should be visible");
}

/**
 * Waits for the element's display property indicate it is visible.
 */
export async function promise_content_tab_element_display(aTab, aElem) {
  await TestUtils.waitForCondition(
    () => get_content_tab_element_display(aTab, aElem) != "none",
    "waiting for element to become visible"
  );
}

/**
 * Finds element in document fragment, containing only the specified text
 * as its textContent value.
 *
 * @param aRootNode  Root node of the node tree where search should start.
 * @param aText      The string to search.
 */
export function get_element_by_text(aRootNode, aText) {
  // Check every node existing.
  const nodes = aRootNode.querySelectorAll("*");
  for (const node of nodes) {
    // We ignore surrounding whitespace.
    if (node.textContent.trim() == aText) {
      return node;
    }
  }

  return null;
}

/**
 * Finds element containing only the specified text in the content tab's page.
 */
function get_content_tab_element_by_text(aTab, aText) {
  const doc = aTab.browser.contentDocument.documentElement;
  return get_element_by_text(doc, aText);
}

/**
 * Asserts that the given text is present on the content tab's page.
 */
export function assert_content_tab_text_present(aTab, aText) {
  Assert.ok(
    get_content_tab_element_by_text(aTab, aText),
    `String "${aText}" should be on the content tab's page`
  );
}

/**
 * Asserts that the given text is absent on the content tab's page.
 */
export function assert_content_tab_text_absent(aTab, aText) {
  Assert.ok(
    !get_content_tab_element_by_text(aTab, aText),
    `String "${aText}" should not be on the content tab's page`
  );
}

/**
 * Returns the notification bar for a tab if one is currently visible,
 * null if otherwise.
 */
export function get_notification_bar_for_tab() {
  const notificationBoxEls = mc.document
    .getElementById("tabmail")
    .selectedTab.panel.querySelector("notificationbox");
  if (!notificationBoxEls) {
    return null;
  }

  return notificationBoxEls;
}
