import {
  awaitPromise,
  closePages,
  FSI,
  getThumbnailSelector,
  kbFocusNext,
  loadAndWait,
  PDI,
  showViewsManager,
} from "./test_utils.mjs";

function waitForThumbnailVisible(page, pageNum) {
  return page.waitForSelector(getThumbnailSelector(pageNum), { visible: true });
}

async function waitForMenu(page, buttonSelector, visible = true) {
  return page.waitForFunction(
    (selector, vis) => {
      const button = document.querySelector(selector);
      if (!button) {
        return false;
      }
      return button.getAttribute("aria-expanded") === (vis ? "true" : "false");
    },
    {},
    buttonSelector,
    visible
  );
}

describe("PDF Thumbnail View", () => {
  describe("Works without errors", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
    });

    afterEach(async () => {
      await closePages(pages);
    });

    it("should render thumbnails without errors", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);

          const thumbSelector =
            "#thumbnailsView .thumbnailImageContainer > img";
          await page.waitForSelector(thumbSelector, { visible: true });

          await waitForThumbnailVisible(page, 1);

          await page.waitForSelector(`${thumbSelector}[src^="blob:http:"]`, {
            visible: true,
          });

          const title = await page.$eval(
            getThumbnailSelector(1),
            el => el.title
          );
          expect(title)
            .withContext(`In ${browserName}`)
            .toBe(`Page ${FSI}1${PDI} of ${FSI}14${PDI}`);
        })
      );
    });

    it("should have accessible label on resizer", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);

          const ariaLabel = await page.$eval("#viewsManagerResizer", el =>
            el.getAttribute("aria-label")
          );
          expect(ariaLabel)
            .withContext(`In ${browserName}`)
            .toBe("Sidebar resizer");
        })
      );
    });
  });

  describe("The view is scrolled correctly", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
    });

    afterEach(async () => {
      await closePages(pages);
    });

    async function goToPage(page, number) {
      const handle = await page.evaluateHandle(
        num => [
          new Promise(resolve => {
            const container = document.getElementById("viewsManagerContent");
            container.addEventListener("scrollend", resolve, { once: true });
            // eslint-disable-next-line no-undef
            PDFViewerApplication.pdfLinkService.goToPage(num);
          }),
        ],
        number
      );
      return awaitPromise(handle);
    }

    it("should scroll the view", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          for (const pageNum of [14, 1, 13, 2]) {
            await goToPage(page, pageNum);
            const thumbSelector = getThumbnailSelector(pageNum);
            await page.waitForSelector(
              `.thumbnail ${thumbSelector}[aria-current="page"]`,
              { visible: true }
            );
            await page.waitForSelector(
              `${thumbSelector} > img[src^="blob:http:"]`,
              {
                visible: true,
              }
            );
          }
        })
      );
    });
  });

  describe("The view is accessible with the keyboard", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
    });

    afterEach(async () => {
      await closePages(pages);
    });

    it("should navigate with the keyboard", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);
          await waitForThumbnailVisible(page, 2);
          await waitForThumbnailVisible(page, 3);

          await kbFocusNext(page);
          await page.waitForSelector("#viewsManagerSelectorButton:focus", {
            visible: true,
          });

          await kbFocusNext(page);
          await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
            visible: true,
          });

          await kbFocusNext(page);
          await page.waitForSelector(
            `#thumbnailsView ${getThumbnailSelector(1)}:focus`,
            { visible: true }
          );

          await page.keyboard.press("ArrowDown");
          await page.waitForSelector(
            `#thumbnailsView ${getThumbnailSelector(2)}:focus`,
            { visible: true }
          );

          await page.keyboard.press("ArrowUp");
          await page.waitForSelector(`${getThumbnailSelector(1)}:focus`, {
            visible: true,
          });

          await page.keyboard.press("ArrowDown");
          await page.keyboard.press("ArrowDown");
          await page.waitForSelector(
            `#thumbnailsView ${getThumbnailSelector(3)}:focus`,
            { visible: true }
          );

          await page.keyboard.press("Enter");
          const currentPage = await page.$eval(
            "#pageNumber",
            el => el.valueAsNumber
          );
          expect(currentPage).withContext(`In ${browserName}`).toBe(3);

          await page.keyboard.press("End");
          await page.waitForSelector(
            `#thumbnailsView ${getThumbnailSelector(14)}:focus`,
            { visible: true }
          );

          await page.keyboard.press("Home");
          await page.waitForSelector(
            `#thumbnailsView ${getThumbnailSelector(1)}:focus`,
            { visible: true }
          );
        })
      );
    });
  });

  describe("The manage dropdown menu", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait(
        "tracemonkey.pdf",
        "#viewsManagerToggleButton",
        null,
        null,
        { enableSplitMerge: true }
      );
    });

    afterEach(async () => {
      await closePages(pages);
    });

    async function enableMenuItems(page) {
      await page.evaluate(() => {
        document
          .querySelectorAll("#viewsManagerStatusActionOptions button")
          .forEach(button => {
            button.disabled = false;
          });
      });
    }

    it("should open with Enter key and remain open", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          await enableMenuItems(page);

          // Focus the manage button
          await kbFocusNext(page);
          await kbFocusNext(page);
          await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
            visible: true,
          });

          // Press Enter to open the menu
          await page.keyboard.press("Enter");

          await waitForMenu(page, "#viewsManagerStatusActionButton");

          // Verify first menu item can be focused
          await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
            visible: true,
          });

          // Close menu with Escape
          await page.keyboard.press("Escape");
          await waitForMenu(page, "#viewsManagerStatusActionButton", false);
        })
      );
    });

    it("should open with Space key and remain open", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          await enableMenuItems(page);

          // Focus the manage button
          await kbFocusNext(page);
          await kbFocusNext(page);
          await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
            visible: true,
          });

          // Press Space to open the menu
          await page.keyboard.press(" ");

          await waitForMenu(page, "#viewsManagerStatusActionButton");

          // Verify first menu item can be focused
          await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
            visible: true,
          });

          // Navigate menu items with arrow keys
          await page.keyboard.press("ArrowDown");
          await page.waitForSelector("#viewsManagerStatusActionCut:focus", {
            visible: true,
          });

          // Menu should still be open
          await waitForMenu(page, "#viewsManagerStatusActionButton");

          // Close menu with Escape
          await page.keyboard.press("Escape");
          await waitForMenu(page, "#viewsManagerStatusActionButton", false);
        })
      );
    });
  });

  describe("Checkbox accessibility", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait(
        "tracemonkey.pdf",
        "#viewsManagerToggleButton",
        null,
        null,
        { enableSplitMerge: true }
      );
    });

    afterEach(async () => {
      await closePages(pages);
    });

    it("should have a title on the checkbox", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          const title = await page.$eval(
            `.thumbnail[page-number="1"] input[type="checkbox"]`,
            el => el.title
          );
          expect(title)
            .withContext(`In ${browserName}`)
            .toBe(`Select page ${FSI}1${PDI}`);
        })
      );
    });
  });

  describe("Menu keyboard navigation with multi-character keys (bug 2016212)", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait(
        "page_with_number_and_link.pdf",
        "#viewsManagerSelectorButton",
        null,
        null,
        { enableSplitMerge: true }
      );
    });

    afterEach(async () => {
      await closePages(pages);
    });

    it("must navigate menus with ArrowDown and Tab keys", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          // Focus the views manager selector button
          await page.waitForSelector("#viewsManagerSelectorButton", {
            visible: true,
          });
          await page.focus("#viewsManagerSelectorButton");

          // Open menu with Enter key
          await page.keyboard.press("Enter");

          // Wait for menu to be expanded
          await waitForMenu(page, "#viewsManagerSelectorButton");

          // Check that focus moved to the first menu button (pages)
          await page.waitForSelector("#thumbnailsViewMenu:focus", {
            visible: true,
          });

          // Press ArrowDown to navigate to second item
          await page.keyboard.press("ArrowDown");

          // Should now be on outlines button
          await page.waitForSelector("#outlinesViewMenu:focus", {
            visible: true,
          });

          // Press Tab to move to the manage button (should close views menu)
          await page.keyboard.press("Tab");

          // Wait for views manager menu to be collapsed
          await waitForMenu(page, "#viewsManagerSelectorButton", false);

          // Focus should be on manage button
          await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
            visible: true,
          });

          // Open manage menu with Space key
          await page.keyboard.press(" ");

          // Wait for manage menu to be expanded
          await waitForMenu(page, "#viewsManagerStatusActionButton");
        })
      );
    });
  });

  describe("Views manager status visibility (bug 2016656)", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait(
        "page_with_number_and_link.pdf",
        "#viewsManagerToggleButton",
        null,
        null,
        { enableSplitMerge: true }
      );
    });

    afterEach(async () => {
      await closePages(pages);
    });

    it("should show the manage button in thumbnail view and hide it in outline view", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          // The status bar (Select pages + Manage button) must be visible in
          // thumbnail view.
          await page.waitForSelector("#viewsManagerStatus", { visible: true });

          // Switch to outline view.
          await page.click("#viewsManagerSelectorButton");
          await page.waitForSelector("#outlinesViewMenu", { visible: true });
          await page.click("#outlinesViewMenu");
          await page.waitForSelector("#outlinesView", { visible: true });

          // The status bar must no longer be visible in outline view.
          await page.waitForSelector("#viewsManagerStatus", { hidden: true });
        })
      );
    });
  });

  describe("Checkbox keyboard navigation", () => {
    let pages;

    beforeEach(async () => {
      pages = await loadAndWait(
        "tracemonkey.pdf",
        "#viewsManagerToggleButton",
        null,
        null,
        { enableSplitMerge: true }
      );
    });

    afterEach(async () => {
      await closePages(pages);
    });

    it("should focus checkboxes with Tab key", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);

          // Focus the first thumbnail button
          await kbFocusNext(page);
          await kbFocusNext(page);
          await kbFocusNext(page);

          // Verify we're on the first thumbnail
          await page.waitForSelector(`${getThumbnailSelector(1)}:focus`, {
            visible: true,
          });

          // Tab to checkbox
          await kbFocusNext(page);
          await page.waitForSelector(
            `.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
            { visible: true }
          );
        })
      );
    });

    it("should navigate checkboxes with arrow keys", async () => {
      await Promise.all(
        pages.map(async ([browserName, page]) => {
          await showViewsManager(page);
          await waitForThumbnailVisible(page, 1);
          await waitForThumbnailVisible(page, 2);

          // Navigate to first checkbox
          await kbFocusNext(page);
          await kbFocusNext(page);
          await kbFocusNext(page);
          await kbFocusNext(page);

          // Verify first checkbox is focused
          await page.waitForSelector(
            `.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
            { visible: true }
          );

          // Navigate to next checkbox with ArrowDown
          await page.keyboard.press("ArrowDown");
          await page.waitForSelector(
            `.thumbnail[page-number="2"] input[type="checkbox"]:focus`,
            { visible: true }
          );

          // Navigate back with ArrowUp
          await page.keyboard.press("ArrowUp");
          await page.waitForSelector(
            `.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
            { visible: true }
          );
        })
      );
    });
  });
});
