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

/**
 * Shared helpers for browser tests that exercise addon install dialogs
 * (e.g. WebMIDI, WebSerial, and the toolkit xpinstall doorhanger tests).
 */

"use strict";

/* exported waitForInstallDialog, alwaysAcceptAddonPostInstallDialogs, waitForNotification, getObserverTopic, waitForTick */

async function waitForInstallDialog(id = "addon-webext-permissions") {
  let panel = await waitForNotification(id);
  // NOTE: the panel may intermittently still be in the "showing" state, and
  // so we explicitly await for the state to become "open" before proceeding
  // with asserting the visibility of the elements we expected to be in the
  // panel.
  if (panel.state === "showing") {
    await TestUtils.waitForCondition(
      () => panel.state === "open",
      `Wait for ${id} panel state to become open`
    );
    is(panel.state, "open", "Panel.state should be open");
  }

  return panel.childNodes[0];
}

/**
 * Adds an event listener that will listen for post-install dialog event and automatically
 * close the dialogs.
 */
function alwaysAcceptAddonPostInstallDialogs() {
  // Once the addon is installed, a dialog is displayed as a confirmation.
  // This could interfere with tests running after this one, so we set up a listener
  // that will always accept post install dialogs so we don't have to deal with them in
  // the test.
  const abortController = new AbortController();

  const { AppMenuNotifications } = ChromeUtils.importESModule(
    "resource://gre/modules/AppMenuNotifications.sys.mjs"
  );
  info("Start listening and accept addon post-install notifications");
  PanelUI.notificationPanel.addEventListener(
    "popupshown",
    async function popupshown() {
      let notification = AppMenuNotifications.activeNotification;
      if (!notification || notification.id !== "addon-installed") {
        return;
      }

      let popupnotificationID = PanelUI._getPopupId(notification);
      if (popupnotificationID) {
        info("Accept post-install dialog");
        let popupnotification = document.getElementById(popupnotificationID);
        popupnotification?.button.click();
      }
    },
    {
      signal: abortController.signal,
    }
  );

  registerCleanupFunction(async () => {
    // Clear the listener at the end of the test file, to prevent it to stay
    // around when the same browser instance may be running other unrelated
    // test files.
    abortController.abort();
  });
}

async function waitForNotification(
  aId,
  aExpectedCount = 1,
  expectedAnchorID = "unified-extensions-button",
  win = window
) {
  const PROGRESS_NOTIFICATION = "addon-progress";
  info(`Waiting for ${aId} notification`);

  let topic = getObserverTopic(aId);

  let observerPromise;
  if (aId !== "addon-webext-permissions") {
    observerPromise = new Promise(resolve => {
      Services.obs.addObserver(function observer(aSubject, aTopic) {
        // Ignore the progress notification unless that is the notification we want
        if (
          aId != PROGRESS_NOTIFICATION &&
          aTopic == getObserverTopic(PROGRESS_NOTIFICATION)
        ) {
          return;
        }
        Services.obs.removeObserver(observer, topic);
        resolve();
      }, topic);
    });
  }

  let panelEventPromise = new Promise(resolve => {
    win.PopupNotifications.panel.addEventListener(
      "PanelUpdated",
      function eventListener(e) {
        // Skip notifications that are not the one that we are supposed to be looking for
        if (!e.detail.includes(aId)) {
          return;
        }
        win.PopupNotifications.panel.removeEventListener(
          "PanelUpdated",
          eventListener
        );
        resolve();
      }
    );
  });

  await observerPromise;
  await panelEventPromise;
  await waitForTick();

  info(`Saw a ${aId} notification`);
  ok(win.PopupNotifications.isPanelOpen, "Panel should be open");
  is(
    win.PopupNotifications.panel.childNodes.length,
    aExpectedCount,
    "Should be the right number of notifications"
  );
  if (win.PopupNotifications.panel.childNodes.length) {
    let nodes = Array.from(win.PopupNotifications.panel.childNodes);
    let notification = nodes.find(n => n.id == aId + "-notification");
    ok(notification, `Should have seen the ${aId} notification`);

    let n = win.PopupNotifications.getNotification(aId);
    is(
      n?.anchorElement?.id || n?.anchorElement?.parentElement?.id,
      expectedAnchorID,
      "expected the right anchor ID"
    );
  }
  await SimpleTest.promiseFocus(win.PopupNotifications.window);

  return win.PopupNotifications.panel;
}

function getObserverTopic(aNotificationId) {
  let topic = aNotificationId;
  if (topic == "xpinstall-disabled") {
    topic = "addon-install-disabled";
  } else if (topic == "addon-progress") {
    topic = "addon-install-started";
  } else if (topic == "addon-installed") {
    topic = "webextension-install-notify";
  } else if (topic == "addon-install-failed-blocklist") {
    topic = "addon-install-failed";
  }
  return topic;
}

function waitForTick() {
  return new Promise(resolve => executeSoon(resolve));
}
