"use strict";

/* exported MockAlertsService */

function mockServicesChromeScript() {
  /* eslint-env mozilla/chrome-script */

  const MOCK_ALERTS_CID = Components.ID(
    "{48068bc2-40ab-4904-8afd-4cdfb3a385f3}"
  );
  const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";

  const { setTimeout } = ChromeUtils.importESModule(
    "resource://gre/modules/Timer.sys.mjs"
  );
  const registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);

  let activeNotifications = Object.create(null);

  const mockAlertsService = {
    showPersistentNotification: function (
      persistentData,
      alert,
      alertListener
    ) {
      this.showAlert(alert, alertListener);
    },

    showAlert: function (alert, listener) {
      activeNotifications[alert.name] = {
        listener: listener,
        cookie: alert.cookie,
        title: alert.title,
      };

      // fake async alert show event
      if (listener) {
        setTimeout(function () {
          listener.observe(null, "alertshow", alert.cookie);
        }, 100);
      }
    },

    showAlertNotification: function (
      imageUrl,
      title,
      text,
      textClickable,
      cookie,
      alertListener,
      name
    ) {
      this.showAlert(
        {
          name: name,
          cookie: cookie,
          title: title,
        },
        alertListener
      );
    },

    closeAlert: function (name) {
      let alertNotification = activeNotifications[name];
      if (alertNotification) {
        if (alertNotification.listener) {
          alertNotification.listener.observe(
            null,
            "alertfinished",
            alertNotification.cookie
          );
        }
        delete activeNotifications[name];
      }
    },

    QueryInterface: ChromeUtils.generateQI(["nsIAlertsService"]),

    createInstance: function (iid) {
      return this.QueryInterface(iid);
    },
  };

  registrar.registerFactory(
    MOCK_ALERTS_CID,
    "alerts service",
    ALERTS_SERVICE_CONTRACT_ID,
    mockAlertsService
  );

  function clickNotifications(doClose) {
    // Until we need to close a specific notification, just click them all.
    for (let [name, notification] of Object.entries(activeNotifications)) {
      let { listener, cookie } = notification;
      listener.observe(null, "alertclickcallback", cookie);
      if (doClose) {
        mockAlertsService.closeAlert(name);
      }
    }
  }

  function closeAllNotifications() {
    for (let alertName of Object.keys(activeNotifications)) {
      mockAlertsService.closeAlert(alertName);
    }
  }

  const { addMessageListener, sendAsyncMessage } = this;

  addMessageListener("mock-alert-service:unregister", () => {
    closeAllNotifications();
    activeNotifications = null;
    registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService);
    sendAsyncMessage("mock-alert-service:unregistered");
  });

  addMessageListener(
    "mock-alert-service:click-notifications",
    clickNotifications
  );

  addMessageListener(
    "mock-alert-service:close-notifications",
    closeAllNotifications
  );

  sendAsyncMessage("mock-alert-service:registered");
}

const MockAlertsService = {
  async register() {
    if (this._chromeScript) {
      throw new Error("MockAlertsService already registered");
    }
    this._chromeScript = SpecialPowers.loadChromeScript(
      mockServicesChromeScript
    );
    await this._chromeScript.promiseOneMessage("mock-alert-service:registered");
  },
  async unregister() {
    if (!this._chromeScript) {
      throw new Error("MockAlertsService not registered");
    }
    this._chromeScript.sendAsyncMessage("mock-alert-service:unregister");
    return this._chromeScript
      .promiseOneMessage("mock-alert-service:unregistered")
      .then(() => {
        this._chromeScript.destroy();
        this._chromeScript = null;
      });
  },
  async clickNotifications() {
    // Most implementations of the nsIAlertsService automatically close upon click.
    await this._chromeScript.sendAsyncMessage(
      "mock-alert-service:click-notifications",
      true
    );
  },
  async clickNotificationsWithoutClose() {
    // The implementation on macOS does not automatically close the notification.
    await this._chromeScript.sendAsyncMessage(
      "mock-alert-service:click-notifications",
      false
    );
  },
  async closeNotifications() {
    await this._chromeScript.sendAsyncMessage(
      "mock-alert-service:close-notifications"
    );
  },
};
