/* 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";

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

let gAccount;

add_setup(async () => {
  gAccount = createAccount();
  addIdentity(gAccount);
});

add_task(async function testLockedComposeWindow() {
  const files = {
    "background.js": async () => {
      // Open a compose tab with a message.
      const composeTab = await new Promise(resolve => {
        const tabListener = tab => {
          if (tab.type == "messageCompose") {
            browser.tabs.onCreated.removeListener(tabListener);
            resolve(tab);
          }
        };
        browser.tabs.onCreated.addListener(tabListener);
        browser.compose.beginNew({
          to: ["test@test.invalid"],
          subject: "Test",
          body: "This is a test",
          isPlainText: false,
        });
      });
      await browser.compose.getComposeDetails(composeTab.id);

      // Add a compose action click listener.
      let clickCounts = 0;
      const composeActionClickListener = () => {
        clickCounts++;
      };
      browser.composeAction.onClicked.addListener(composeActionClickListener);

      // Record original state and verify the composeAction button is clickable.
      await window.sendMessage("recordOriginalState");
      browser.test.assertEq(
        1,
        clickCounts,
        "A click on the enabled compose action button should have been counted"
      );

      // Open print preview and verify locked state.
      await window.sendMessage("openPreviewAndVerifyLockedState");
      browser.test.assertEq(
        1,
        clickCounts,
        "A click on the disabled compose action button should have been ignored"
      );

      // After unlocking the compose window, the original state should have been
      // restored. The composeAction button should be clickable again.
      await window.sendMessage("verifyOriginalState");
      browser.test.assertEq(
        2,
        clickCounts,
        "A click on the enabled compose action button should have been counted"
      );

      // Clean up.
      browser.composeAction.onClicked.removeListener(
        composeActionClickListener
      );
      const removedWindowPromise = window.waitForEvent("windows.onRemoved");
      browser.windows.remove(composeTab.windowId);
      await removedWindowPromise;

      browser.test.notifyPass("finished");
    },
    "utils.js": await getUtilsJS(),
  };
  const extension = ExtensionTestUtils.loadExtension({
    files,
    manifest: {
      browser_specific_settings: {
        gecko: {
          id: "printpreview@mochi.test",
        },
      },
      background: { scripts: ["utils.js", "background.js"] },
      compose_action: { default_title: "click" },
      permissions: ["compose"],
    },
  });

  const elements = new Map();

  const isDisabled = element =>
    element.hasAttribute("disabled") &&
    element.getAttribute("disabled") !== "false";

  const clickComposeActionButton = async composeWindow => {
    await promiseAnimationFrame(composeWindow);
    await new Promise(resolve => composeWindow.setTimeout(resolve));
    const buttonId = "printpreview_mochi_test-composeAction-toolbarbutton";
    const button = composeWindow.document.getElementById(buttonId);
    Assert.ok(button, "Button should exist");
    EventUtils.synthesizeMouseAtCenter(
      button,
      { clickCount: 1 },
      composeWindow
    );
    await new Promise(resolve => composeWindow.setTimeout(resolve));
  };

  const recordElementState = (composeWindow, query) => {
    let found = false;
    for (const item of composeWindow.document.querySelectorAll(query)) {
      elements.set(item, isDisabled(item));
      found = true;
    }
    // Make sure the query returned some elements.
    Assert.ok(found, `Should have found elements for the query: ${query}`);
  };

  const elementToString = item => {
    const id = item.id ? ` id="${item.id}"` : ``;
    const command =
      !id && item.hasAttribute("command")
        ? ` command="${item.getAttribute("command")}"`
        : ``;
    const oncommand =
      !id && !command && item.hasAttribute("oncommand")
        ? ` oncommand="${item.getAttribute("oncommand")}"`
        : ``;
    return `<${item.tagName}${id}${command}${oncommand}>`;
  };

  extension.onMessage("recordOriginalState", async () => {
    const composeWindow = Services.wm.getMostRecentWindow("msgcompose");
    const editor = composeWindow.document.getElementById("messageEditor");
    editor.focus();
    editor.contentDocument.execCommand("selectAll");

    // Click on the composeAction button to make sure it is counted.
    await clickComposeActionButton(composeWindow);

    recordElementState(
      composeWindow,
      "menu, toolbarbutton, [command], [oncommand]"
    );
    recordElementState(composeWindow, "#FormatToolbar menulist");
    recordElementState(composeWindow, "#recipientsContainer input");

    extension.sendMessage();
  });

  extension.onMessage("openPreviewAndVerifyLockedState", async () => {
    const composeWindow = Services.wm.getMostRecentWindow("msgcompose");
    const editor = composeWindow.document.getElementById("messageEditor");
    editor.focus();

    Assert.equal(
      composeWindow.document.getElementsByClassName("printSettingsBrowser")
        .length,
      0,
      "There should be no print settings browser"
    );

    // Open Print-Preview.
    composeWindow.DoCommandPrint();

    // Wait for print settings browser.
    await TestUtils.waitForCondition(
      () =>
        composeWindow.document.getElementsByClassName("printSettingsBrowser")
          .length,
      "There should be a print setting browser"
    );

    // Click on the composeAction button to make sure it is ignored.
    await clickComposeActionButton(composeWindow);

    // Check that all general elements are as expected.
    for (const item of composeWindow.document.querySelectorAll(
      "menu, toolbarbutton, [command], [oncommand]"
    )) {
      // The disabled editor still allows to select text. The helpMenu is skipped
      // due to Bug 1883647.
      if (["menu_selectAll", "cmd_selectAll", "helpMenu"].includes(item.id)) {
        continue;
      }
      Assert.ok(
        isDisabled(item),
        `General item ${elementToString(
          item
        )} should be disabled if the composer is locked`
      );
    }
    // Check that all format toolbar elements are as expected.
    for (const item of composeWindow.document.querySelectorAll(
      "#FormatToolbar menulist"
    )) {
      Assert.ok(
        isDisabled(item),
        `Format toolbar item ${elementToString(
          item
        )} should be disabled if the composer is locked`
      );
    }
    // Check input fields.
    for (const item of composeWindow.document.querySelectorAll(
      "#recipientsContainer input"
    )) {
      Assert.ok(
        isDisabled(item),
        `Input field item ${elementToString(
          item
        )} should be disabled if the composer is locked`
      );
    }

    // Close print preview.
    const browser = composeWindow.GetCurrentEditorElement();
    composeWindow.PrintUtils.togglePrintPreview(browser.browsingContext);

    // Wait for print settings browser to close.
    await TestUtils.waitForCondition(
      () =>
        composeWindow.document.getElementsByClassName("printSettingsBrowser")
          .length == 0,
      "All print settings browser should be closed"
    );

    extension.sendMessage();
  });

  extension.onMessage("verifyOriginalState", async () => {
    const composeWindow = Services.wm.getMostRecentWindow("msgcompose");
    const editor = composeWindow.document.getElementById("messageEditor");
    editor.focus();

    // Click on the composeAction button to make sure it is counted.
    await clickComposeActionButton(composeWindow);

    for (const [item, state] of elements) {
      Assert.equal(
        state,
        isDisabled(item),
        `Original disabled state of item ${elementToString(
          item
        )} should have been restored`
      );
    }
    extension.sendMessage();
  });

  await extension.startup();
  await extension.awaitFinish("finished");
  await extension.unload();
});
