/* 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/. */
/* eslint-disable mozilla/no-arbitrary-setTimeout */

"use strict";

/**
 * Test opening and closing the bookmarks panel.
 */

let bookmarkPanel = document.getElementById("editBookmarkPanel");
let bookmarkStar = BookmarkingUI.star;
let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
let bookmarkRemoveButton = document.getElementById("editBookmarkPanelRemoveButton");
let editBookmarkPanelRemoveButtonRect;

const TEST_URL = "data:text/html,<html><body></body></html>";

StarUI._closePanelQuickForTesting = true;

add_task(async function setup() {
  bookmarkPanel.setAttribute("animate", false);
  registerCleanupFunction(() => {
    bookmarkPanel.removeAttribute("animate");
  });
});

async function test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
                                shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
  await BrowserTestUtils.withNewTab({gBrowser, url: TEST_URL}, async function(browser) {
    try {
      if (!isNewBookmark) {
        await PlacesUtils.bookmarks.insert({
          parentGuid: PlacesUtils.bookmarks.unfiledGuid,
          url: TEST_URL,
          title: "Home Page"
        });
      }

      info(`BookmarkingUI.status is ${BookmarkingUI.status}`);
      await BrowserTestUtils.waitForCondition(
        () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING,
        "BookmarkingUI should not be updating");

      Assert.equal(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
                   "Page should only be starred prior to popupshown if editing bookmark");
      Assert.equal(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
      let shownPromise = promisePopupShown(bookmarkPanel);
      await popupShowFn(browser);
      await shownPromise;
      Assert.equal(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved");

      editBookmarkPanelRemoveButtonRect = bookmarkRemoveButton.getBoundingClientRect();

      if (popupEditFn) {
        await popupEditFn();
      }
      let bookmarks = [];
      await PlacesUtils.bookmarks.fetch({url: TEST_URL}, bm => bookmarks.push(bm));
      Assert.equal(bookmarks.length, 1, "Only one bookmark should exist");
      Assert.equal(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
      Assert.equal(bookmarkPanelTitle.value,
        isNewBookmark ?
          gNavigatorBundle.getString("editBookmarkPanel.newBookmarkTitle") :
          gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"),
        "title should match isEditingBookmark state");
      Assert.equal(bookmarkRemoveButton.label,
        isNewBookmark ?
          gNavigatorBundle.getString("editBookmarkPanel.cancel.label") :
          PluralForm.get(1, gNavigatorBundle.getString("editBookmark.removeBookmarks.label")).replace("#1", 1),
        "remove/cancel button label should match isEditingBookmark state");

      if (!shouldAutoClose) {
        await new Promise(resolve => setTimeout(resolve, 400));
        Assert.equal(bookmarkPanel.state, "open", "Panel should still be 'open' for non-autoclose");
      }

      let onItemRemovedPromise = Promise.resolve();
      if (isBookmarkRemoved) {
        onItemRemovedPromise = PlacesTestUtils.waitForNotification("onItemRemoved",
          (id, parentId, index, type, itemUrl) => TEST_URL == itemUrl.spec);
      }

      let hiddenPromise = promisePopupHidden(bookmarkPanel);
      if (popupHideFn) {
        await popupHideFn();
      }
      await Promise.all([hiddenPromise, onItemRemovedPromise]);

      Assert.equal(bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved,
         "Page is starred after closing");
    } finally {
      let bookmark = await PlacesUtils.bookmarks.fetch({url: TEST_URL});
      Assert.equal(!!bookmark, !isBookmarkRemoved,
         "bookmark should not be present if a panel action should've removed it");
      if (bookmark) {
        await PlacesUtils.bookmarks.remove(bookmark);
      }
    }
  });
}

add_task(async function panel_shown_for_new_bookmarks_and_autocloses() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    shouldAutoClose: true,
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_once_for_doubleclick_on_new_bookmark_star_and_autocloses() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      EventUtils.synthesizeMouse(bookmarkStar, 10, 10, { clickCount: 2 },
                                 window);
    },
    shouldAutoClose: true,
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() {
  todo(false, "bug 1250267, may need to add some tracking state to " +
              "browser-places.js for this.");

  /*
  yield test_bookmarks_popup({
    isNewBookmark: true,
    *popupShowFn() {
      EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
      yield new Promise(resolve => setTimeout(resolve, 300));
      EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
    },
    shouldAutoClose: true,
    isBookmarkRemoved: false,
  });
  */
});

add_task(async function panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      EventUtils.synthesizeKey("D", {accelKey: true}, window);
    },
    shouldAutoClose: true,
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_for_new_bookmarks_mousemove_mouseout() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    async popupEditFn() {
      let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
      EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
      info("Waiting for mousemove event");
      await mouseMovePromise;
      info("Got mousemove event");

      await new Promise(resolve => setTimeout(resolve, 400));
      is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");
    },
    async popupHideFn() {
      let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
      EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
      EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
      info("Waiting for mouseout event");
      await mouseOutPromise;
      info("Got mouseout event, should autoclose now");
    },
    shouldAutoClose: false,
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_for_new_bookmark_close_with_ESC() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    shouldAutoClose: true,
    popupHideFn() {
      EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
    },
    isBookmarkRemoved: true,
  });
});

add_task(async function panel_shown_for_editing_no_autoclose_close_with_ESC() {
  await test_bookmarks_popup({
    isNewBookmark: false,
    popupShowFn() {
      bookmarkStar.click();
    },
    shouldAutoClose: false,
    popupHideFn() {
      EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
    },
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_for_new_bookmark_keypress_no_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    popupEditFn() {
      EventUtils.sendChar("VK_TAB", window);
    },
    shouldAutoClose: false,
    popupHideFn() {
      bookmarkPanel.hidePopup();
    },
    isBookmarkRemoved: false,
  });
});


add_task(async function panel_shown_for_new_bookmark_compositionstart_no_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    async popupEditFn() {
      let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart");
      EventUtils.synthesizeComposition({ type: "compositionstart" }, window);
      info("Waiting for compositionstart event");
      await compositionStartPromise;
      info("Got compositionstart event");
    },
    shouldAutoClose: false,
    popupHideFn() {
      EventUtils.synthesizeComposition({ type: "compositioncommitasis" });
      bookmarkPanel.hidePopup();
    },
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_for_new_bookmark_compositionstart_mouseout_no_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    async popupEditFn() {
      let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
      EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
      info("Waiting for mousemove event");
      await mouseMovePromise;
      info("Got mousemove event");

      let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart");
      EventUtils.synthesizeComposition({ type: "compositionstart" }, window);
      info("Waiting for compositionstart event");
      await compositionStartPromise;
      info("Got compositionstart event");

      let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
      EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
      EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
      info("Waiting for mouseout event");
      await mouseOutPromise;
      info("Got mouseout event, but shouldn't run autoclose");
    },
    shouldAutoClose: false,
    popupHideFn() {
      EventUtils.synthesizeComposition({ type: "compositioncommitasis" });
      bookmarkPanel.hidePopup();
    },
    isBookmarkRemoved: false,
  });
});

add_task(async function panel_shown_for_new_bookmark_compositionend_no_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn() {
      bookmarkStar.click();
    },
    async popupEditFn() {
      let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
      EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
      info("Waiting for mousemove event");
      await mouseMovePromise;
      info("Got mousemove event");

      EventUtils.synthesizeComposition({ type: "compositioncommit", data: "committed text" });
    },
    popupHideFn() {
      bookmarkPanel.hidePopup();
    },
    shouldAutoClose: false,
    isBookmarkRemoved: false,
  });
});

add_task(async function contextmenu_new_bookmark_keypress_no_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    async popupShowFn(browser) {
      let contextMenu = document.getElementById("contentAreaContextMenu");
      let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
                                                          "popupshown");
      let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
                                                           "popuphidden");
      await BrowserTestUtils.synthesizeMouseAtCenter("body", {
        type: "contextmenu",
        button: 2
      }, browser);
      await awaitPopupShown;
      document.getElementById("context-bookmarkpage").click();
      contextMenu.hidePopup();
      await awaitPopupHidden;
    },
    popupEditFn() {
      EventUtils.sendChar("VK_TAB", window);
    },
    shouldAutoClose: false,
    popupHideFn() {
      bookmarkPanel.hidePopup();
    },
    isBookmarkRemoved: false,
  });
});

add_task(async function bookmarks_menu_new_bookmark_remove_bookmark() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn(browser) {
      document.getElementById("menu_bookmarkThisPage").doCommand();
    },
    shouldAutoClose: true,
    popupHideFn() {
      document.getElementById("editBookmarkPanelRemoveButton").click();
    },
    isBookmarkRemoved: true,
  });
});

add_task(async function ctrl_d_edit_bookmark_remove_bookmark() {
  await test_bookmarks_popup({
    isNewBookmark: false,
    popupShowFn(browser) {
      EventUtils.synthesizeKey("D", {accelKey: true}, window);
    },
    shouldAutoClose: true,
    popupHideFn() {
      document.getElementById("editBookmarkPanelRemoveButton").click();
    },
    isBookmarkRemoved: true,
  });
});

add_task(async function enter_on_remove_bookmark_should_remove_bookmark() {
  if (AppConstants.platform == "macosx") {
    // "Full Keyboard Access" is disabled by default, and thus doesn't allow
    // keyboard navigation to the "Remove Bookmarks" button by default.
    return;
  }

  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn(browser) {
      EventUtils.synthesizeKey("D", {accelKey: true}, window);
    },
    shouldAutoClose: true,
    popupHideFn() {
      while (!document.activeElement ||
             document.activeElement.id != "editBookmarkPanelRemoveButton") {
        EventUtils.sendChar("VK_TAB", window);
      }
      EventUtils.sendChar("VK_RETURN", window);
    },
    isBookmarkRemoved: true,
  });
});

add_task(async function mouse_hovering_panel_should_prevent_autoclose() {
  if (AppConstants.platform != "win") {
    // This test requires synthesizing native mouse movement which is
    // best supported on Windows.
    return;
  }
  await test_bookmarks_popup({
    isNewBookmark: true,
    async popupShowFn() {
      await new Promise(resolve => {
        EventUtils.synthesizeNativeMouseMove(
          document.documentElement,
          editBookmarkPanelRemoveButtonRect.left,
          editBookmarkPanelRemoveButtonRect.top,
          resolve);
      });
      EventUtils.synthesizeKey("D", {accelKey: true}, window);
    },
    shouldAutoClose: false,
    popupHideFn() {
      document.getElementById("editBookmarkPanelRemoveButton").click();
    },
    isBookmarkRemoved: true,
  });
});

add_task(async function ctrl_d_new_bookmark_mousedown_mouseout_no_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    popupShowFn(browser) {
      EventUtils.synthesizeKey("D", {accelKey: true}, window);
    },
    async popupEditFn() {
      let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
      EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
      info("Waiting for mousemove event");
      await mouseMovePromise;
      info("Got mousemove event");

      await new Promise(resolve => setTimeout(resolve, 400));
      is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");

      EventUtils.synthesizeMouseAtCenter(bookmarkPanelTitle, {button: 1, type: "mousedown"});

      let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
      EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
      EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
      info("Waiting for mouseout event");
      await mouseOutPromise;
    },
    shouldAutoClose: false,
    popupHideFn() {
      document.getElementById("editBookmarkPanelRemoveButton").click();
    },
    isBookmarkRemoved: true,
  });
});

add_task(async function enter_during_autocomplete_should_prevent_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    async popupShowFn(browser) {
      EventUtils.synthesizeKey("d", {accelKey: true}, window);
    },
    async popupEditFn() {
      let tagsField = document.getElementById("editBMPanel_tagsField");
      tagsField.value = "";
      tagsField.focus();

      // Register a tag into the DB.
      EventUtils.sendString("Abc", window);
      tagsField.blur();

      // Start autocomplete with the registered tag.
      tagsField.value = "";
      let popup = document.getElementById("PopupAutoComplete");
      let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
      tagsField.focus();
      EventUtils.sendString("a", window);
      await promiseShown;
      ok(promiseShown, "autocomplete shown");

      // Select first candidate.
      EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);

      // Type Enter key to choose the item.
      EventUtils.synthesizeKey("KEY_Enter", {}, window);

      Assert.equal(tagsField.value, "Abc",
        "Autocomplete should've inserted the selected item");
    },
    shouldAutoClose: false,
    popupHideFn() {
      EventUtils.synthesizeKey("KEY_Escape", {}, window);
    },
    isBookmarkRemoved: false,
  });
});

add_task(async function escape_during_autocomplete_should_prevent_autoclose() {
  await test_bookmarks_popup({
    isNewBookmark: true,
    async popupShowFn(browser) {
      EventUtils.synthesizeKey("d", {accelKey: true}, window);
    },
    async popupEditFn() {
      let tagsField = document.getElementById("editBMPanel_tagsField");
      tagsField.value = "";
      tagsField.focus();

      // Register a tag into the DB.
      EventUtils.sendString("Abc", window);
      tagsField.blur();

      // Start autocomplete with the registered tag.
      tagsField.value = "";
      let popup = document.getElementById("PopupAutoComplete");
      let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
      tagsField.focus();
      EventUtils.sendString("a", window);
      await promiseShown;
      ok(promiseShown, "autocomplete shown");

      // Select first candidate.
      EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);

      // Type Enter key to choose the item.
      EventUtils.synthesizeKey("KEY_Escape", {}, window);

      Assert.equal(tagsField.value, "Abc",
        "Autocomplete should've inserted the selected item and shouldn't clear it");
    },
    shouldAutoClose: false,
    popupHideFn() {
      EventUtils.synthesizeKey("KEY_Escape", {}, window);
    },
    isBookmarkRemoved: false,
  });
});

registerCleanupFunction(function() {
  delete StarUI._closePanelQuickForTesting;
});
