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

"use strict";

add_task(function test() {
  Assert.throws(
    () => UrlbarPrefs.get("browser.migration.version"),
    /Trying to access an unknown pref/,
    "Should throw when passing an untracked pref"
  );

  Assert.throws(
    () => UrlbarPrefs.set("browser.migration.version", 100),
    /Trying to access an unknown pref/,
    "Should throw when passing an untracked pref"
  );
  Assert.throws(
    () => UrlbarPrefs.set("maxRichResults", "10"),
    /Invalid value/,
    "Should throw when passing an invalid value type"
  );

  Assert.deepEqual(UrlbarPrefs.get("formatting.enabled"), true);
  UrlbarPrefs.set("formatting.enabled", false);
  Assert.deepEqual(UrlbarPrefs.get("formatting.enabled"), false);

  Assert.deepEqual(UrlbarPrefs.get("maxRichResults"), 10);
  UrlbarPrefs.set("maxRichResults", 6);
  Assert.deepEqual(UrlbarPrefs.get("maxRichResults"), 6);

  Assert.deepEqual(UrlbarPrefs.get("intentThreshold"), 0.5);
  UrlbarPrefs.set("intentThreshold", 0.75);
  // Due to rounding errors, floats are slightly imprecise, so we can't
  // directly compare what we set to what we retrieve.
  Assert.deepEqual(
    parseFloat(UrlbarPrefs.get("intentThreshold").toFixed(2)),
    0.75
  );
});

const EXPECTED_SUGGESTIONS_FIRST_GROUPS = {
  children: [
    // heuristic
    {
      maxResultCount: 1,
      children: [
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_ENGINE_ALIAS },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_BOOKMARK_KEYWORD },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TOKEN_ALIAS_ENGINE },
        {
          group: UrlbarUtils.RESULT_GROUP.HEURISTIC_RESTRICT_KEYWORD_AUTOFILL,
        },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_HISTORY_URL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK },
      ],
    },
    // extensions using the omnibox API
    {
      group: UrlbarUtils.RESULT_GROUP.OMNIBOX,
    },
    // main group
    {
      flexChildren: true,
      children: [
        // suggestions
        {
          flex: 2,
          children: [
            {
              flexChildren: true,
              children: [
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY,
                },
                {
                  flex: 99,
                  group: UrlbarUtils.RESULT_GROUP.RECENT_SEARCH,
                },
                {
                  flex: 4,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.TAIL_SUGGESTION,
            },
          ],
        },
        // general
        {
          group: UrlbarUtils.RESULT_GROUP.GENERAL_PARENT,
          flex: 1,
          children: [
            {
              availableSpan: 3,
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
            {
              flexChildren: true,
              children: [
                {
                  flex: 1,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_TAB,
                },
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.GENERAL,
                  orderBy: "frecency",
                },
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.ABOUT_PAGES,
                },
                {
                  flex: 99,
                  group: UrlbarUtils.RESULT_GROUP.RESTRICT_SEARCH_KEYWORD,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
          ],
        },
      ],
    },
  ],
};

const EXPECTED_NOT_SUGGESTIONS_FIRST_GROUPS = {
  children: [
    // heuristic
    {
      maxResultCount: 1,
      children: [
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_ENGINE_ALIAS },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_BOOKMARK_KEYWORD },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TOKEN_ALIAS_ENGINE },
        {
          group: UrlbarUtils.RESULT_GROUP.HEURISTIC_RESTRICT_KEYWORD_AUTOFILL,
        },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_HISTORY_URL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK },
      ],
    },
    // extensions using the omnibox API
    {
      group: UrlbarUtils.RESULT_GROUP.OMNIBOX,
    },
    // main group
    {
      flexChildren: true,
      children: [
        // general
        {
          group: UrlbarUtils.RESULT_GROUP.GENERAL_PARENT,
          flex: 2,
          children: [
            {
              availableSpan: 3,
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
            {
              flexChildren: true,
              children: [
                {
                  flex: 1,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_TAB,
                },
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.GENERAL,
                  orderBy: "frecency",
                },
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.ABOUT_PAGES,
                },
                {
                  flex: 99,
                  group: UrlbarUtils.RESULT_GROUP.RESTRICT_SEARCH_KEYWORD,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
          ],
        },
        // suggestions
        {
          flex: 1,
          children: [
            {
              flexChildren: true,
              children: [
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY,
                },
                {
                  flex: 99,
                  group: UrlbarUtils.RESULT_GROUP.RECENT_SEARCH,
                },
                {
                  flex: 4,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.TAIL_SUGGESTION,
            },
          ],
        },
      ],
    },
  ],
};

const EXPECTED_SMARTBAR_SUGGESTIONS_FIRST_GROUPS = {
  children: [
    // heuristic
    {
      maxResultCount: 1,
      children: [
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_HISTORY_URL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AI_CHAT },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK },
      ],
    },
    // main group
    {
      flexChildren: true,
      children: [
        // suggestions
        {
          flex: 2,
          children: [
            {
              availableSpan: 2,
              group: UrlbarUtils.RESULT_GROUP.AI,
            },
            {
              flexChildren: true,
              children: [
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY,
                },
                {
                  flex: 99,
                  group: UrlbarUtils.RESULT_GROUP.RECENT_SEARCH,
                },
                {
                  flex: 4,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.TAIL_SUGGESTION,
            },
          ],
        },
        // general
        {
          flex: 1,
          group: UrlbarUtils.RESULT_GROUP.GENERAL_PARENT,
          children: [
            {
              availableSpan: 3,
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
            {
              flexChildren: true,
              children: [
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.GENERAL,
                  orderBy: "frecency",
                },
                {
                  flex: 1,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_TAB,
                },
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.ABOUT_PAGES,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
          ],
        },
      ],
    },
  ],
};

const EXPECTED_SMARTBAR_NOT_SUGGESTIONS_FIRST_GROUPS = {
  children: [
    // heuristic
    {
      maxResultCount: 1,
      children: [
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_HISTORY_URL },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AI_CHAT },
        { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK },
      ],
    },
    // main group
    {
      flexChildren: true,
      children: [
        // general
        {
          flex: 2,
          group: UrlbarUtils.RESULT_GROUP.GENERAL_PARENT,
          children: [
            {
              availableSpan: 3,
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
            {
              flexChildren: true,
              children: [
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.GENERAL,
                  orderBy: "frecency",
                },
                {
                  flex: 1,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_TAB,
                },
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.ABOUT_PAGES,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
            },
          ],
        },
        // suggestions
        {
          flex: 1,
          children: [
            {
              availableSpan: 2,
              group: UrlbarUtils.RESULT_GROUP.AI,
            },
            {
              flexChildren: true,
              children: [
                {
                  flex: 2,
                  group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY,
                },
                {
                  flex: 99,
                  group: UrlbarUtils.RESULT_GROUP.RECENT_SEARCH,
                },
                {
                  flex: 4,
                  group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION,
                },
              ],
            },
            {
              group: UrlbarUtils.RESULT_GROUP.TAIL_SUGGESTION,
            },
          ],
        },
      ],
    },
  ],
};

// Tests interaction between showSearchSuggestionsFirst and resultGroups.
add_task(function showSearchSuggestionsFirst_resultGroups() {
  // Check initial values.
  Assert.equal(
    UrlbarPrefs.get("showSearchSuggestionsFirst"),
    true,
    "showSearchSuggestionsFirst is true initially"
  );
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "urlbar", searchString: "test" },
    }),
    EXPECTED_SUGGESTIONS_FIRST_GROUPS,
    "resultGroups is the same as the groups for which showSearchSuggestionsFirst is true"
  );

  UrlbarPrefs.set("showSearchSuggestionsFirst", false);
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "urlbar", searchString: "test" },
    }),
    EXPECTED_NOT_SUGGESTIONS_FIRST_GROUPS,
    "resultGroups is updated after setting showSearchSuggestionsFirst = false"
  );

  UrlbarPrefs.set("showSearchSuggestionsFirst", true);
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "urlbar", searchString: "test" },
    }),
    EXPECTED_SUGGESTIONS_FIRST_GROUPS,
    "resultGroups is updated after setting showSearchSuggestionsFirst = true"
  );

  // Set showSearchSuggestionsFirst = false again so we can clear it next.
  UrlbarPrefs.set("showSearchSuggestionsFirst", false);
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "urlbar", searchString: "test" },
    }),
    EXPECTED_NOT_SUGGESTIONS_FIRST_GROUPS,
    "resultGroups is updated after setting showSearchSuggestionsFirst = false"
  );

  // Clear showSearchSuggestionsFirst.
  Services.prefs.clearUserPref("browser.urlbar.showSearchSuggestionsFirst");
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "urlbar", searchString: "test" },
    }),
    EXPECTED_SUGGESTIONS_FIRST_GROUPS,
    "resultGroups is updated immediately after clearing showSearchSuggestionsFirst"
  );
  Assert.equal(
    UrlbarPrefs.get("showSearchSuggestionsFirst"),
    true,
    "showSearchSuggestionsFirst defaults to true after clearing it"
  );
});

// Tests interaction between showSearchSuggestionsFirst and smartbar resultGroups.
add_task(function showSearchSuggestionsFirst_smartbar_resultGroups() {
  Assert.equal(
    UrlbarPrefs.get("showSearchSuggestionsFirst"),
    true,
    "showSearchSuggestionsFirst is true initially"
  );
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "smartbar", searchString: "test" },
    }),
    EXPECTED_SMARTBAR_SUGGESTIONS_FIRST_GROUPS,
    "smartbar resultGroups has suggestions first when showSearchSuggestionsFirst is true"
  );

  UrlbarPrefs.set("showSearchSuggestionsFirst", false);
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "smartbar", searchString: "test" },
    }),
    EXPECTED_SMARTBAR_NOT_SUGGESTIONS_FIRST_GROUPS,
    "smartbar resultGroups is updated after setting showSearchSuggestionsFirst = false"
  );

  // Clear showSearchSuggestionsFirst.
  Services.prefs.clearUserPref("browser.urlbar.showSearchSuggestionsFirst");
  Assert.deepEqual(
    UrlbarPrefs.getResultGroups({
      context: { sapName: "smartbar", searchString: "test" },
    }),
    EXPECTED_SMARTBAR_SUGGESTIONS_FIRST_GROUPS,
    "smartbar resultGroups is updated immediately after clearing showSearchSuggestionsFirst"
  );
  Assert.equal(
    UrlbarPrefs.get("showSearchSuggestionsFirst"),
    true,
    "showSearchSuggestionsFirst defaults to true after clearing it"
  );
});

// Tests whether observer.onNimbusChanged works.
add_task(async function onNimbusChanged() {
  Services.prefs.setBoolPref(
    "browser.urlbar.autoFill.adaptiveHistory.enabled",
    false
  );

  // Add an observer that throws an Error and an observer that does not define
  // anything to check whether the other observers can get notifications.
  UrlbarPrefs.addObserver({
    onPrefChanged() {
      throw new Error("From onPrefChanged");
    },
    onNimbusChanged() {
      throw new Error("From onNimbusChanged");
    },
  });
  UrlbarPrefs.addObserver({});

  const observer = {
    onPrefChanged(pref) {
      this.prefChangedList.push(pref);
    },
    onNimbusChanged(pref) {
      this.nimbusChangedList.push(pref);
    },
  };
  observer.prefChangedList = [];
  observer.nimbusChangedList = [];
  UrlbarPrefs.addObserver(observer);

  const doCleanup = await UrlbarTestUtils.initNimbusFeature({
    autoFillAdaptiveHistoryEnabled: true,
  });
  Assert.equal(observer.prefChangedList.length, 0);
  Assert.ok(
    observer.nimbusChangedList.includes("autoFillAdaptiveHistoryEnabled")
  );
  await doCleanup();
});

// Tests whether observer.onPrefChanged works.
add_task(async function onPrefChanged() {
  const doCleanup = await UrlbarTestUtils.initNimbusFeature({
    autoFillAdaptiveHistoryEnabled: false,
  });
  Services.prefs.setBoolPref(
    "browser.urlbar.autoFill.adaptiveHistory.enabled",
    false
  );

  // Add an observer that throws an Error and an observer that does not define
  // anything to check whether the other observers can get notifications.
  UrlbarPrefs.addObserver({
    onPrefChanged() {
      throw new Error("From onPrefChanged");
    },
    onNimbusChanged() {
      throw new Error("From onNimbusChanged");
    },
  });
  UrlbarPrefs.addObserver({});

  const deferred = Promise.withResolvers();
  const observer = {
    onPrefChanged(pref) {
      this.prefChangedList.push(pref);
      deferred.resolve();
    },
    onNimbusChanged(pref) {
      this.nimbusChangedList.push(pref);
      deferred.resolve();
    },
  };
  observer.prefChangedList = [];
  observer.nimbusChangedList = [];
  UrlbarPrefs.addObserver(observer);

  Services.prefs.setBoolPref(
    "browser.urlbar.autoFill.adaptiveHistory.enabled",
    true
  );
  await deferred.promise;
  Assert.equal(observer.prefChangedList.length, 1);
  Assert.equal(observer.prefChangedList[0], "autoFill.adaptiveHistory.enabled");
  Assert.equal(observer.nimbusChangedList.length, 0);

  Services.prefs.clearUserPref(
    "browser.urlbar.autoFill.adaptiveHistory.enabled"
  );
  await doCleanup();
});

// Tests add function.
add_task(async function add() {
  info("Start from empty value");
  Services.prefs.setStringPref(
    "browser.urlbar.quicksuggest.realtimeOptIn.notNowTypes",
    ""
  );
  let result = UrlbarPrefs.get("quicksuggest.realtimeOptIn.notNowTypes");
  Assert.equal(result.size, 0);
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "a");
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "b");
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "a");
  result = UrlbarPrefs.get("quicksuggest.realtimeOptIn.notNowTypes");
  Assert.equal(result.size, 2);
  Assert.ok(result.has("a"));
  Assert.ok(result.has("b"));

  info("Start from some values");
  Services.prefs.setStringPref(
    "browser.urlbar.quicksuggest.realtimeOptIn.notNowTypes",
    "a,b,c"
  );
  result = UrlbarPrefs.get("quicksuggest.realtimeOptIn.notNowTypes");
  Assert.equal(result.size, 3);
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "a");
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "b");
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "a");
  UrlbarPrefs.add("quicksuggest.realtimeOptIn.notNowTypes", "d");
  result = UrlbarPrefs.get("quicksuggest.realtimeOptIn.notNowTypes");
  Assert.equal(result.size, 4);
  Assert.ok(result.has("a"));
  Assert.ok(result.has("b"));
  Assert.ok(result.has("c"));
  Assert.ok(result.has("d"));

  info("Test for singular value pref");
  Assert.throws(
    () => UrlbarPrefs.add("merino.providers", "a"),
    /The pref merino.providers should handle the values as Set but 'string'/
  );
  Assert.throws(
    () => UrlbarPrefs.add("addons.featureGate", true),
    /The pref addons.featureGate should handle the values as Set but 'boolean'/
  );
});
