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

/**
 * Determines whether a given value is a fluent id or plain text and adds it to an element
 *
 * @param {Array<[HTMLElement, string]>} items An array of [element, value] where value is
 *                                       a fluent id starting with "fluent:" or plain text
 */
function translateElements(items) {
  items.forEach(([element, value]) => {
    // Skip empty text or elements
    if (!element || !value) {
      return;
    }
    const fluentId = value.replace(/^fluent:/, "");
    if (fluentId !== value) {
      document.l10n.setAttributes(element, fluentId);
    } else {
      element.textContent = value;
      element.removeAttribute("data-l10n-id");
    }
  });
}

function renderInfo({
  infoEnabled,
  infoTitle,
  infoTitleEnabled,
  infoBody,
  infoLinkText,
  infoLinkUrl,
  infoIcon,
} = {}) {
  const container = document.querySelector(".info");
  if (infoEnabled === false) {
    container.hidden = true;
    return;
  }
  container.hidden = false;

  const titleEl = document.getElementById("info-title");
  const bodyEl = document.getElementById("info-body");
  const linkEl = document.getElementById("private-browsing-myths");

  let feltPrivacyEnabled = RPMGetBoolPref(
    "browser.privatebrowsing.felt-privacy-v1",
    false
  );

  if (infoIcon && !feltPrivacyEnabled) {
    container.style.backgroundImage = `url(${infoIcon})`;
  }

  if (feltPrivacyEnabled) {
    // Record exposure event for Felt Privacy experiment
    window.FeltPrivacyExposureTelemetry();

    infoTitleEnabled = true;
    infoTitle = "fluent:about-private-browsing-felt-privacy-v1-info-header";
    infoBody = "fluent:about-private-browsing-felt-privacy-v1-info-body";
    infoLinkText = "fluent:about-private-browsing-felt-privacy-v1-info-link";
  }

  titleEl.hidden = !infoTitleEnabled;

  translateElements([
    [titleEl, infoTitle],
    [bodyEl, infoBody],
    [linkEl, infoLinkText],
  ]);

  if (infoLinkUrl) {
    linkEl.setAttribute("href", infoLinkUrl);
  }
}

async function renderPromo({
  messageId = null,
  promoEnabled = false,
  promoType = "VPN",
  promoTitle,
  promoTitleEnabled,
  promoLinkText,
  promoLinkType,
  promoSectionStyle,
  promoHeader,
  promoImageLarge,
  promoImageSmall,
  promoButton = null,
} = {}) {
  const shouldShow = await RPMSendQuery("ShouldShowPromo", { type: promoType });
  const container = document.querySelector(".promo");

  if (!promoEnabled || !shouldShow) {
    container.remove();
    return false;
  }

  const titleEl = document.getElementById("private-browsing-promo-text");
  const linkEl = document.getElementById("private-browsing-promo-link");
  const promoHeaderEl = document.getElementById("promo-header");
  const infoContainerEl = document.querySelector(".info");
  const promoImageLargeEl = document.querySelector(".promo-image-large img");
  const promoImageSmallEl = document.querySelector(".promo-image-small img");
  const dismissBtn = document.querySelector("#dismiss-btn");

  if (promoLinkType === "link") {
    linkEl.classList.remove("primary");
    linkEl.classList.add("text-link", "promo-link");
  }

  if (promoButton?.action) {
    linkEl.addEventListener("click", async event => {
      event.preventDefault();

      // Record promo click telemetry and set metrics as allow for spotlight
      // modal opened on promo click if user is enrolled in an experiment
      let isExperiment = window.PrivateBrowsingRecordClick("PromoLink");
      const promoButtonData = promoButton?.action?.data;
      if (
        promoButton?.action?.type === "SHOW_SPOTLIGHT" &&
        promoButtonData?.content
      ) {
        promoButtonData.content.metrics = isExperiment ? "allow" : "block";
      }

      await RPMSendQuery("SpecialMessageActionDispatch", promoButton.action);
    });
  } else {
    // If the action doesn't exist, remove the promo completely
    container.remove();
    return false;
  }

  const onDismissBtnClick = () => {
    window.ASRouterMessage({
      type: "BLOCK_MESSAGE_BY_ID",
      data: { id: messageId },
    });
    window.PrivateBrowsingRecordClick("DismissButton");
    container.remove();
  };

  if (dismissBtn && messageId) {
    dismissBtn.addEventListener("click", onDismissBtnClick, { once: true });
  }

  if (promoSectionStyle) {
    container.classList.add(promoSectionStyle);

    switch (promoSectionStyle) {
      case "below-search":
        container.remove();
        infoContainerEl?.insertAdjacentElement("beforebegin", container);
        break;
      case "top":
        container.remove();
        document.body.insertAdjacentElement("afterbegin", container);
    }
  }

  if (promoImageLarge) {
    promoImageLargeEl.src = promoImageLarge;
  } else {
    promoImageLargeEl.parentNode.remove();
  }

  if (promoImageSmall) {
    promoImageSmallEl.src = promoImageSmall;
  } else {
    promoImageSmallEl.parentNode.remove();
  }

  if (!promoTitleEnabled) {
    titleEl.remove();
  }

  if (!promoHeader) {
    promoHeaderEl.remove();
  }

  translateElements([
    [titleEl, promoTitle],
    [linkEl, promoLinkText],
    [promoHeaderEl, promoHeader],
  ]);

  // Only make promo section visible after adding content
  // and translations to prevent layout shifting in page
  container.classList.add("promo-visible");
  return true;
}

/**
 * For every PB newtab loaded, a second is pre-rendered in the background.
 * We need to guard against invalid impressions by checking visibility state.
 * If visible, record. Otherwise, listen for visibility change and record later.
 */
function recordOnceVisible(message) {
  const recordImpression = () => {
    if (document.visibilityState === "visible") {
      window.ASRouterMessage({
        type: "IMPRESSION",
        data: message,
      });
      // Similar telemetry, but for Nimbus experiments
      window.PrivateBrowsingPromoExposureTelemetry();
      document.removeEventListener("visibilitychange", recordImpression);
    }
  };

  if (document.visibilityState === "visible") {
    window.ASRouterMessage({
      type: "IMPRESSION",
      data: message,
    });
    // Similar telemetry, but for Nimbus experiments
    window.PrivateBrowsingPromoExposureTelemetry();
  } else {
    document.addEventListener("visibilitychange", recordImpression);
  }
}

// The PB newtab may be pre-rendered. Once the tab is visible, check to make sure the message wasn't blocked after the initial render. If it was, remove the promo.
function handlePromoOnPreload(message) {
  async function removePromoIfBlocked() {
    if (document.visibilityState === "visible") {
      let blocked = await RPMSendQuery("IsPromoBlocked", message);
      if (blocked) {
        const container = document.querySelector(".promo");
        container.remove();
      }
    }
    document.removeEventListener("visibilitychange", removePromoIfBlocked);
  }
  // Only add the listener to pre-rendered tabs that aren't visible
  if (document.visibilityState !== "visible") {
    document.addEventListener("visibilitychange", removePromoIfBlocked);
  }
}

async function setupMessageConfig(config = null) {
  let message = null;

  if (!config) {
    let hideDefault = window.PrivateBrowsingShouldHideDefault();
    try {
      let response = await window.ASRouterMessage({
        type: "PBNEWTAB_MESSAGE_REQUEST",
        data: { hideDefault: !!hideDefault },
      });
      message = response?.message;
      config = message?.content;
      config.messageId = message?.id;
    } catch (e) {}
  }

  renderInfo(config);
  let hasRendered = await renderPromo(config);
  if (hasRendered && message) {
    recordOnceVisible(message);
    handlePromoOnPreload(message);
  }
  // For tests
  document.documentElement.setAttribute("PrivateBrowsingRenderComplete", true);
}

let SHOW_DEVTOOLS_MESSAGE = "ShowDevToolsMessage";

function showDevToolsMessage(msg) {
  msg.data.content.messageId = "DEVTOOLS_MESSAGE";
  setupMessageConfig(msg?.data?.content);
  RPMRemoveMessageListener(SHOW_DEVTOOLS_MESSAGE, showDevToolsMessage);
}

document.addEventListener("DOMContentLoaded", function () {
  // check the url to see if we're rendering a devtools message
  if (document.location.toString().includes("debug")) {
    RPMAddMessageListener(SHOW_DEVTOOLS_MESSAGE, showDevToolsMessage);
    return;
  }
  if (!RPMIsWindowPrivate()) {
    document.documentElement.classList.remove("private");
    document.documentElement.classList.add("normal");
    document
      .getElementById("startPrivateBrowsing")
      .addEventListener("click", function () {
        RPMSendAsyncMessage("OpenPrivateWindow");
      });
    return;
  }

  // The default info content is already in the markup, but we need to use JS to
  // set up the learn more link, since it's dynamically generated.
  const linkEl = document.getElementById("private-browsing-myths");
  linkEl.setAttribute(
    "href",
    RPMGetFormatURLPref("app.support.baseURL") + "private-browsing-myths"
  );
  linkEl.addEventListener("click", () => {
    window.PrivateBrowsingRecordClick("InfoLink");
  });

  // We don't do this setup until now, because we don't want to record any impressions until we're
  // sure we're actually running a private window, not just about:privatebrowsing in a normal window.
  setupMessageConfig();

  // Set up the private search banner.
  const privateSearchBanner = document.getElementById("search-banner");

  RPMSendQuery("ShouldShowSearchBanner", {}).then(engineName => {
    if (engineName) {
      document.l10n.setAttributes(
        document.getElementById("about-private-browsing-search-banner-title"),
        "about-private-browsing-search-banner-title",
        { engineName }
      );
      privateSearchBanner.removeAttribute("hidden");
      document.body.classList.add("showBanner");
    }

    // We set this attribute so that tests know when we are done.
    document.documentElement.setAttribute("SearchBannerInitialized", true);
  });

  function hideSearchBanner() {
    privateSearchBanner.hidden = true;
    document.body.classList.remove("showBanner");
    RPMSendAsyncMessage("SearchBannerDismissed");
  }

  document
    .getElementById("search-banner-close-button")
    .addEventListener("click", () => {
      hideSearchBanner();
    });

  let openSearchOptions = document.getElementById(
    "about-private-browsing-search-banner-description"
  );
  let openSearchOptionsEvtHandler = evt => {
    if (
      evt.target.id == "open-search-options-link" &&
      (evt.keyCode == evt.DOM_VK_RETURN || evt.type == "click")
    ) {
      RPMSendAsyncMessage("OpenSearchPreferences");
      hideSearchBanner();
    }
  };
  openSearchOptions.addEventListener("click", openSearchOptionsEvtHandler);
  openSearchOptions.addEventListener("keypress", openSearchOptionsEvtHandler);

  // Setup the search hand-off box.
  let btn = document.getElementById("search-handoff-button");

  let editable = document.getElementById("fake-editable");
  let DISABLE_SEARCH_TOPIC = "DisableSearch";
  let SHOW_SEARCH_TOPIC = "ShowSearch";
  let SEARCH_HANDOFF_TOPIC = "SearchHandoff";

  function showSearch() {
    btn.classList.remove("focused");
    btn.classList.remove("disabled");
    RPMRemoveMessageListener(SHOW_SEARCH_TOPIC, showSearch);
  }

  function disableSearch() {
    btn.classList.add("disabled");
  }

  function handoffSearch(text) {
    RPMSendAsyncMessage(SEARCH_HANDOFF_TOPIC, { text });
    RPMAddMessageListener(SHOW_SEARCH_TOPIC, showSearch);
    if (text) {
      disableSearch();
    } else {
      btn.classList.add("focused");
      RPMAddMessageListener(DISABLE_SEARCH_TOPIC, disableSearch);
    }
  }
  btn.addEventListener("focus", function () {
    handoffSearch();
  });
  btn.addEventListener("click", function () {
    handoffSearch();
  });

  // Hand-off any text that gets dropped or pasted
  editable.addEventListener("drop", function (ev) {
    ev.preventDefault();
    let text = ev.dataTransfer.getData("text");
    if (text) {
      handoffSearch(text);
    }
  });
  editable.addEventListener("paste", function (ev) {
    ev.preventDefault();
    handoffSearch(ev.clipboardData.getData("Text"));
  });

  // Load contentSearchUI so it sets the search engine icon and name for us.
  new window.ContentSearchHandoffUIController();
});
