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

/**
 * Shared functions generally available for tests involving PanelMultiView and
 * the CustomizableUI elements in the browser window.
 */

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

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

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CustomizableUI:
    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
});

export class CustomizableUITestUtils {
  /**
   * Constructs an instance that operates with the specified browser window.
   */
  constructor(window) {
    this.window = window;
    this.document = window.document;
    this.PanelUI = window.PanelUI;
  }

  /**
   * Opens a closed PanelMultiView via the specified function while waiting for
   * the main view with the specified ID to become fully interactive.
   */
  async openPanelMultiView(panel, mainView, openFn) {
    if (panel.state == "open") {
      // Some tests may intermittently leave the panel open. We report this, but
      // don't fail so we don't introduce new intermittent test failures.
      Assert.ok(
        true,
        "A previous test left the panel open. This should be" +
          " fixed, but we can still do a best-effort recovery and" +
          " assume that the requested view will be made visible."
      );
      await openFn();
      return;
    }

    if (panel.state == "hiding") {
      // There may still be tests that don't wait after invoking a command that
      // causes the main menu panel to close. Depending on timing, the panel may
      // or may not be fully closed when the following test runs. We handle this
      // case gracefully so we don't risk introducing new intermittent test
      // failures that may show up at a later time.
      Assert.ok(
        true,
        "A previous test requested the panel to close but" +
          " didn't wait for the operation to complete. While" +
          " the test should be fixed, we can still continue."
      );
    } else {
      Assert.equal(panel.state, "closed", "The panel is closed to begin with.");
    }

    let promiseShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
    await openFn();
    await promiseShown;
  }

  /**
   * Closes an open PanelMultiView via the specified function while waiting for
   * the operation to complete.
   */
  async hidePanelMultiView(panel, closeFn) {
    Assert.ok(panel.state == "open", "The panel is open to begin with.");

    let promiseHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
    await closeFn();
    await promiseHidden;
  }

  /**
   * Opens the main menu and waits for it to become fully interactive.
   */
  async openMainMenu() {
    await this.openPanelMultiView(
      this.PanelUI.panel,
      this.PanelUI.mainView,
      () => this.PanelUI.show()
    );
  }

  /**
   * Closes the main menu and waits for the operation to complete.
   */
  async hideMainMenu() {
    await this.hidePanelMultiView(this.PanelUI.panel, () =>
      this.PanelUI.hide()
    );
  }

  /**
   * Add the search bar into the nav bar and verify it does not overflow.
   *
   * @returns {Promise}
   * @resolves The search bar element.
   * @rejects If search bar is not found, or overflows.
   */
  async addSearchBar() {
    lazy.CustomizableUI.addWidgetToArea(
      "search-container",
      lazy.CustomizableUI.AREA_NAVBAR,
      lazy.CustomizableUI.getPlacementOfWidget("urlbar-container").position + 1
    );

    // addWidgetToArea adds the search bar into the nav bar first.  If the
    // search bar overflows, OverflowableToolbar for the nav bar moves the
    // search bar into the overflow panel in its overflow event handler
    // asynchronously.
    //
    // We should first wait for the layout flush to make sure either the search
    // bar fits into the nav bar, or overflow event gets dispatched and the
    // overflow event handler is called.
    await this.window.promiseDocumentFlushed(() => {});

    // Check if the OverflowableToolbar is handling the overflow event.
    let navbar = this.window.document.getElementById(
      lazy.CustomizableUI.AREA_NAVBAR
    );
    await TestUtils.waitForCondition(() => {
      return !navbar.overflowable.isHandlingOverflow();
    });

    let searchbar = this.window.document.getElementById(
      Services.prefs.getBoolPref("browser.search.widget.new")
        ? "searchbar-new"
        : "searchbar"
    );
    if (!searchbar) {
      throw new Error("The search bar should exist.");
    }

    // If the search bar overflows, it's placed inside the overflow panel.
    //
    // We cannot use navbar's property to check if overflow happens, since it
    // can be different widget than the search bar that overflows.
    if (searchbar.closest("#widget-overflow")) {
      throw new Error(
        "The search bar should not overflow from the nav bar. " +
          "This test fails if the screen resolution is small and " +
          "the search bar overflows from the nav bar."
      );
    }

    return searchbar;
  }

  removeSearchBar() {
    lazy.CustomizableUI.removeWidgetFromArea("search-container");
  }
}
