ChromeUtils.defineESModuleGetters(this, {
  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
});

const TRANSITION_LINK = PlacesUtils.history.TRANSITION_LINK;
const TRANSITION_TYPED = PlacesUtils.history.TRANSITION_TYPED;
const TRANSITION_BOOKMARK = PlacesUtils.history.TRANSITION_BOOKMARK;
const TRANSITION_REDIRECT_PERMANENT =
  PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT;
const TRANSITION_REDIRECT_TEMPORARY =
  PlacesUtils.history.TRANSITION_REDIRECT_TEMPORARY;
const TRANSITION_EMBED = PlacesUtils.history.TRANSITION_EMBED;
const TRANSITION_FRAMED_LINK = PlacesUtils.history.TRANSITION_FRAMED_LINK;
const TRANSITION_DOWNLOAD = PlacesUtils.history.TRANSITION_DOWNLOAD;

function whenNewWindowLoaded(aOptions, aCallback) {
  BrowserTestUtils.waitForNewWindow().then(aCallback);
  OpenBrowserWindow(aOptions);
}

async function clearHistoryAndHistoryCache() {
  await PlacesUtils.history.clear();
  // Clear HistoryRestiction cache as well.
  Cc["@mozilla.org/browser/history;1"]
    .getService(Ci.mozIAsyncHistory)
    .clearCache();
}

async function synthesizeVisitByUser(browser, url) {
  let onNewTab = BrowserTestUtils.waitForNewTab(
    browser.documentGlobal.gBrowser
  );
  // We intentionally turn off this a11y check, because the following click is
  // purposefully sent on an arbitrary web content that is not expected to be
  // tested by itself with the browser mochitests, therefore this rule check
  // shall be ignored by a11y_checks suite.
  AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
  await SpecialPowers.spawn(browser, [url], async href => {
    EventUtils.synthesizeMouseAtCenter(
      content.document.querySelector(`a[href='${href}'`),
      {},
      content
    );
  });
  AccessibilityUtils.resetEnv();
  let tab = await onNewTab;
  BrowserTestUtils.removeTab(tab);
}

async function synthesizeVisitByScript(browser, url) {
  let onNewTab = BrowserTestUtils.waitForNewTab(
    browser.documentGlobal.gBrowser
  );
  AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
  await SpecialPowers.spawn(browser, [url], async href => {
    let a = content.document.querySelector(`a[href='${href}'`);
    a.click();
  });
  AccessibilityUtils.resetEnv();
  let tab = await onNewTab;
  BrowserTestUtils.removeTab(tab);
}

async function assertLinkVisitedStatus(
  browser,
  url,
  { visitCount: expectedVisitCount, isVisited: expectedVisited }
) {
  await BrowserTestUtils.waitForCondition(async () => {
    let visitCount =
      (await PlacesTestUtils.getDatabaseValue("moz_places", "visit_count", {
        url,
      })) ?? 0;

    if (visitCount != expectedVisitCount) {
      return false;
    }

    Assert.equal(visitCount, expectedVisitCount, "The visit count is correct");
    return true;
  });

  await SpecialPowers.spawn(
    browser,
    [url, expectedVisited],
    async (href, visited) => {
      // ElementState::VISITED
      const VISITED_STATE = 1 << 18;
      await ContentTaskUtils.waitForCondition(() => {
        let isVisited = !!(
          content.InspectorUtils.getContentState(
            content.document.querySelector(`a[href='${href}']`)
          ) & VISITED_STATE
        );
        return isVisited == visited;
      });
    }
  );
  Assert.ok(true, "The visited state is corerct");
}

async function checkFrecencyEqual(url1, url2) {
  let frecency1 = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: url1,
    }
  );
  let frecency2 = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: url2,
    }
  );
  Assert.equal(
    frecency1,
    frecency2,
    `Frecency of ${url1} is equal to frecency of ${url2}.`
  );
}

async function checkFrecencyGreater(url1, url2) {
  let frecency1 = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: url1,
    }
  );
  let frecency2 = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "frecency",
    {
      url: url2,
    }
  );
  Assert.greater(
    frecency1,
    frecency2,
    `Frecency of ${url1} is greater than frecency of ${url2}.`
  );
}

async function checkFrecencyNonZero(url) {
  Assert.greater(
    await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
      url,
    }),
    0,
    `Frecency of ${url} is greater than 0.`
  );
}

async function checkUrlHidden(url, expectedHidden) {
  let hiddenState = await PlacesTestUtils.getDatabaseValue(
    "moz_places",
    "hidden",
    {
      url,
    }
  );
  Assert.equal(
    hiddenState,
    expectedHidden,
    `URL ${url} is ${expectedHidden ? "hidden" : "not hidden."}`
  );
}

async function checkRedirect(
  redirectUrl,
  targetUrl,
  intermediateUrls = [],
  isTyped = false
) {
  // First, check redirect and intermediate urls are hidden and the target URL
  // is not hidden.
  await checkUrlHidden(redirectUrl, 1);
  for (let intermediateUrl of intermediateUrls) {
    await checkUrlHidden(intermediateUrl, 1);
  }
  await checkUrlHidden(targetUrl, 0);

  // Check the frecency values of redirect URLs are expected.
  await checkFrecencyNonZero(redirectUrl);
  for (let intermediateUrl of intermediateUrls) {
    if (isTyped) {
      await checkFrecencyGreater(redirectUrl, intermediateUrl);
    } else {
      await checkFrecencyEqual(redirectUrl, intermediateUrl);
    }
  }

  // Check intermediate urls have the same frecency.
  let intermediateUrlObj = [];
  for (let intermediateUrl of intermediateUrls) {
    let frecency = await PlacesTestUtils.getDatabaseValue(
      "moz_places",
      "frecency",
      {
        url: intermediateUrl,
      }
    );
    intermediateUrlObj.push({ url: intermediateUrl, frecency });
  }
  if (intermediateUrlObj.length) {
    let expected = intermediateUrlObj[0];
    for (let i = 1; i < intermediateUrlObj.length; ++i) {
      Assert.equal(
        intermediateUrlObj[i].frecency,
        expected.frecency,
        `Frecency of intermediate url ${intermediateUrlObj[i].url} should match ${expected.url}`
      );
    }
  }

  // Check the target URL has a higher frecency than the intermediate URLs.
  for (let intermediateUrl of intermediateUrls) {
    await checkFrecencyGreater(targetUrl, intermediateUrl);
  }

  if (isTyped && intermediateUrls.length) {
    await checkFrecencyGreater(redirectUrl, targetUrl);
  } else {
    await checkFrecencyGreater(targetUrl, redirectUrl);
  }
}
