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

"use strict";
const { MessageLoaderUtils } = ChromeUtils.importESModule(
  "resource:///modules/asrouter/ASRouter.sys.mjs"
);

const { BuiltInThemes } = ChromeUtils.importESModule(
  "resource:///modules/BuiltInThemes.sys.mjs"
);

const defaultPrefValue = getPrefValueByScreen(1);

add_setup(async function () {
  requestLongerTimeout(3);
  registerCleanupFunction(() => ASRouter.resetMessageState());
});

add_task(async function feature_callout_renders_in_firefox_view() {
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, defaultPrefValue]],
  });

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);
      await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");

      ok(
        document.querySelector(calloutSelector),
        "Feature Callout element exists"
      );
    }
  );
});

add_task(async function feature_callout_is_not_shown_twice() {
  // Third comma-separated value of the pref is set to a string value once a user completes the tour
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, '{"screen":"","complete":true}']],
  });

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      ok(
        !document.querySelector(calloutSelector),
        "Feature Callout tour does not render if the user finished it previously"
      );
    }
  );
  Services.prefs.clearUserPref(featureTourPref);
});

add_task(async function feature_callout_syncs_across_visits_and_tabs() {
  // Second comma-separated value of the pref is the id
  // of the last viewed screen of the feature tour
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, '{"screen":"FEATURE_CALLOUT_1","complete":false}']],
  });
  // Open an about:firefoxview tab
  let tab1 = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:firefoxview"
  );
  let tab1Doc = tab1.linkedBrowser.contentWindow.document;
  launchFeatureTourIn(tab1.linkedBrowser.contentWindow);
  await waitForCalloutScreen(tab1Doc, "FEATURE_CALLOUT_1");

  ok(
    tab1Doc.querySelector(".FEATURE_CALLOUT_1"),
    "First tab's Feature Callout shows the tour screen saved in the user pref"
  );

  // Open a second about:firefoxview tab
  let tab2 = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "about:firefoxview"
  );
  let tab2Doc = tab2.linkedBrowser.contentWindow.document;
  launchFeatureTourIn(tab2.linkedBrowser.contentWindow);
  await waitForCalloutScreen(tab2Doc, "FEATURE_CALLOUT_1");

  ok(
    tab2Doc.querySelector(".FEATURE_CALLOUT_1"),
    "Second tab's Feature Callout shows the tour screen saved in the user pref"
  );

  await clickCTA(tab2Doc);
  await waitForCalloutScreen(tab2Doc, "FEATURE_CALLOUT_2");

  gBrowser.selectedTab = tab1;
  tab1.focus();
  await waitForCalloutScreen(tab1Doc, "FEATURE_CALLOUT_2");
  ok(
    tab1Doc.querySelector(".FEATURE_CALLOUT_2"),
    "First tab's Feature Callout advances to the next screen when the tour is advanced in second tab"
  );

  await clickCTA(tab1Doc);
  gBrowser.selectedTab = tab1;
  await waitForCalloutRemoved(tab1Doc);

  ok(
    !tab1Doc.body.querySelector(calloutSelector),
    "Feature Callout is removed in first tab after being dismissed in first tab"
  );

  gBrowser.selectedTab = tab2;
  tab2.focus();
  await waitForCalloutRemoved(tab2Doc);

  ok(
    !tab2Doc.body.querySelector(calloutSelector),
    "Feature Callout is removed in second tab after tour was dismissed in first tab"
  );

  BrowserTestUtils.removeTab(tab1);
  BrowserTestUtils.removeTab(tab2);
  Services.prefs.clearUserPref(featureTourPref);
});

add_task(async function feature_callout_closes_on_dismiss() {
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, '{"screen":"FEATURE_CALLOUT_2","complete":false}']],
  });
  const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
  const spy = new TelemetrySpy(sandbox);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");

      document.querySelector(".dismiss-button").click();
      await waitForCalloutRemoved(document);

      ok(
        !document.querySelector(calloutSelector),
        "Callout is removed from screen on dismiss"
      );

      let tourComplete = JSON.parse(
        Services.prefs.getStringPref(featureTourPref)
      ).complete;
      ok(
        tourComplete,
        `Tour is recorded as complete in ${featureTourPref} preference value`
      );

      // Test that appropriate telemetry is sent
      spy.assertCalledWith({
        event: "CLICK_BUTTON",
        event_context: {
          source: "dismiss_button",
          page: "about:firefoxview",
        },
        message_id: sinon.match("FEATURE_CALLOUT_2"),
      });
      spy.assertCalledWith({
        event: "DISMISS",
        event_context: {
          source: "dismiss_button",
          page: "about:firefoxview",
        },
        message_id: sinon.match("FEATURE_CALLOUT_2"),
      });
    }
  );
  sandbox.restore();
});

add_task(async function feature_callout_arrow_position_attribute_exists() {
  const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      const callout = await BrowserTestUtils.waitForCondition(
        () =>
          document.querySelector(`${calloutSelector}[arrow-position="top"]`),
        "Waiting for callout to render"
      );
      is(
        callout.getAttribute("arrow-position"),
        "top",
        "Arrow position attribute exists on parent container"
      );
    }
  );
  sandbox.restore();
});

add_task(async function feature_callout_arrow_is_not_flipped_on_ltr() {
  const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
  testMessage.message.content.screens[0].anchors[0].arrow_position = "start";
  testMessage.message.content.screens[0].anchors[0].selector =
    "span.brand-feature-name";
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      const callout = await BrowserTestUtils.waitForCondition(
        () =>
          document.querySelector(
            `${calloutSelector}[arrow-position="inline-start"]:not(.hidden)`
          ),
        "Waiting for callout to render"
      );
      is(
        callout.getAttribute("arrow-position"),
        "inline-start",
        "Feature callout has inline-start arrow position when arrow_position is set to 'start'"
      );
      ok(
        !callout.classList.contains("hidden"),
        "Feature Callout is not hidden"
      );
    }
  );
  sandbox.restore();
});

add_task(async function feature_callout_respects_cfr_features_pref() {
  async function toggleCFRFeaturesPref(value) {
    await SpecialPowers.pushPrefEnv({
      set: [
        [
          "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
          value,
        ],
      ],
    });
  }

  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, defaultPrefValue]],
  });

  await toggleCFRFeaturesPref(true);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
      ok(
        document.querySelector(calloutSelector),
        "Feature Callout element exists"
      );
    }
  );

  await SpecialPowers.popPrefEnv();
  await toggleCFRFeaturesPref(false);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      ok(
        !document.querySelector(calloutSelector),
        "Feature Callout element was not created because CFR pref was disabled"
      );
    }
  );

  await SpecialPowers.popPrefEnv();
});

add_task(
  async function feature_callout_tab_pickup_reminder_primary_click_elm() {
    Services.prefs.setBoolPref("identity.fxaccounts.enabled", false);
    const testMessage = getCalloutMessageById(
      "FIREFOX_VIEW_TAB_PICKUP_REMINDER"
    );
    const sandbox = createSandboxWithCalloutTriggerStub(testMessage);

    const expectedUrl =
      await fxAccounts.constructor.config.promiseConnectAccountURI("fx-view");
    info(`Expected FxA URL: ${expectedUrl}`);

    await BrowserTestUtils.withNewTab(
      {
        gBrowser,
        url: "about:firefoxview",
      },
      async browser => {
        const { document } = browser.contentWindow;

        launchFeatureTourIn(browser.contentWindow);

        let tabOpened = new Promise(resolve => {
          gBrowser.tabContainer.addEventListener(
            "TabOpen",
            event => {
              let newTab = event.target;
              let newBrowser = newTab.linkedBrowser;
              let result = newTab;
              BrowserTestUtils.waitForDocLoadAndStopIt(
                expectedUrl,
                newBrowser
              ).then(() => resolve(result));
            },
            { once: true }
          );
        });

        info("Waiting for callout to render");
        await waitForCalloutScreen(
          document,
          "FIREFOX_VIEW_TAB_PICKUP_REMINDER"
        );

        info("Clicking primary button");
        let calloutRemoved = waitForCalloutRemoved(document);
        await clickCTA(document);
        let openedTab = await tabOpened;
        ok(openedTab, "FxA sign in page opened");
        // The callout should be removed when primary CTA is clicked
        await calloutRemoved;
        BrowserTestUtils.removeTab(openedTab);
      }
    );
    Services.prefs.clearUserPref("identity.fxaccounts.enabled");
    sandbox.restore();
  }
);

add_task(async function feature_callout_dismiss_on_timeout() {
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, `{"screen":"","complete":true}`]],
  });
  const screenId = "FIREFOX_VIEW_TAB_PICKUP_REMINDER";
  let testMessage = getCalloutMessageById(screenId);
  // Configure message with a dismiss action on tab container click
  testMessage.message.content.screens[0].content.page_event_listeners = [
    {
      params: { type: "timeout", options: { once: true, interval: 5000 } },
      action: { dismiss: true, type: "CANCEL" },
    },
  ];
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
  const telemetrySpy = new TelemetrySpy(sandbox);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      let onInterval;
      let startedInterval = new Promise(resolve => {
        sandbox
          .stub(browser.contentWindow, "setInterval")
          .callsFake((fn, ms) => {
            Assert.strictEqual(
              ms,
              5000,
              "setInterval called with 5 second interval"
            );
            onInterval = fn;
            resolve();
            return 1;
          });
      });

      launchFeatureTourIn(browser.contentWindow);

      info("Waiting for callout to render");
      await startedInterval;
      await waitForCalloutScreen(document, screenId);

      info("Ending timeout");
      onInterval();
      await waitForCalloutRemoved(document);

      // Test that appropriate telemetry is sent
      telemetrySpy.assertCalledWith({
        event: "PAGE_EVENT",
        event_context: {
          action: "CANCEL",
          reason: "TIMEOUT",
          source: "timeout",
          page: "about:firefoxview",
        },
        message_id: screenId,
      });
      telemetrySpy.assertCalledWith({
        event: "DISMISS",
        event_context: {
          source: "PAGE_EVENT:timeout",
          page: "about:firefoxview",
        },
        message_id: screenId,
      });
    }
  );
  Services.prefs.clearUserPref("browser.firefox-view.view-count");
  sandbox.restore();
  ASRouter.resetMessageState();
});

add_task(async function feature_callout_advance_tour_on_page_click() {
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        featureTourPref,
        JSON.stringify({
          screen: "FEATURE_CALLOUT_1",
          complete: false,
        }),
      ],
    ],
  });

  // Add page action listeners to the built-in messages.
  let testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
  // Configure message with a dismiss action on tab container click
  testMessage.message.content.screens.forEach(screen => {
    screen.content.page_event_listeners = [
      {
        params: { type: "click", selectors: ".brand-logo" },
        action: JSON.parse(
          JSON.stringify(screen.content.primary_button.action)
        ),
      },
    ];
  });
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
      info("Clicking page container");
      // We intentionally turn off a11y_checks, because the following click
      // is send to dismiss the feature callout using an alternative way of
      // the callout dismissal, where other ways are accessible, therefore
      // this test can be ignored.
      AccessibilityUtils.setEnv({
        mustHaveAccessibleRule: false,
      });
      document.querySelector(".brand-logo").click();
      AccessibilityUtils.resetEnv();

      await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");
      info("Clicking page container");
      // We intentionally turn off a11y_checks, because the following click
      // is send to dismiss the feature callout using an alternative way of
      // the callout dismissal, where other ways are accessible, therefore
      // this test can be ignored.
      AccessibilityUtils.setEnv({
        mustHaveAccessibleRule: false,
      });
      document.querySelector(".brand-logo").click();
      AccessibilityUtils.resetEnv();

      await waitForCalloutRemoved(document);
      let tourComplete = JSON.parse(
        Services.prefs.getStringPref(featureTourPref)
      ).complete;
      ok(
        tourComplete,
        `Tour is recorded as complete in ${featureTourPref} preference value`
      );
    }
  );

  sandbox.restore();
  ASRouter.resetMessageState();
});

add_task(async function feature_callout_dismiss_on_escape() {
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, `{"screen":"","complete":true}`]],
  });
  const screenId = "FIREFOX_VIEW_TAB_PICKUP_REMINDER";
  let testMessage = getCalloutMessageById(screenId);
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
  const spy = new TelemetrySpy(sandbox);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      info("Waiting for callout to render");
      await waitForCalloutScreen(document, screenId);

      info("Pressing escape");
      // Press Escape to close
      EventUtils.synthesizeKey("KEY_Escape", {}, browser.contentWindow);
      await waitForCalloutRemoved(document);

      // Test that appropriate telemetry is sent
      spy.assertCalledWith({
        event: "DISMISS",
        event_context: {
          source: "KEY_Escape",
          page: "about:firefoxview",
        },
        message_id: screenId,
      });
    }
  );
  Services.prefs.clearUserPref("browser.firefox-view.view-count");
  sandbox.restore();
  ASRouter.resetMessageState();
});

add_task(async function test_firefox_view_spotlight_promo() {
  // Prevent attempts to fetch CFR messages remotely.
  const sandbox = sinon.createSandbox();
  let remoteSettingsStub = sandbox.stub(
    MessageLoaderUtils,
    "_remoteSettingsLoader"
  );
  remoteSettingsStub.resolves([]);

  await SpecialPowers.pushPrefEnv({
    clear: [
      [featureTourPref],
      ["browser.newtabpage.activity-stream.asrouter.providers.cfr"],
    ],
  });
  ASRouter.resetMessageState();

  let dialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen(
    null,
    "chrome://browser/content/spotlight.html",
    { isSubDialog: true }
  );

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      launchFeatureTourIn(browser.contentWindow);

      info("Waiting for the Fx View Spotlight promo to open");
      let dialogBrowser = await dialogOpenPromise;
      let primaryBtnSelector = ".action-buttons button.primary";
      await TestUtils.waitForCondition(
        () => dialogBrowser.document.querySelector("main.DEFAULT_MODAL_UI"),
        `Should render main.DEFAULT_MODAL_UI`
      );

      dialogBrowser.document.querySelector(primaryBtnSelector).click();
      info("Fx View Spotlight promo clicked");

      const { document } = browser.contentWindow;
      await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
      ok(
        document.querySelector(calloutSelector),
        "Feature Callout element exists"
      );
      info("Feature tour started");
      await clickCTA(document);
    }
  );

  ok(remoteSettingsStub.called, "Tried to load CFR messages");
  sandbox.restore();
  await SpecialPowers.popPrefEnv();
  ASRouter.resetMessageState();
});

add_task(async function feature_callout_returns_default_fxview_focus_to_top() {
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, defaultPrefValue]],
  });
  const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");

      ok(
        document.querySelector(calloutSelector),
        "Feature Callout element exists"
      );

      document.querySelector(".dismiss-button").click();
      await waitForCalloutRemoved(document);

      Assert.strictEqual(
        document.activeElement.localName,
        "body",
        "by default focus returns to the document body after callout closes"
      );
    }
  );
  sandbox.restore();
  await SpecialPowers.popPrefEnv();
  ASRouter.resetMessageState();
});

add_task(
  async function feature_callout_returns_moved_fxview_focus_to_previous() {
    const testMessage = getCalloutMessageById(
      "FIREFOX_VIEW_TAB_PICKUP_REMINDER"
    );
    const sandbox = createSandboxWithCalloutTriggerStub(testMessage);

    await BrowserTestUtils.withNewTab(
      {
        gBrowser,
        url: "about:firefoxview",
      },
      async browser => {
        const { document } = browser.contentWindow;

        launchFeatureTourIn(browser.contentWindow);

        await waitForCalloutScreen(
          document,
          "FIREFOX_VIEW_TAB_PICKUP_REMINDER"
        );

        // change focus to recently-closed-tabs-container
        let recentlyClosedHeaderSection = document.querySelector(
          "#recently-closed-tabs-header-section"
        );
        recentlyClosedHeaderSection.focus();

        // close the callout dialog
        document.querySelector(".dismiss-button").click();
        await waitForCalloutRemoved(document);

        // verify that the focus landed in the right place
        Assert.strictEqual(
          document.activeElement.id,
          "recently-closed-tabs-header-section",
          "when focus changes away from callout it reverts after callout closes"
        );
      }
    );
    sandbox.restore();
  }
);

add_task(async function feature_callout_does_not_display_arrow_if_hidden() {
  await SpecialPowers.pushPrefEnv({
    set: [[featureTourPref, defaultPrefValue]],
  });
  const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
  testMessage.message.content.screens[0].anchors[0].hide_arrow = true;
  const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "about:firefoxview",
    },
    async browser => {
      const { document } = browser.contentWindow;

      launchFeatureTourIn(browser.contentWindow);

      await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");

      is(
        getComputedStyle(
          document.querySelector(`${calloutSelector} .arrow-box`)
        ).getPropertyValue("display"),
        "none",
        "callout arrow is not visible"
      );
    }
  );
  sandbox.restore();
});
