(function (exports) {

  // Import some things we need.
  var { ExtensionCommon } = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs");
  var { MsgHdrToMimeMessage } = ChromeUtils.importESModule("resource:///modules/gloda/MimeMessage.sys.mjs");

  var tracker;

  class Tracker {
    constructor(extension) {
      this.extension = extension;
      this.windowTracker = new Set();
      this.onClickedListener = new ExtensionCommon.EventEmitter();

      this.notifyOnClickedListener = (event) => {
        let window = event.target.ownerGlobal.top;
        let tabmail = window.document.getElementById("tabmail");
        // for standalone message windows "tabmail" = null
        // therefore we must then use "window" to get "nativeTab"
        let nativeTab = tabmail ? tabmail.currentTabInfo : window;

        let tabId = this.extension.tabManager.getWrapper(nativeTab).id;
        this.onClickedListener.emit("remoteContentForMsg-clicked", tabId);
      }
    }

    addOnClickedListener(callback) {
      this.onClickedListener.on("remoteContentForMsg-clicked", callback);
    }

    removeOnClickedListener(callback) {
      this.onClickedListener.off("remoteContentForMsg-clicked", callback);
    }

    trackWindow(window) {
      this.windowTracker.add(window);
    }

    untrackWindow(window) {
      this.windowTracker.delete(window);
    }

    get trackedWindows() {
      return Array.from(this.windowTracker);
    }

  }

  class allowHtmlTemp extends ExtensionCommon.ExtensionAPI {
    // Alternative to defining a constructor here in order to init the class, is
    // to use the onStartup event. However, this causes the API to be instantiated
    // directly after the add-on has been loaded, not when the API is first used.
    constructor(extension) {
      // The only parameter is extension, but it could change in the future.
      // super() will add the extension as a member of this.
      super(extension);

      tracker = new Tracker(this.extension);
    }

    // The API implementation.
    getAPI(context) {

      function getMessageWindow(tabId) {
        // Get about:message from the tabId.
        let { nativeTab } = context.extension.tabManager.get(tabId);
        if (nativeTab instanceof Ci.nsIDOMWindow) {
          return nativeTab.messageBrowser.contentWindow
        } else if (nativeTab.mode && nativeTab.mode.name == "mail3PaneTab") {
          return nativeTab.chromeBrowser.contentWindow.messageBrowser.contentWindow
        } else if (nativeTab.mode && nativeTab.mode.name == "mailMessageTab") {
          return nativeTab.chromeBrowser.contentWindow;
        }
        return null;
      }

      return {
        // Again, this key must have the same name.
        allowHtmlTemp: {

          // An event. Most of this is boilerplate you don't need to worry about, just copy it.
          onClick: new ExtensionCommon.EventManager({
            context,
            name: "allowHtmlTemp.onClick",
            // In this function we add listeners for any events we want to listen to, and return a
            // function that removes those listeners. To have the event fire in your extension,
            // call fire.async.
            register: (fire) => {
              function listener(event, tabId) {
                // console.debug("AHT: onClick register: tabId = " + tabId);
                fire.sync(tabId);
              }
              tracker.addOnClickedListener(listener);
              return () => {
                tracker.removeOnClickedListener(listener);
              };
            },
          }).api(),

          disconnectTab(tabId) {
            // console.debug("AHT - disconnectTab ", tabId);
            let messageWindow = getMessageWindow(tabId);
            if (!messageWindow) {
              return;
            }
            // Remove preference observers, to decouple this message tab from the
            // menu item. If they would not be decoupled, every pref change would lead
            // to a message reload (with default plaintext / HTML settings).
            // For a list of observed (and message reload triggering) prefs see
            // the _"topics":
            // https://searchfox.org/comm-central/source/mail/base/content/aboutMessage.js#341

            // The following line decouples the original complete "_topics" list from observed prefs:
            messageWindow.preferenceObserver.cleanUp();
            // The following code block would only decouple the own "topics" array from observed prefs:
            /*
            let topics = [
              "mailnews.display.prefer_plaintext",
              "mailnews.display.html_as",
              "mailnews.display.disallow_mime_handlers",
              "mail.inline_attachments",
            ];
            for (let i = 0; i < topics.length; i++) {
              console.debug("Services.prefs.removeObserver " + topics[i] + " from messageWindow.preferenceObserver in tabId = " + tabId);
              Services.prefs.removeObserver(topics[i], messageWindow.preferenceObserver);
            }
            */

            tracker.trackWindow(messageWindow);

            // register the remoteContentBar click listener and remove the original oncommand
            try {
              let element = messageWindow.document.getElementById("remoteContentOptionAllowForMsg");
              if(!element) {
                // console.debug('AHT: addEventListener: !element');
                return;
              }
              element.removeAttribute("oncommand");
              element.addEventListener("click", tracker.notifyOnClickedListener);
              // console.debug('AHT: addEventListener: ready');
            } catch (e) {
              // console.debug("AHT: addEventListener: not possible");
            }
          },

          reconnectTab(tabId) {
            // console.debug("AHT - reconnectTab ", tabId);
            let messageWindow = getMessageWindow(tabId);
            if (!messageWindow) {
              return;
            }
            // Connect the tab back to the prefs.
            messageWindow.preferenceObserver.init();
          },

          reloadTab(tabId) {
            // console.debug("AHT - reloadTab ", tabId);
            let messageWindow = getMessageWindow(tabId);
            if (!messageWindow) {
              return;
            }
            // Connect the tab back to the prefs.
            try {
              messageWindow.ReloadMessage();
            } catch {
              // console.debug("AHT - Error in reloadTab (not a real issue on Thunderbird startup)", tabId);
            }
          },

          async checkMailForHtmlpart(messageId, optionsDebug) {
            if(optionsDebug)
              console.debug("AHT: run checkMailForHtmlpart ----------------");
            let ahtMsgHdr = context.extension.messageManager.get(messageId);
  
            // First check MsgHdr without decrypting to prevent an additional passphrase dialog in case of PGP/MIME
            let aMimeMsg = await new Promise(resolve => {
              MsgHdrToMimeMessage(
                ahtMsgHdr,
                null,
                (aMsgHdr, aMimeMsg) => resolve(aMimeMsg),
                true,
                {
                  examineEncryptedParts: false
                }
              );
            })
  
            // multipart/encrypted enables the button for encrypted PGP/MIME messages
            // in this case we don't check for HTML, because the check seems not to be possible for PGP/MIME
            if (aMimeMsg.prettyString().search("multipart/encrypted") != -1) {
              if(optionsDebug)
                console.debug("AHT: message is PGP/MIME multipart/encrypted");
              return true;
            } else {
              // search for 'Body: text/html' in MIME parts,
              // it seems this is only working if messages are downloaded for offline reading?
              let aMimeMsg = await new Promise(resolve => {
                MsgHdrToMimeMessage(
                  ahtMsgHdr,
                  null,
                  (aMsgHdr, aMimeMsg) => resolve(aMimeMsg),
                  true,
                  {
                    examineEncryptedParts: true
                  }
                );
              })
  
              if(optionsDebug) {
                console.debug("AHT: Check for html part ----------------");
                console.debug("AHT: Body: text/html " + aMimeMsg.prettyString().search("Body: text/html"));
                console.debug("AHT: text/html " + aMimeMsg.prettyString().search("text/html"));
                console.debug("AHT: Body: plain/html " + aMimeMsg.prettyString().search("Body: plain/html"));
                console.debug("AHT: plain/html " + aMimeMsg.prettyString().search("plain/html"));
                console.debug("AHT: multipart/alternative " + aMimeMsg.prettyString().search("multipart/alternative"));
                console.debug("AHT: multipart/signed " + aMimeMsg.prettyString().search("multipart/signed"));
                console.debug("AHT: multipart/encrypted " + aMimeMsg.prettyString().search("multipart/encrypted"));
              }
  
              // 'Body: text/html' is found, enable ahtButtons
              if (aMimeMsg.prettyString().search("Body: text/html") != -1) {
                if(optionsDebug)
                  console.debug("AHT: message contains HTML body part");
                return true;
              }
              // no 'Body: text/html', disable ahtButtons
              else {
                if(optionsDebug)
                  console.debug("AHT: no HTML body part");
                return false;
              }
            }
          }
  
        }
      };
    }

    onShutdown(isAppShutdown) {
      if (isAppShutdown) {
        return; // the application gets unloaded anyway
      }

      for(let messageWindow of tracker.trackedWindows) {
        if (!messageWindow) {
          continue;
        }

        // Connect the tab back to the prefs.
        messageWindow.preferenceObserver.init();

        // Unregister and reset manipulated elements
        // console.debug("AHT - onShutdown");
        let element = messageWindow.document.getElementById("remoteContentOptionAllowForMsg");
        element.removeEventListener("click", tracker.notifyOnClickedListener);
        let oncommand_original = "LoadMsgWithRemoteContent();";
        element.setAttribute("oncommand", oncommand_original);
      }

      // Flush all caches
      Services.obs.notifyObservers(null, "startupcache-invalidate");
    }
  }

  exports.allowHtmlTemp = allowHtmlTemp;
})(this);