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

// This tests the Search pane's Firefox Suggest UI.

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  QuickSuggest: "moz-src:///browser/components/urlbar/QuickSuggest.sys.mjs",
});

const CONTAINER_ID = "firefoxSuggestContainer";
const ONLINE_ENABLED_TOGGLE_ID = "firefoxSuggestOnlineEnabledToggle";
const ONLINE_ENABLED_LEARN_MORE_ID = "firefoxSuggestOnlineEnabledLearnMore";
const LEARN_MORE_ID = "firefoxSuggestLearnMore";
const BUTTON_RESTORE_DISMISSED_ID = "restoreDismissedSuggestions";

// Maps `SETTINGS_UI` values to expected visibility state objects. See
// `assertSuggestVisibility()` in `head.js` for info on the state objects.
const EXPECTED = {
  [QuickSuggest.SETTINGS_UI.FULL]: {
    [LEARN_MORE_ID]: { isVisible: true },
    [CONTAINER_ID]: { isVisible: true },
    [ONLINE_ENABLED_TOGGLE_ID]: { isVisible: true },
    [ONLINE_ENABLED_LEARN_MORE_ID]: { isVisible: true },
    locationBarGroupHeader: {
      isVisible: true,
      l10nId: "addressbar-header-firefox-suggest-1",
    },
    locationBarSuggestionLabel: {
      isVisible: true,
      l10nId: "addressbar-suggest-firefox-suggest-1",
    },
  },
  [QuickSuggest.SETTINGS_UI.NONE]: {
    [LEARN_MORE_ID]: { isVisible: false },
    [CONTAINER_ID]: { isVisible: false },
    locationBarGroupHeader: { isVisible: true, l10nId: "addressbar-header" },
    locationBarSuggestionLabel: {
      isVisible: true,
      l10nId: "addressbar-suggest-1",
    },
  },
  [QuickSuggest.SETTINGS_UI.OFFLINE_ONLY]: {
    [LEARN_MORE_ID]: { isVisible: true },
    [CONTAINER_ID]: { isVisible: true },
    [ONLINE_ENABLED_TOGGLE_ID]: { isVisible: false },
    [ONLINE_ENABLED_LEARN_MORE_ID]: { isVisible: false },
    locationBarGroupHeader: {
      isVisible: true,
      l10nId: "addressbar-header-firefox-suggest-1",
    },
    locationBarSuggestionLabel: {
      isVisible: true,
      l10nId: "addressbar-suggest-firefox-suggest-1",
    },
  },
};

// This test can take a while due to the many permutations some of these tasks
// run through, so request a longer timeout.
requestLongerTimeout(10);

add_setup(async function () {
  // Suggest needs to be initialized in order to dismiss a suggestion.
  await QuickSuggestTestUtils.ensureQuickSuggestInit();

  // Set the default value of the preference to FULL, since that's what the
  // test was originally written with.
  await SpecialPowers.pushPrefEnv({
    set: [
      ["browser.urlbar.quicksuggest.settingsUi", QuickSuggest.SETTINGS_UI.FULL],
    ],
  });
});

// The following tasks check the initial visibility of the Firefox Suggest UI
// and the visibility after installing a Nimbus experiment.

add_task(async function initiallyDisabled_disable() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: false,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
    nimbusVariables: {
      quickSuggestEnabled: false,
    },
  });
});

add_task(async function initiallyDisabled_disable_settingsUiFull() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: false,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
    nimbusVariables: {
      quickSuggestEnabled: false,
      // `quickSuggestEnabled: false` should override this, so the Suggest
      // settings should not be visible (`initialExpected` should persist).
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.FULL,
    },
  });
});

add_task(async function initiallyDisabled_enable() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: false,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
    nimbusVariables: {
      quickSuggestEnabled: true,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
  });
});

add_task(async function initiallyDisabled_enable_settingsUiFull() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: false,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
    nimbusVariables: {
      quickSuggestEnabled: true,
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.FULL,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
  });
});

add_task(async function initiallyDisabled_enable_settingsUiNone() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: false,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
    nimbusVariables: {
      quickSuggestEnabled: true,
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.NONE,
    },
  });
});

add_task(async function initiallyDisabled_enable_settingsUiOfflineOnly() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: false,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
    nimbusVariables: {
      quickSuggestEnabled: true,
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.OFFLINE_ONLY,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.OFFLINE_ONLY],
  });
});

add_task(async function initiallyEnabled_disable() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: true,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
    nimbusVariables: {
      quickSuggestEnabled: false,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
  });
});

add_task(async function initiallyEnabled_disable_settingsUiFull() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: true,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
    nimbusVariables: {
      quickSuggestEnabled: false,
      // `quickSuggestEnabled: false` should override this, so the Suggest
      // settings should not be visible.
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.FULL,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
  });
});

add_task(async function initiallyEnabled_enable() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: true,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
    nimbusVariables: {
      quickSuggestEnabled: true,
    },
  });
});

add_task(async function initiallyEnabled_settingsUiFull() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: true,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
    nimbusVariables: {
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.FULL,
    },
  });
});

add_task(async function initiallyEnabled_settingsUiNone() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: true,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
    nimbusVariables: {
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.NONE,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.NONE],
  });
});

add_task(async function initiallyEnabled_settingsUiOfflineOnly() {
  await doSuggestVisibilityTest({
    initialSuggestEnabled: true,
    initialExpected: EXPECTED[QuickSuggest.SETTINGS_UI.FULL],
    nimbusVariables: {
      quickSuggestSettingsUi: QuickSuggest.SETTINGS_UI.OFFLINE_ONLY,
    },
    newExpected: EXPECTED[QuickSuggest.SETTINGS_UI.OFFLINE_ONLY],
  });
});

add_task(async function toggling_all_firefoxsuggest_disables_other_options() {
  // Enable quicksuggest since it could be off by default depending on location.
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.suggest.quicksuggest.all", true]],
  });

  await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });

  let doc = gBrowser.selectedBrowser.contentDocument;
  let allCheckbox = doc.getElementById("firefoxSuggestAll");
  let sponsoredCheckbox = doc.getElementById("firefoxSuggestSponsored");
  let onlineEnabledCheckbox = doc.getElementById(
    "firefoxSuggestOnlineEnabledToggle"
  );

  // Initial state.
  Assert.ok(
    allCheckbox.checked,
    "firefoxSuggestAll should initially be checked"
  );
  Assert.ok(
    !sponsoredCheckbox.disabled,
    "sponsoredCheckbox should initially be enabled"
  );
  Assert.ok(
    !onlineEnabledCheckbox.disabled,
    "onlineEnabledCheckbox should initially be enabled"
  );

  allCheckbox.click();

  Assert.ok(!allCheckbox.checked, "firefoxSuggestAll should now be unchecked");
  Assert.ok(sponsoredCheckbox.disabled, "sponsoredCheckbox should be disabled");
  Assert.ok(
    onlineEnabledCheckbox.disabled,
    "onlineEnabledCheckbox should be disabled"
  );

  allCheckbox.click();

  Assert.ok(allCheckbox.checked, "firefoxSuggestAll should be checked");
  Assert.ok(
    !sponsoredCheckbox.disabled,
    "sponsoredCheckbox should be enabled again"
  );
  Assert.ok(
    !onlineEnabledCheckbox.disabled,
    "onlineEnabledCheckbox should be enabled again"
  );

  gBrowser.removeCurrentTab();
});

add_task(
  async function all_firefoxsuggest_disabled_disables_other_options_on_open() {
    // Disable the "all" preference and enable the others before opening settings.
    await SpecialPowers.pushPrefEnv({
      set: [
        ["browser.urlbar.suggest.quicksuggest.all", false],
        ["browser.urlbar.suggest.quicksuggest.sponsored", true],
        ["browser.urlbar.quicksuggest.online.enabled", true],
      ],
    });

    await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });

    let doc = gBrowser.selectedBrowser.contentDocument;
    let allCheckbox = doc.getElementById("firefoxSuggestAll");
    let sponsoredCheckbox = doc.getElementById("firefoxSuggestSponsored");
    let onlineEnabledCheckbox = doc.getElementById(
      "firefoxSuggestOnlineEnabledToggle"
    );

    // Initial state.
    Assert.ok(!allCheckbox.checked, "firefoxSuggestAll should not be checked");
    Assert.ok(
      sponsoredCheckbox.disabled,
      "sponsoredCheckbox should initially be disabled"
    );
    Assert.ok(
      onlineEnabledCheckbox.disabled,
      "onlineEnabledCheckbox should initially be disabled"
    );

    gBrowser.removeCurrentTab();

    await SpecialPowers.popPrefEnv();
  }
);

// Tests the "Restore" button for dismissed suggestions.
add_task(async function restoreDismissedSuggestions() {
  // Start with no dismissed suggestions.
  await QuickSuggest.clearDismissedSuggestions();
  Assert.ok(
    !(await QuickSuggest.canClearDismissedSuggestions()),
    "canClearDismissedSuggestions should be false after clearing suggestions"
  );

  await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });

  let doc = gBrowser.selectedBrowser.contentDocument;
  let addressBarSection = doc.getElementById("locationBarGroup");
  addressBarSection.scrollIntoView();

  let button = doc.getElementById(BUTTON_RESTORE_DISMISSED_ID);
  Assert.ok(button.disabled, "Restore button is disabled initially.");

  await QuickSuggest.dismissResult(QuickSuggestTestUtils.ampResult());

  Assert.ok(
    await QuickSuggest.canClearDismissedSuggestions(),
    "canClearDismissedSuggestions should return true after dismissing a suggestion"
  );
  await TestUtils.waitForCondition(
    () => !button.disabled,
    "Waiting for Restore button to become enabled after dismissing suggestion"
  );
  Assert.ok(
    !button.disabled,
    "Restore button should be enabled after dismissing suggestion"
  );

  let clearPromise = TestUtils.topicObserved("quicksuggest-dismissals-cleared");
  button.click();
  await clearPromise;

  Assert.ok(
    !(await QuickSuggest.canClearDismissedSuggestions()),
    "canClearDismissedSuggestions should return false after restoring dismissals"
  );
  await TestUtils.waitForCondition(
    () => button.disabled,
    "Waiting for Restore button to become disabled after clicking it"
  );
  Assert.ok(
    button.disabled,
    "Restore button should be disabled after clearing suggestions"
  );

  gBrowser.removeCurrentTab();
});

// If the pane is open while Suggest is still initializing and there are
// dismissed suggestions, the "Restore" button should become enabled when init
// finishes.
add_task(async function restoreDismissedSuggestions_init_enabled() {
  // Dismiss a suggestion.
  await QuickSuggest.dismissResult(QuickSuggestTestUtils.ampResult());
  Assert.ok(
    await QuickSuggest.canClearDismissedSuggestions(),
    "canClearDismissedSuggestions should be true after dismissing suggestion"
  );

  await doRestoreInitTest(async button => {
    // The button should become enabled since we dismissed a suggestion above.
    await TestUtils.waitForCondition(
      () => !button.disabled,
      "Waiting for Restore button to become enabled after re-enabling Rust backend"
    );
    Assert.ok(
      !button.disabled,
      "Restore button should be enabled after re-enabling Rust backend"
    );
  });
});

// If the pane is open while Suggest is still initializing and there are no
// dismissed suggestions, the "Restore" button should remain disabled when init
// finishes.
add_task(async function restoreDismissedSuggestions_init_disabled() {
  // Clear dismissed suggestions.
  await QuickSuggest.clearDismissedSuggestions();
  Assert.ok(
    !(await QuickSuggest.canClearDismissedSuggestions()),
    "canClearDismissedSuggestions should be false after clearing suggestions"
  );

  await doRestoreInitTest(async button => {
    // The button should remain disabled since there are no dismissed
    // suggestions.
    await TestUtils.waitForTick();
    Assert.ok(
      button.disabled,
      "Restore button should remain disabled after re-enabling Rust backend"
    );
  });
});

async function doRestoreInitTest(checkButton) {
  // Disable the Suggest Rust backend, which manages individually dismissed
  // suggestions. While Rust is disabled, Suggest won't be able to tell whether
  // there are any individually dismissed suggestions.
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.quicksuggest.rustEnabled", false]],
  });

  // Open the pane.
  await openPreferencesViaOpenPreferencesAPI("search", { leaveOpen: true });

  let doc = gBrowser.selectedBrowser.contentDocument;
  let addressBarSection = doc.getElementById("locationBarGroup");
  addressBarSection.scrollIntoView();

  let button = doc.getElementById(BUTTON_RESTORE_DISMISSED_ID);
  Assert.ok(button.disabled, "Restore button is disabled initially.");

  // Re-enable the Rust backend. It will send `quicksuggest-dismissals-changed`
  // when it finishes initialization.
  let changedPromise = TestUtils.topicObserved(
    "quicksuggest-dismissals-changed"
  );
  await SpecialPowers.popPrefEnv();

  info(
    "Waiting for quicksuggest-dismissals-changed after re-enabling Rust backend"
  );
  await changedPromise;

  await checkButton(button);

  // Clean up.
  await QuickSuggest.clearDismissedSuggestions();
  gBrowser.removeCurrentTab();
}
