"use strict";

/**
 * Tests that private browsing windows substitute the built-in private-window
 * theme when Nova is enabled and the active user theme is the default or an
 * in-App built-in. Third-party and non-inApp themes (e.g. alpenglow)
 * are left unchanged.
 */

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

const DEFAULT_THEME_ID = "default-theme@mozilla.org";
const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
const ALPENGLOW_THEME_ID = "firefox-alpenglow@mozilla.org";
const PRIVATE_THEME_ID = "firefox-privatewindow@mozilla.org";

async function checkWindowTheme(win, { effectiveThemeId, expectInApp }) {
  let root = win.document.documentElement;
  await TestUtils.waitForCondition(
    () => root.getAttribute("theme-effective-id") === effectiveThemeId,
    `Waiting for theme-effective-id to be ${effectiveThemeId}`
  );
  Assert.equal(
    root.getAttribute("theme-effective-id"),
    effectiveThemeId,
    "Window should apply the expected theme."
  );
  Assert.equal(
    root.hasAttribute("theme-in-app"),
    expectInApp,
    `theme-in-app attribute should${expectInApp ? "" : " not"} be set.`
  );
}

/**
 * Open a normal and a private window, assert their effective theme, then clean
 * up.
 *
 * @param {object} options
 * @param {string} options.normalThemeId - Expected effective theme id in a
 *        normal browsing window.
 * @param {string} options.privateThemeId - Expected effective theme id in a
 *        private browsing window.
 * @param {boolean} options.expectInApp - Expected theme-in-app attribute state
 *        (the same for both windows in every case we test).
 */
async function testNormalAndPrivate({
  normalThemeId,
  privateThemeId,
  expectInApp,
}) {
  await checkWindowTheme(window, {
    effectiveThemeId: normalThemeId,
    expectInApp,
  });

  await checkWindowTheme(privateWin, {
    effectiveThemeId: privateThemeId,
    expectInApp,
  });
}

let privateWin = null;

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.nova.enabled", true]],
  });
  // Ensure the built-in themes are initialized.
  await BuiltInThemes.ensureBuiltInThemes();

  // Switching to a theme and back reaches a consistent themeData state, so the
  // default-theme case below sees fully-populated theme data. See the matching
  // note in browser_ext_themes_pbm.js.
  let lightTheme = await AddonManager.getAddonByID(LIGHT_THEME_ID);
  await lightTheme.enable();
  await lightTheme.disable();

  privateWin = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });

  registerCleanupFunction(async () => {
    await BrowserTestUtils.closeWindow(privateWin);
  });
});

// Default theme: private windows substitute the private-window theme; both
// windows are considered in-app.
add_task(async function test_default_theme() {
  await testNormalAndPrivate({
    normalThemeId: DEFAULT_THEME_ID,
    privateThemeId: PRIVATE_THEME_ID,
    expectInApp: true,
  });
});

// Dark built-in (inApp): gets the new theme in private windows
add_task(async function test_dark_theme() {
  let darkTheme = await AddonManager.getAddonByID(DARK_THEME_ID);
  await darkTheme.enable();

  await testNormalAndPrivate({
    normalThemeId: DARK_THEME_ID,
    privateThemeId: PRIVATE_THEME_ID,
    expectInApp: true,
  });

  await darkTheme.disable();
});

// Light built-in (inApp): get the new theme in private windows
add_task(async function test_light_theme() {
  let lightTheme = await AddonManager.getAddonByID(LIGHT_THEME_ID);
  await lightTheme.enable();

  await testNormalAndPrivate({
    normalThemeId: LIGHT_THEME_ID,
    privateThemeId: PRIVATE_THEME_ID,
    expectInApp: true,
  });

  await lightTheme.disable();
});

// Alpenglow (built-in, but not inApp): no substitution in private windows, and
// theme-in-app is not set in either window.
add_task(async function test_alpenglow_theme() {
  let alpenglowTheme = await AddonManager.getAddonByID(ALPENGLOW_THEME_ID);
  await alpenglowTheme.enable();

  await testNormalAndPrivate({
    normalThemeId: ALPENGLOW_THEME_ID,
    privateThemeId: ALPENGLOW_THEME_ID,
    expectInApp: false,
  });

  await alpenglowTheme.disable();
});

// With browser.theme.dark-private-windows disabled, private windows should not
// substitute the private-window theme even with the default theme and Nova
// enabled. theme-in-app still reflects the user's (default) theme.
add_task(async function test_dark_private_windows_disabled() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.theme.dark-private-windows", false]],
  });

  // The pref is read per-update with no observer, so open a fresh private
  // window to pick up the disabled state at construction.
  let privateWinNoSub = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });

  await checkWindowTheme(privateWinNoSub, {
    effectiveThemeId: DEFAULT_THEME_ID,
    expectInApp: true,
  });

  await BrowserTestUtils.closeWindow(privateWinNoSub);
  await SpecialPowers.popPrefEnv();
});
