/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

var { MailServices } = ChromeUtils.importESModule(
  "resource:///modules/MailServices.sys.mjs"
);
var { MailUtils } = ChromeUtils.importESModule(
  "resource:///modules/MailUtils.sys.mjs"
);
var { MessageGenerator } = ChromeUtils.importESModule(
  "resource://testing-common/mailnews/MessageGenerator.sys.mjs"
);

let folderA, messagesA, folderB, messagesB;

add_setup(async function () {
  const tabmail = document.getElementById("tabmail");
  Assert.equal(tabmail.tabInfo.length, 1, "should be set up with one tab");
  tabmail.closeOtherTabs(0);

  const generator = new MessageGenerator();

  const account = MailServices.accounts.createLocalMailAccount();
  const rootFolder = account.incomingServer.rootFolder.QueryInterface(
    Ci.nsIMsgLocalMailFolder
  );

  folderA = rootFolder
    .createLocalSubfolder("mailTabsA")
    .QueryInterface(Ci.nsIMsgLocalMailFolder);
  folderA.addMessageBatch(
    generator
      .makeMessages({ count: 5 })
      .map(message => message.toMessageString())
  );
  messagesA = [...folderA.messages];

  folderB = rootFolder
    .createLocalSubfolder("mailTabsB")
    .QueryInterface(Ci.nsIMsgLocalMailFolder);
  folderB.addMessageBatch(
    generator
      .makeMessages({ count: 2 })
      .map(message => message.toMessageString())
  );
  messagesB = [...folderB.messages];

  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("mail.tabs.loadInBackground");
    MailServices.accounts.removeAccount(account, false);
  });
});

add_task(async function testTabs() {
  const tabmail = document.getElementById("tabmail");
  Assert.equal(tabmail.tabInfo.length, 1, "should start off with one tab open");
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [true]
  );
  Assert.equal(tabmail.currentTabInfo, tabmail.tabInfo[0], "should show tab 0");

  info("Check the first tab.");

  const firstTab = tabmail.currentTabInfo;
  Assert.equal(firstTab.mode.name, "mail3PaneTab");
  Assert.equal(firstTab.mode.tabType.name, "mailTab");

  const firstChromeBrowser = firstTab.chromeBrowser;
  Assert.equal(firstChromeBrowser.currentURI.spec, "about:3pane");
  Assert.equal(tabmail.currentAbout3Pane, firstChromeBrowser.contentWindow);

  const firstWebBrowser =
    firstChromeBrowser.contentDocument.getElementById("webBrowser");
  const firstMultiMessageBrowser =
    firstChromeBrowser.contentDocument.getElementById("multiMessageBrowser");
  const firstMessageBrowser =
    firstChromeBrowser.contentDocument.getElementById("messageBrowser");
  Assert.equal(firstMessageBrowser.currentURI.spec, "about:message");

  const firstMessagePane =
    firstMessageBrowser.contentDocument.getElementById("messagepane");
  Assert.equal(firstMessagePane.currentURI.spec, "about:blank");
  Assert.equal(
    tabmail.currentAboutMessage,
    null,
    "currentAboutMessage should be null with no message selected"
  );
  Assert.equal(firstTab.browser, null);
  Assert.equal(firstTab.linkedBrowser, null);

  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  const { folderTree, threadTree, messagePane, paneLayout } =
    firstChromeBrowser.contentWindow;

  firstTab.folder = folderA;
  Assert.equal(firstTab.folder, folderA);
  Assert.equal(
    folderTree.querySelector(".selected .name").textContent,
    "mailTabsA"
  );
  Assert.equal(threadTree.view.rowCount, 5);
  Assert.equal(threadTree.selectedIndex, -1);

  Assert.equal(firstTab.message, null);
  threadTree.selectedIndex = 0;
  Assert.equal(
    tabmail.currentAboutMessage,
    firstMessageBrowser.contentWindow,
    "currentAboutMessage should have a value with a message selected"
  );
  Assert.equal(firstTab.message, messagesA[0]);
  Assert.equal(firstTab.browser, firstMessagePane);
  Assert.equal(firstTab.linkedBrowser, firstMessagePane);
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    firstMessagePane.docShellIsActive,
    "first message browser should be visible"
  );

  Assert.ok(BrowserTestUtils.isVisible(folderTree));
  Assert.ok(BrowserTestUtils.isVisible(firstMessageBrowser));

  paneLayout.folderPaneVisible = false;
  Assert.ok(BrowserTestUtils.isHidden(folderTree));
  Assert.ok(BrowserTestUtils.isVisible(firstMessageBrowser));

  paneLayout.messagePaneVisible = false;
  Assert.ok(BrowserTestUtils.isHidden(folderTree));
  Assert.ok(BrowserTestUtils.isHidden(firstMessageBrowser));
  Assert.equal(
    tabmail.currentAboutMessage,
    null,
    "currentAboutMessage should be null with the message pane hidden"
  );
  Assert.equal(firstTab.browser, null);
  Assert.equal(firstTab.linkedBrowser, null);

  paneLayout.folderPaneVisible = true;
  Assert.ok(BrowserTestUtils.isVisible(folderTree));
  Assert.ok(BrowserTestUtils.isHidden(firstMessageBrowser));

  paneLayout.messagePaneVisible = true;
  Assert.ok(BrowserTestUtils.isVisible(folderTree));
  Assert.ok(BrowserTestUtils.isVisible(firstMessageBrowser));
  Assert.equal(
    tabmail.currentAboutMessage,
    firstMessageBrowser.contentWindow,
    "currentAboutMessage should have a value with the message pane shown"
  );
  Assert.equal(firstTab.browser, firstMessagePane);
  Assert.equal(firstTab.linkedBrowser, firstMessagePane);

  Assert.equal(firstChromeBrowser.contentWindow.tabOrWindow, firstTab);
  Assert.equal(firstMessageBrowser.contentWindow.tabOrWindow, firstTab);

  info("Select multiple messages.");

  threadTree.selectedIndices = [1, 2];
  Assert.ok(BrowserTestUtils.isHidden(firstWebBrowser));
  Assert.ok(BrowserTestUtils.isHidden(firstMessageBrowser));
  Assert.ok(BrowserTestUtils.isVisible(firstMultiMessageBrowser));
  Assert.equal(
    tabmail.currentAboutMessage,
    null,
    "currentAboutMessage should be null with multiple messages selected"
  );
  Assert.equal(firstTab.browser, null);
  Assert.equal(firstTab.linkedBrowser, null);
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  info("Load a web page.");

  const loadedPromise = BrowserTestUtils.browserLoaded(
    firstWebBrowser,
    false,
    "http://mochi.test:8888/"
  );
  messagePane.displayWebPage("http://mochi.test:8888/");
  await loadedPromise;
  Assert.ok(BrowserTestUtils.isVisible(firstWebBrowser));
  Assert.ok(BrowserTestUtils.isHidden(firstMessageBrowser));
  Assert.ok(BrowserTestUtils.isHidden(firstMultiMessageBrowser));
  Assert.equal(firstWebBrowser.currentURI.spec, "http://mochi.test:8888/");
  Assert.equal(
    tabmail.currentAboutMessage,
    null,
    "currentAboutMessage should be null with a web page loaded"
  );
  Assert.equal(firstTab.browser, firstWebBrowser);
  Assert.equal(firstTab.linkedBrowser, firstWebBrowser);
  Assert.ok(
    firstWebBrowser.docShellIsActive,
    "first web browser should be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  info("Go back to a single selection.");

  threadTree.selectedIndex = 0;
  Assert.ok(BrowserTestUtils.isHidden(firstWebBrowser));
  Assert.ok(BrowserTestUtils.isVisible(firstMessageBrowser));
  Assert.ok(BrowserTestUtils.isHidden(firstMultiMessageBrowser));
  Assert.equal(
    tabmail.currentAboutMessage,
    firstMessageBrowser.contentWindow,
    "currentAboutMessage should have a value with a single message selected"
  );
  Assert.equal(firstTab.browser, firstMessagePane);
  Assert.equal(firstTab.linkedBrowser, firstMessagePane);
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    firstMessagePane.docShellIsActive,
    "first message browser should be visible"
  );

  Services.prefs.setBoolPref("mail.tabs.loadInBackground", true);
  info("Open some more tabs. These should open in the background.");

  window.MsgOpenNewTabForFolders([folderB], {
    folderPaneVisible: true,
    messagePaneVisible: true,
  });

  for (const message of messagesB) {
    window.OpenMessageInNewTab(message, {});
  }

  Assert.equal(tabmail.tabInfo.length, 4);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [true, false, false, false]
  );
  Assert.equal(tabmail.currentTabInfo, firstTab);
  Assert.equal(tabmail.currentAbout3Pane, firstChromeBrowser.contentWindow);
  Assert.equal(tabmail.currentAboutMessage, firstMessageBrowser.contentWindow);

  info("Check the second tab.");

  const secondTab = tabmail.tabInfo[1];
  Assert.equal(secondTab.mode.name, "mail3PaneTab");
  Assert.equal(secondTab.mode.tabType.name, "mailTab");

  const secondChromeBrowser = secondTab.chromeBrowser;
  await ensureBrowserLoaded(secondChromeBrowser);
  Assert.equal(secondChromeBrowser.currentURI.spec, "about:3pane");

  const secondWebBrowser =
    secondChromeBrowser.contentDocument.getElementById("webBrowser");
  const secondMessageBrowser =
    secondChromeBrowser.contentDocument.getElementById("messageBrowser");
  await ensureBrowserLoaded(secondMessageBrowser);
  Assert.equal(secondMessageBrowser.currentURI.spec, "about:message");

  const secondMessagePane =
    secondMessageBrowser.contentDocument.getElementById("messagepane");
  Assert.equal(secondMessagePane.currentURI.spec, "about:blank");
  Assert.equal(secondTab.browser, null);
  Assert.equal(secondTab.linkedBrowser, null);
  Assert.equal(secondTab.folder, folderB);

  Assert.ok(
    !secondWebBrowser.docShellIsActive,
    "second web browser should NOT be visible"
  );
  Assert.ok(
    !secondMessagePane.docShellIsActive,
    "second message browser should NOT be visible"
  );

  tabmail.switchToTab(1);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [false, true, false, false]
  );
  Assert.equal(tabmail.currentTabInfo, tabmail.tabInfo[1]);
  Assert.equal(tabmail.currentAbout3Pane, secondChromeBrowser.contentWindow);
  Assert.equal(
    tabmail.currentAboutMessage,
    null,
    "currentAboutMessage should be null with no message selected"
  );

  Assert.ok(
    !secondWebBrowser.docShellIsActive,
    "second web browser should NOT be visible"
  );
  Assert.ok(
    !secondMessagePane.docShellIsActive,
    "second message browser should NOT be visible"
  );
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  secondChromeBrowser.contentWindow.threadTree.selectedIndex = 0;
  Assert.equal(
    tabmail.currentAboutMessage,
    secondMessageBrowser.contentWindow,
    "currentAboutMessage should have a value with a message selected"
  );
  Assert.ok(
    !secondWebBrowser.docShellIsActive,
    "second web browser should NOT be visible"
  );
  Assert.ok(
    secondMessagePane.docShellIsActive,
    "second message browser should be visible"
  );

  Assert.equal(secondChromeBrowser.contentWindow.tabOrWindow, secondTab);
  Assert.equal(secondMessageBrowser.contentWindow.tabOrWindow, secondTab);

  info("Check the third tab.");

  const thirdTab = tabmail.tabInfo[2];
  Assert.equal(thirdTab.mode.name, "mailMessageTab");
  Assert.equal(thirdTab.mode.tabType.name, "mailTab");

  const thirdChromeBrowser = thirdTab.chromeBrowser;
  await ensureBrowserLoaded(thirdChromeBrowser);
  Assert.equal(thirdChromeBrowser.currentURI.spec, "about:message");

  const thirdMessagePane =
    thirdChromeBrowser.contentDocument.getElementById("messagepane");
  Assert.equal(thirdMessagePane.currentURI.spec, messageToURL(messagesB[0]));
  Assert.equal(thirdTab.browser, thirdMessagePane);
  Assert.equal(thirdTab.linkedBrowser, thirdMessagePane);

  Assert.ok(
    !thirdMessagePane.docShellIsActive,
    "third message browser should NOT be visible"
  );

  tabmail.switchToTab(2);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [false, false, true, false]
  );
  Assert.equal(tabmail.currentTabInfo, tabmail.tabInfo[2]);
  Assert.equal(tabmail.currentAbout3Pane, null);
  Assert.equal(tabmail.currentAboutMessage, thirdChromeBrowser.contentWindow);

  Assert.ok(
    thirdMessagePane.docShellIsActive,
    "third message browser should be visible"
  );
  Assert.ok(
    !secondWebBrowser.docShellIsActive,
    "second web browser should NOT be visible"
  );
  Assert.ok(
    !secondMessagePane.docShellIsActive,
    "second message browser should NOT be visible"
  );

  Assert.equal(thirdTab.folder, folderB);
  Assert.equal(thirdTab.message, messagesB[0]);

  Assert.equal(thirdChromeBrowser.contentWindow.tabOrWindow, thirdTab);

  info("Check the fourth tab.");

  const fourthTab = tabmail.tabInfo[3];
  Assert.equal(fourthTab.mode.name, "mailMessageTab");
  Assert.equal(fourthTab.mode.tabType.name, "mailTab");

  const fourthChromeBrowser = fourthTab.chromeBrowser;
  await ensureBrowserLoaded(fourthChromeBrowser);
  Assert.equal(fourthChromeBrowser.currentURI.spec, "about:message");

  const fourthMessagePane =
    fourthChromeBrowser.contentDocument.getElementById("messagepane");
  Assert.equal(fourthMessagePane.currentURI.spec, messageToURL(messagesB[1]));
  Assert.equal(fourthTab.browser, fourthMessagePane);
  Assert.equal(fourthTab.linkedBrowser, fourthMessagePane);
  Assert.equal(fourthTab.folder, folderB);
  Assert.equal(fourthTab.message, messagesB[1]);

  Assert.ok(
    !fourthMessagePane.docShellIsActive,
    "fourth message browser should NOT be visible"
  );

  tabmail.switchToTab(3);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [false, false, false, true]
  );
  Assert.equal(tabmail.currentTabInfo, tabmail.tabInfo[3]);
  Assert.equal(tabmail.currentAbout3Pane, null);
  Assert.equal(tabmail.currentAboutMessage, fourthChromeBrowser.contentWindow);

  Assert.ok(
    fourthMessagePane.docShellIsActive,
    "fourth message browser should be visible"
  );
  Assert.ok(
    !thirdMessagePane.docShellIsActive,
    "third message browser should NOT be visible"
  );

  Assert.equal(fourthChromeBrowser.contentWindow.tabOrWindow, fourthTab);

  info("Close tabs.");

  tabmail.closeTab(3);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [false, false, true]
  );
  Assert.equal(tabmail.currentTabInfo, thirdTab);
  Assert.equal(tabmail.currentAbout3Pane, null);
  Assert.equal(tabmail.currentAboutMessage, thirdChromeBrowser.contentWindow);

  tabmail.closeTab(2);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [false, true]
  );
  Assert.equal(tabmail.currentTabInfo, secondTab);
  Assert.equal(tabmail.currentAbout3Pane, secondChromeBrowser.contentWindow);
  Assert.equal(tabmail.currentAboutMessage, secondMessageBrowser.contentWindow);

  tabmail.closeTab(1);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [true]
  );
  Assert.equal(tabmail.currentTabInfo, firstTab);
  Assert.equal(tabmail.currentAbout3Pane, firstChromeBrowser.contentWindow);
  Assert.equal(tabmail.currentAboutMessage, firstMessageBrowser.contentWindow);
  Assert.equal(firstTab.browser, firstMessagePane);
  Assert.equal(firstTab.linkedBrowser, firstMessagePane);
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    firstMessagePane.docShellIsActive,
    "first message browser should be visible"
  );

  Services.prefs.setBoolPref("mail.tabs.loadInBackground", false);
  info("Open a new mail tab. Focus should switch to the new tab.");

  window.OpenMessageInNewTab(messagesB[0], {});

  Assert.equal(tabmail.tabInfo.length, 2);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [false, true]
  );

  const secondOpenedTab = tabmail.tabInfo[1];
  Assert.equal(secondOpenedTab.mode.name, "mailMessageTab");
  Assert.equal(secondOpenedTab.mode.tabType.name, "mailTab");

  const secondOpenedChromeBrowser = secondOpenedTab.chromeBrowser;
  await ensureBrowserLoaded(secondOpenedChromeBrowser);
  Assert.equal(secondOpenedChromeBrowser.currentURI.spec, "about:message");

  const secondOpenedMessagePane =
    secondOpenedChromeBrowser.contentDocument.getElementById("messagepane");
  await ensureBrowserLoaded(secondOpenedMessagePane);
  Assert.equal(
    secondOpenedMessagePane.currentURI.spec,
    messageToURL(messagesB[0])
  );
  Assert.equal(secondOpenedTab.browser, secondOpenedMessagePane);
  Assert.equal(secondOpenedTab.linkedBrowser, secondOpenedMessagePane);

  tabmail.closeTab(1);
  Assert.equal(tabmail.tabInfo.length, 1);
  Assert.deepEqual(
    tabmail.tabInfo.map(t => t.selected),
    [true]
  );

  info("Open a content tab.");
  // This test isn't about content tabs, but we're testing docShell visibility
  // for everything else, and we need another tab to test about:3pane, so this
  // seemed like a good place to test docShell visibility for content tabs.

  const contentTab = tabmail.openTab("contentTab", {
    background: true,
    url: "https://example.org/browser/comm/mail/base/test/browser/files/sampleContent.html",
  });
  const contentTabBrowser = contentTab.browser;
  Assert.ok(
    !contentTabBrowser.docShellIsActive,
    "content tab browser should NOT be visible"
  );

  tabmail.switchToTab(contentTab);
  Assert.ok(
    contentTabBrowser.docShellIsActive,
    "content tab browser should be visible"
  );
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  info("While in the background, load a web page.");

  messagePane.displayWebPage("https://example.org/");
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  tabmail.switchToTab(firstTab);
  Assert.ok(
    !contentTabBrowser.docShellIsActive,
    "content tab browser should NOT be visible"
  );
  Assert.ok(
    firstWebBrowser.docShellIsActive,
    "first web browser should be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  tabmail.switchToTab(contentTab);
  Assert.ok(
    contentTabBrowser.docShellIsActive,
    "content tab browser should be visible"
  );
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  info("While in the background, load a message.");

  threadTree.selectedIndex = 1;
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );

  tabmail.closeTab(contentTab);
  Assert.ok(
    !firstWebBrowser.docShellIsActive,
    "first web browser should NOT be visible"
  );
  Assert.ok(
    firstMessagePane.docShellIsActive,
    "first message browser should be visible"
  );

  threadTree.selectedIndex = -1;
  Assert.ok(
    !firstMessagePane.docShellIsActive,
    "first message browser should NOT be visible"
  );
});

add_task(async function testMessageWindow() {
  const messageWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(
    undefined,
    async win =>
      win.document.documentURI ==
      "chrome://messenger/content/messageWindow.xhtml"
  );
  MailUtils.openMessageInNewWindow(messagesB[0]);

  const messageWindow = await messageWindowPromise;
  const messageBrowser = messageWindow.messageBrowser;
  await ensureBrowserLoaded(messageBrowser);
  Assert.equal(messageBrowser.contentWindow.tabOrWindow, messageWindow);

  await BrowserTestUtils.closeWindow(messageWindow);
});

async function ensureBrowserLoaded(browser) {
  await TestUtils.waitForCondition(
    () =>
      browser.currentURI.spec != "about:blank" &&
      browser.contentDocument.readyState == "complete",
    "waiting for browser to finish loading"
  );
}

function messageToURL(message) {
  const messageService =
    MailServices.messageServiceFromURI("mailbox-message://");
  const uri = message.folder.getUriForMsg(message);
  return messageService.getUrlForUri(uri).spec;
}
