/* 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/. */

"use strict";

const getCommonFiles = async () => {
  return {
    "utils.js": await getUtilsJS(),
    "common.js": () => {
      window.CreateTabPromise = class {
        constructor() {
          this.promise = new Promise(resolve => {
            const createListener = tab => {
              browser.tabs.onCreated.removeListener(createListener);
              resolve(tab);
            };
            browser.tabs.onCreated.addListener(createListener);
          });
        }
        async done() {
          return this.promise;
        }
      };

      window.UpdateTabPromise = class {
        constructor(options) {
          this.logWindowId = options?.logWindowId;
          this.promise = new Promise(resolve => {
            const updateLog = new Map();
            const updateListener = (tabId, changes, tab) => {
              const id = this.logWindowId ? tab.windowId : tabId;

              if (changes?.url != "about:blank") {
                const log = updateLog.get(id) || {};

                if (changes.url) {
                  log.url = changes.url;
                }
                // The complete is only valid, if we have seen a url (which was
                // not "about:blank")
                if (log.url && changes?.status == "complete") {
                  log.complete = true;
                }

                updateLog.set(id, log);
                if (log.url && log.complete) {
                  browser.tabs.onUpdated.removeListener(updateListener);
                  resolve(updateLog);
                }
              }
            };
            browser.tabs.onUpdated.addListener(updateListener);
          });
        }
        async verify(id, url) {
          // The updatePromise resolves after we have seen the "complete" state
          // and a url.
          const updateLog = await this.promise;
          browser.test.assertEq(
            1,
            updateLog.size,
            `Should have seen exactly one tab being updated - ${JSON.stringify(
              Array.from(updateLog)
            )}`
          );
          browser.test.assertTrue(
            updateLog.has(id),
            `Updates must belong to the current tab ${id}`
          );
          browser.test.assertEq(
            url,
            updateLog.get(id).url,
            "Should have seen the correct url loaded."
          );
        }
      };
    },
    "background.js": async () => {
      // Open a local extension page and click a handler link. They are all
      // expected to open in a new tab.
      const testSelectors = ["#link1", "#link2", "#link3", "#link4"];
      for (const linkSelector of testSelectors) {
        await window.expectLinkOpenInNewTab(
          browser.runtime.getURL("test.html"),
          linkSelector,
          browser.runtime.getURL("handler.html#ext%2Btest%3Apayload")
        );
      }
      browser.test.notifyPass();
    },
    "handler.html": `<!DOCTYPE html>
      <html>
        <head>
          <title>EXAMPLE</title>
          <meta charset="utf-8">
        </head>
        <body>
          <p>This is an example page</p>
        </body>
      </html>`,
    "test.html": `<!DOCTYPE html>
      <html>
        <head>
          <title>TEST</title>
          <meta charset="utf-8">
        </head>
        <body>
          <ul>
            <li><a id="link1" href="ext+test:payload">extension handler without target</a>
            <li><a id="link2" href="ext+test:payload" target = "_self">extension handler with _self target</a>
            <li><a id="link3" href="ext+test:payload" target = "_blank">extension handler with _blank target</a>
            <li><a id="link4" href="ext+test:payload" target = "_other">extension handler with _other target</a>
          </ul>
        </body>
      </html>`,
  };
};

const subtest_clickInBrowser = async (extension, getBrowser) => {
  async function clickLink(linkSelector, browser) {
    await awaitBrowserLoaded(browser, url => url != "about:blank");
    await synthesizeMouseAtCenterAndRetry(linkSelector, {}, browser);
  }

  await extension.startup();

  const testSelectors = ["#link1", "#link2", "#link3", "#link4"];

  for (const expectedSelector of testSelectors) {
    // Wait for click on link (new tab)
    const { linkSelector } = await extension.awaitMessage("click");
    Assert.equal(
      expectedSelector,
      linkSelector,
      `Test should click on the correct link.`
    );
    await clickLink(linkSelector, getBrowser());
    await extension.sendMessage();
  }

  await extension.awaitFinish();
  await extension.unload();
};

add_setup(async () => {
  const account = createAccount();
  const rootFolder = account.incomingServer.rootFolder;
  await createSubfolder(rootFolder, "test0");

  const subFolders = {};
  for (const folder of rootFolder.subFolders) {
    subFolders[folder.name] = folder;
  }
  await createMessages(subFolders.test0, 5);

  const about3Pane = document.getElementById("tabmail").currentAbout3Pane;
  about3Pane.displayFolder(subFolders.test0.URI);
});

add_task(async function test_tabs() {
  const extension = ExtensionTestUtils.loadExtension({
    files: {
      "tabFunctions.js": async () => {
        const openTestTab = async url => {
          const createdTestTab = new window.CreateTabPromise();
          const updatedTestTab = new window.UpdateTabPromise();
          const testTab = await browser.tabs.create({ url });
          await createdTestTab.done();
          await updatedTestTab.verify(testTab.id, url);
          return testTab;
        };

        window.expectLinkOpenInNewTab = async (
          testUrl,
          linkSelector,
          expectedUrl
        ) => {
          const testTab = await openTestTab(testUrl);

          // Click a link in testTab to open a new tab.
          const createdNewTab = new window.CreateTabPromise();
          const updatedNewTab = new window.UpdateTabPromise();
          await window.sendMessage("click", { linkSelector });
          const createdTab = await createdNewTab.done();
          await updatedNewTab.verify(createdTab.id, expectedUrl);

          await browser.tabs.remove(createdTab.id);
          await browser.tabs.remove(testTab.id);
        };
      },
      ...(await getCommonFiles()),
    },
    manifest: {
      background: {
        scripts: ["utils.js", "common.js", "tabFunctions.js", "background.js"],
      },
      permissions: ["tabs"],
      protocol_handlers: [
        {
          protocol: "ext+test",
          name: "Protocol Handler Example",
          uriTemplate: "/handler.html#%s",
        },
      ],
    },
  });

  await subtest_clickInBrowser(
    extension,
    () => document.getElementById("tabmail").currentTabInfo.browser
  );
});

add_task(async function test_windows() {
  const extension = ExtensionTestUtils.loadExtension({
    files: {
      "windowFunctions.js": async () => {
        const openTestWin = async url => {
          const createdTestTab = new window.CreateTabPromise();
          const updatedTestTab = new window.UpdateTabPromise({
            logWindowId: true,
          });
          const testWindow = await browser.windows.create({
            type: "popup",
            url,
          });
          await createdTestTab.done();
          await updatedTestTab.verify(testWindow.id, url);
          return testWindow;
        };

        window.expectLinkOpenInNewTab = async (
          testUrl,
          linkSelector,
          expectedUrl
        ) => {
          const testWindow = await openTestWin(testUrl);

          // Click a link in testWindow to open a new tab.
          const createdNewTab = new window.CreateTabPromise();
          const updatedNewTab = new window.UpdateTabPromise();
          await window.sendMessage("click", { linkSelector });
          const createdTab = await createdNewTab.done();
          await updatedNewTab.verify(createdTab.id, expectedUrl);

          await browser.tabs.remove(createdTab.id);
          await browser.windows.remove(testWindow.id);
        };
      },
      ...(await getCommonFiles()),
    },
    manifest: {
      background: {
        scripts: [
          "utils.js",
          "common.js",
          "windowFunctions.js",
          "background.js",
        ],
      },
      permissions: ["tabs"],
      protocol_handlers: [
        {
          protocol: "ext+test",
          name: "Protocol Handler Example",
          uriTemplate: "/handler.html#%s",
        },
      ],
    },
  });

  await subtest_clickInBrowser(
    extension,
    () => Services.wm.getMostRecentWindow("mail:extensionPopup").browser
  );
});

add_task(async function test_mail3pane() {
  const extension = ExtensionTestUtils.loadExtension({
    files: {
      "mail3paneFunctions.js": async () => {
        const updateTestTab = async url => {
          const updatedTestTab = new window.UpdateTabPromise();
          const mailTabs = await browser.tabs.query({ type: "mail" });
          browser.test.assertEq(
            1,
            mailTabs.length,
            "Should find a single mailTab"
          );
          await browser.tabs.update(mailTabs[0].id, { url });
          await updatedTestTab.verify(mailTabs[0].id, url);
          return mailTabs[0];
        };

        window.expectLinkOpenInNewTab = async (
          testUrl,
          linkSelector,
          expectedUrl
        ) => {
          await updateTestTab(testUrl);

          // Click a link in testTab to open a new tab.
          const createdNewTab = new window.CreateTabPromise();
          const updatedNewTab = new window.UpdateTabPromise();
          await window.sendMessage("click", { linkSelector });
          const createdTab = await createdNewTab.done();
          await updatedNewTab.verify(createdTab.id, expectedUrl);

          await browser.tabs.remove(createdTab.id);
        };
      },
      ...(await getCommonFiles()),
    },
    manifest: {
      background: {
        scripts: [
          "utils.js",
          "common.js",
          "mail3paneFunctions.js",
          "background.js",
        ],
      },
      permissions: ["tabs"],
      protocol_handlers: [
        {
          protocol: "ext+test",
          name: "Protocol Handler Example",
          uriTemplate: "/handler.html#%s",
        },
      ],
    },
  });

  await subtest_clickInBrowser(
    extension,
    () => document.getElementById("tabmail").currentTabInfo.browser
  );
});
