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

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs",
  ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
  PanelMultiView:
    "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  RemoteL10n: "resource:///modules/asrouter/RemoteL10n.sys.mjs",
  SpecialMessageActions:
    "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
  UIState: "resource://services-sync/UIState.sys.mjs",
});

export const MenuMessage = {
  SOURCES: Object.freeze({
    APP_MENU: "app_menu",
    PXI_MENU: "pxi_menu",
  }),

  MESSAGE_TYPES: Object.freeze({
    FXA_CTA: "fxa_cta",
    DEFAULT_CTA: "default_cta",
  }),

  MESSAGE_TYPE_ALLOWED_BY_SOURCE: Object.freeze({
    app_menu: new Set(["fxa_cta", "default_cta"]),
    pxi_menu: new Set(["fxa_cta"]),
  }),
  SHOWING_FXA_MENU_MESSAGE_ATTR: "showing-fxa-menu-message",
  SHOWING_SET_TO_DEFAULT_MENU_MESSAGE_ATTR: "showing-default-cta-menu-message",

  async showMenuMessage(browser, message, trigger, force) {
    if (!browser) {
      return;
    }

    let win = browser.ownerGlobal;

    if (!win || lazy.PrivateBrowsingUtils.isWindowPrivate(win)) {
      return;
    }

    let source = trigger?.context?.source || message.testingTriggerContext;

    // Restrict message types to their allowed sources
    if (
      !this.MESSAGE_TYPE_ALLOWED_BY_SOURCE[source]?.has(
        message.content.messageType
      )
    ) {
      return;
    }

    switch (source) {
      case this.SOURCES.APP_MENU: {
        this.showAppMenuMessage(browser, message, force);
        break;
      }

      case this.SOURCES.PXI_MENU: {
        this.showPxiMenuMessage(browser, message, force);
        break;
      }
    }
  },

  /**
   * Whether the message should be suppressed for a signed-in user.
   *  - fxa_cta: suppress when signed in by default, unless
   *    content.allowWhenSignedIn is set to true.
   *  - default_cta: never suppress
   */
  shouldSuppressForSignedIn(message) {
    const type = message?.content?.messageType;
    const isSignedIn =
      lazy.UIState.get().status === lazy.UIState.STATUS_SIGNED_IN;

    // If not signed in, no need to suppress
    if (!isSignedIn) {
      return false;
    }

    //  Suppress fxa_cta messages by default, unless we explicitly allow it.
    if (type === this.MESSAGE_TYPES.FXA_CTA) {
      const allowWhenSignedIn = !!message.content?.allowWhenSignedIn;
      return !allowWhenSignedIn;
    }

    // For any other message, we don't suppress it.
    return false;
  },

  preparePrimaryAction(message, source) {
    const type = message?.content?.messageType;
    const primaryAction = message?.content?.primaryAction;
    if (!primaryAction) {
      return null;
    }
    const action = structuredClone(primaryAction);

    // For fxa_cta messages, depending on the source that showed the
    // message, we'll want to set a particular entrypoint in the data
    // payload in the event that we're  opening up the FxA sign-up page.
    if (type === this.MESSAGE_TYPES.FXA_CTA) {
      action.data = action.data || {};
      action.data.extraParams = action.data.extraParams || {};

      if (source === this.SOURCES.APP_MENU) {
        action.data.entrypoint = "fxa_app_menu";
        action.data.extraParams.utm_content = `${action.data.extraParams.utm_content}-app_menu`;
      } else if (source === this.SOURCES.PXI_MENU) {
        action.data.entrypoint = "fxa_avatar_menu";
        action.data.extraParams.utm_content = `${action.data.extraParams.utm_content}-avatar`;
      }
    }
    return action;
  },

  async showAppMenuMessage(browser, message, force) {
    const win = browser.ownerGlobal;
    const msgContainer = this.hideAppMenuMessage(browser);
    const type = message?.content?.messageType;

    // This version of the browser only supports the fxa_cta and
    // default_cta versions of this message in the AppMenu.
    // We also don't draw focus away from any existing AppMenuNotifications.
    if (!message || lazy.AppMenuNotifications.activeNotification) {
      return;
    }

    // Respect message type signed-in render rules (fxa_cta suppresses by default)
    if (this.shouldSuppressForSignedIn(message)) {
      return;
    }

    const menuMessageAttribute =
      type === this.MESSAGE_TYPES.DEFAULT_CTA
        ? MenuMessage.SHOWING_SET_TO_DEFAULT_MENU_MESSAGE_ATTR
        : MenuMessage.SHOWING_FXA_MENU_MESSAGE_ATTR;

    let msgElement = await this.constructMenuMessage(
      win,
      message,
      MenuMessage.SOURCES.APP_MENU
    );

    win.PanelUI.mainView.setAttribute(menuMessageAttribute, message.id);

    msgElement.addEventListener("MenuMessage:Close", () => {
      win.PanelUI.mainView.removeAttribute(menuMessageAttribute);
    });

    msgElement.addEventListener("MenuMessage:PrimaryButton", () => {
      win.PanelUI.hide();
    });

    msgContainer.appendChild(msgElement);

    if (force) {
      win.PanelUI.show();
    }
  },

  hideAppMenuMessage(browser) {
    const win = browser.ownerGlobal;
    const document = browser.ownerDocument;
    const msgContainer = lazy.PanelMultiView.getViewNode(
      document,
      "appMenu-menu-message"
    );
    msgContainer.innerHTML = "";
    win.PanelUI.mainView.removeAttribute(
      MenuMessage.SHOWING_FXA_MENU_MESSAGE_ATTR
    );
    win.PanelUI.mainView.removeAttribute(
      MenuMessage.SHOWING_SET_TO_DEFAULT_MENU_MESSAGE_ATTR
    );

    return msgContainer;
  },

  async showPxiMenuMessage(browser, message, force) {
    const win = browser.ownerGlobal;
    const { document } = win;
    const msgContainer = this.hidePxiMenuMessage(browser);

    // Respect message type signed-in render rules (fxa_cta suppresses by default)
    if (this.shouldSuppressForSignedIn(message)) {
      return;
    }

    let fxaPanelView = lazy.PanelMultiView.getViewNode(document, "PanelUI-fxa");
    fxaPanelView.setAttribute(
      MenuMessage.SHOWING_FXA_MENU_MESSAGE_ATTR,
      message.id
    );

    let msgElement = await this.constructMenuMessage(
      win,
      message,
      MenuMessage.SOURCES.PXI_MENU
    );

    msgElement.addEventListener("MenuMessage:Close", () => {
      fxaPanelView.removeAttribute(MenuMessage.SHOWING_FXA_MENU_MESSAGE_ATTR);
    });

    msgElement.addEventListener("MenuMessage:PrimaryButton", () => {
      let panelNode = fxaPanelView.closest("panel");

      if (panelNode) {
        lazy.PanelMultiView.hidePopup(panelNode);
      }
    });

    msgContainer.appendChild(msgElement);

    if (force) {
      await win.gSync.toggleAccountPanel(
        document.getElementById("fxa-toolbar-menu-button"),
        new MouseEvent("mousedown")
      );
    }
  },

  hidePxiMenuMessage(browser) {
    const document = browser.ownerDocument;
    const msgContainer = lazy.PanelMultiView.getViewNode(
      document,
      "PanelUI-fxa-menu-message"
    );
    msgContainer.innerHTML = "";
    let fxaPanelView = lazy.PanelMultiView.getViewNode(document, "PanelUI-fxa");
    fxaPanelView.removeAttribute(MenuMessage.SHOWING_FXA_MENU_MESSAGE_ATTR);
    return msgContainer;
  },

  async constructMenuMessage(win, message, source) {
    let { document, gBrowser } = win;

    win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");

    const msgElement = document.createElement("menu-message");
    msgElement.layout = message.content.layout ?? "column";
    msgElement.imageURL = message.content.imageURL;
    msgElement.logoURL = message.content.logoURL;
    msgElement.primaryButtonSize =
      message.content.primaryButtonSize ?? "default";
    msgElement.buttonText = await lazy.RemoteL10n.formatLocalizableText(
      message.content.primaryActionText
    );
    msgElement.primaryText = await lazy.RemoteL10n.formatLocalizableText(
      message.content.primaryText
    );
    // Simple layout does not support secondary text
    if (message.content.layout !== "simple" && message.content.secondaryText) {
      msgElement.secondaryText = await lazy.RemoteL10n.formatLocalizableText(
        message.content.secondaryText
      );
    }
    msgElement.dataset.navigableWithTabOnly = "true";
    if (message.content.imageWidth !== undefined) {
      msgElement.style.setProperty(
        "--image-width",
        `${message.content.imageWidth}px`
      );
    }
    if (message.content.logoWidth !== undefined) {
      msgElement.style.setProperty(
        "--logo-width",
        `${message.content.logoWidth}px`
      );
    }
    if (message.content.imageVerticalTopOffset !== undefined) {
      msgElement.style.setProperty(
        "--illustration-margin-block-start-offset",
        `${message.content.imageVerticalTopOffset}px`
      );
    }
    if (message.content.imageVerticalBottomOffset !== undefined) {
      msgElement.style.setProperty(
        "--illustration-margin-block-end-offset",
        `${message.content.imageVerticalBottomOffset}px`
      );
    }
    if (message.content.containerVerticalBottomOffset !== undefined) {
      msgElement.style.setProperty(
        "--container-margin-block-end-offset",
        `${message.content.containerVerticalBottomOffset}px`
      );
    }
    if (message.content.containerPaddingBottom !== undefined) {
      msgElement.style.setProperty(
        "--container-padding-block-end",
        `${message.content.containerPaddingBottom}px`
      );
    }

    msgElement.addEventListener("MenuMessage:Close", () => {
      msgElement.remove();

      this.recordMenuMessageTelemetry("DISMISS", source, message.id);

      lazy.SpecialMessageActions.handleAction(
        message.content.closeAction,
        gBrowser.selectedBrowser
      );
    });

    msgElement.addEventListener("MenuMessage:PrimaryButton", () => {
      this.recordMenuMessageTelemetry("CLICK", source, message.id);

      const primaryAction = this.preparePrimaryAction(message, source);
      if (primaryAction) {
        lazy.SpecialMessageActions.handleAction(
          primaryAction,
          gBrowser.selectedBrowser
        );
      }
    });

    return msgElement;
  },

  recordMenuMessageTelemetry(event, source, messageId) {
    let ping = {
      message_id: messageId,
      event,
      source,
    };
    lazy.ASRouter.dispatchCFRAction({
      type: "MENU_MESSAGE_TELEMETRY",
      data: { action: "menu_message_user_event", ...ping },
    });
  },
};
