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

/**
 * Functionality for the main application window (aka the 3pane) usually
 * consisting of folder pane, thread pane and message pane.
 */

/* global MozElements */

/* import-globals-from ../../components/im/content/chat-messenger.js */
/* import-globals-from mailCore.js */
/* import-globals-from mailWindow.js */ // msgWindow and a loooot more
/* import-globals-from utilityOverlay.js */

/* globals MailOfflineMgr */ // From mail-offline.js

var { MailServices } = ChromeUtils.import(
  "resource:///modules/MailServices.jsm"
);
var { PluralForm } = ChromeUtils.importESModule(
  "resource://gre/modules/PluralForm.sys.mjs"
);
ChromeUtils.defineModuleGetter(
  this,
  "MailUtils",
  "resource:///modules/MailUtils.jsm"
);

// DefaultController object (handles commands when one of the trees does not have focus)
var DefaultController = {
  supportsCommand(command) {
    switch (command) {
      case "cmd_newMessage":
      case "cmd_undoCloseTab":
      case "cmd_undo":
      case "cmd_redo":
      case "cmd_sendUnsentMsgs":
      case "cmd_subscribe":
      case "cmd_getNewMessages":
      case "cmd_getMsgsForAuthAccounts":
      case "cmd_getNextNMessages":
      case "cmd_settingsOffline":
      case "cmd_viewAllHeader":
      case "cmd_viewNormalHeader":
      case "cmd_stop":
      case "cmd_chat":
      case "cmd_goFolder":
        return true;
      case "cmd_synchronizeOffline":
        return MailOfflineMgr.isOnline();
      case "cmd_joinChat":
      case "cmd_addChatBuddy":
      case "cmd_chatStatus":
        return !!chatHandler;

      default:
        return false;
    }
  },

  isCommandEnabled(command) {
    if (document.getElementById("tabmail").globalOverlay) {
      return false;
    }
    switch (command) {
      case "cmd_newMessage":
        return MailServices.accounts.allIdentities.length > 0;
      case "cmd_viewAllHeader":
      case "cmd_viewNormalHeader":
        return true;
      case "cmd_undoCloseTab":
        return document.getElementById("tabmail").recentlyClosedTabs.length > 0;
      case "cmd_stop":
        return window.MsgStatusFeedback?._meteorsSpinning;
      case "cmd_undo":
      case "cmd_redo":
        return SetupUndoRedoCommand(command);
      case "cmd_sendUnsentMsgs":
        return IsSendUnsentMsgsEnabled(null);
      case "cmd_subscribe":
        return IsSubscribeEnabled();
      case "cmd_getNewMessages":
      case "cmd_getMsgsForAuthAccounts":
        return IsGetNewMessagesEnabled();
      case "cmd_getNextNMessages":
        return IsGetNextNMessagesEnabled();
      case "cmd_synchronizeOffline":
        return MailOfflineMgr.isOnline();
      case "cmd_settingsOffline":
        return IsAccountOfflineEnabled();
      case "cmd_goFolder":
        return isFolderPaneInitialized();
      case "cmd_chat":
        return true;
      case "cmd_joinChat":
      case "cmd_addChatBuddy":
      case "cmd_chatStatus":
        return !!chatHandler;
    }
    return false;
  },

  doCommand(command, event) {
    // If the user invoked a key short cut then it is possible that we got here
    // for a command which is really disabled. Kick out if the command should be disabled.
    if (!this.isCommandEnabled(command)) {
      return;
    }

    switch (command) {
      case "cmd_getNewMessages":
        MsgGetMessage();
        break;
      case "cmd_getMsgsForAuthAccounts":
        MsgGetMessagesForAllAuthenticatedAccounts();
        break;
      case "cmd_getNextNMessages":
        MsgGetNextNMessages();
        break;
      case "cmd_newMessage":
        MsgNewMessage(event);
        break;
      case "cmd_undoCloseTab":
        document.getElementById("tabmail").undoCloseTab();
        break;
      case "cmd_undo":
        messenger.undo(msgWindow);
        break;
      case "cmd_redo":
        messenger.redo(msgWindow);
        break;
      case "cmd_sendUnsentMsgs":
        // if offline, prompt for sendUnsentMessages
        if (MailOfflineMgr.isOnline()) {
          SendUnsentMessages();
        } else {
          MailOfflineMgr.goOnlineToSendMessages(msgWindow);
        }
        return;
      case "cmd_subscribe":
        MsgSubscribe();
        return;
      case "cmd_stop":
        msgWindow.StopUrls();
        return;
      case "cmd_viewAllHeader":
        MsgViewAllHeaders();
        return;
      case "cmd_viewNormalHeader":
        MsgViewNormalHeaders();
        return;
      case "cmd_synchronizeOffline":
        MsgSynchronizeOffline();
        break;
      case "cmd_settingsOffline":
        MailOfflineMgr.openOfflineAccountSettings();
        break;
      case "cmd_goFolder":
        document
          .getElementById("tabmail")
          .currentAbout3Pane.displayFolder(event.target._folder);
        break;
      case "cmd_chat":
        showChatTab();
        break;
    }
  },

  onEvent(event) {
    // on blur events set the menu item texts back to the normal values
    if (event == "blur") {
      goSetMenuValue("cmd_undo", "valueDefault");
      goSetMenuValue("cmd_redo", "valueDefault");
    }
  },
};
// This is the highest priority controller. It's followed by
// tabmail.tabController and calendarController, then whatever Gecko adds.
window.controllers.insertControllerAt(0, DefaultController);

function CloseTabOrWindow() {
  let tabmail = document.getElementById("tabmail");
  if (tabmail.globalOverlay) {
    return;
  }
  if (tabmail.tabInfo.length == 1) {
    if (Services.prefs.getBoolPref("mail.tabs.closeWindowWithLastTab")) {
      window.close();
    }
  } else {
    tabmail.removeCurrentTab();
  }
}

function IsSendUnsentMsgsEnabled(unsentMsgsFolder) {
  // If no account has been configured, there are no messages for sending.
  if (MailServices.accounts.accounts.length == 0) {
    return false;
  }

  let msgSendlater;
  try {
    msgSendlater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
      Ci.nsIMsgSendLater
    );
  } catch (error) {}

  // If we're currently sending unsent msgs, disable this cmd.
  if (msgSendlater?.sendingMessages) {
    return false;
  }

  if (unsentMsgsFolder) {
    // If unsentMsgsFolder is non-null, it is the "Unsent Messages" folder.
    // We're here because we've done a right click on the "Unsent Messages"
    // folder (context menu), so we can use the folder and return true/false
    // straight away.
    return unsentMsgsFolder.getTotalMessages(false) > 0;
  }

  // Otherwise, we don't know where we are, so use the current identity and
  // find out if we have messages or not via that.
  let identity;
  let folders = GetSelectedMsgFolders();
  if (folders.length > 0) {
    [identity] = MailUtils.getIdentityForServer(folders[0].server);
  }

  if (!identity) {
    let defaultAccount = MailServices.accounts.defaultAccount;
    if (defaultAccount) {
      identity = defaultAccount.defaultIdentity;
    }

    if (!identity) {
      return false;
    }
  }

  let hasUnsentMessages = false;
  try {
    hasUnsentMessages = msgSendlater?.hasUnsentMessages(identity);
  } catch (error) {}
  return hasUnsentMessages;
}

/**
 * Determine whether there exists any server for which to show the Subscribe dialog.
 */
function IsSubscribeEnabled() {
  // If there are any IMAP or News servers, we can show the dialog any time and
  // it will properly show those.
  for (let server of MailServices.accounts.allServers) {
    if (server.type == "imap" || server.type == "nntp") {
      return true;
    }
  }

  // RSS accounts use a separate Subscribe dialog that we can only show when
  // such an account is selected.
  let preselectedFolder = GetFirstSelectedMsgFolder();
  if (preselectedFolder && preselectedFolder.server.type == "rss") {
    return true;
  }

  return false;
}

/**
 * Cycle through the various panes in the 3pane window.
 *
 * @param {Event} event - The keypress DOMEvent.
 */
function SwitchPaneFocus(event) {
  let tabmail = document.getElementById("tabmail");
  // Should not move the focus around when the entire window is covered with
  // something else.
  if (tabmail.globalOverlay) {
    return;
  }
  // First, build an array of panes to cycle through based on our current state.
  // This will usually be something like [folderTree, threadTree, messageBrowser].
  let panes = [];
  // The logically focused element. If the actually focused element is not one
  // of the panes, the code below can change this variable to point to one of
  // the panes.
  let focusedElement = document.activeElement;
  // If the actually focused element is between two of the panes, set this to
  // -1, 0, or 1 (depending on the direction and where the focus is relative to
  // `focusedElement`) so that the element to focus is correctly chosen.
  let adjustment = 0;

  let spacesElement = !gSpacesToolbar.isHidden
    ? gSpacesToolbar.focusButton
    : document.getElementById("spacesPinnedButton");
  panes.push(spacesElement);

  let toolbar = document.getElementById("unifiedToolbar");
  if (!toolbar.hidden) {
    // Prioritise the search bar, otherwise use the first available button.
    let toolbarElement =
      toolbar.querySelector("global-search-bar") ||
      toolbar.querySelector("li:not([hidden]) button, #button-appmenu");
    if (toolbarElement) {
      panes.push(toolbarElement);
      if (toolbar.matches(":focus-within") && focusedElement != spacesElement) {
        focusedElement = toolbarElement;
      }
    }
  }

  let { currentTabInfo } = tabmail;
  switch (currentTabInfo.mode.name) {
    case "mail3PaneTab": {
      let { contentWindow, contentDocument } = currentTabInfo.chromeBrowser;
      let {
        paneLayout,
        folderTree,
        threadTree,
        webBrowser,
        messageBrowser,
        multiMessageBrowser,
        accountCentralBrowser,
      } = contentWindow;

      if (paneLayout.folderPaneVisible) {
        panes.push(folderTree);
      }

      if (accountCentralBrowser.hidden) {
        panes.push(threadTree.table.body);
      } else {
        panes.push(accountCentralBrowser);
      }

      if (paneLayout.messagePaneVisible) {
        if (!webBrowser.hidden) {
          panes.push(webBrowser);
        } else if (!messageBrowser.hidden) {
          panes.push(messageBrowser.contentWindow.getMessagePaneBrowser());
        } else if (!multiMessageBrowser.hidden) {
          panes.push(multiMessageBrowser);
        }
      }

      if (focusedElement == currentTabInfo.chromeBrowser) {
        focusedElement = contentDocument.activeElement;
        if (
          focusedElement != folderTree &&
          contentDocument.getElementById("folderPane").contains(focusedElement)
        ) {
          focusedElement = folderTree;
          adjustment = event.shiftKey ? 0 : -1;
        } else if (
          contentDocument
            .getElementById("threadPaneNotificationBox")
            .contains(focusedElement)
        ) {
          focusedElement = threadTree.table.body;
          adjustment = event.shiftKey ? 1 : 0;
        } else if (
          focusedElement != threadTree.table.body &&
          contentDocument.getElementById("threadPane").contains(focusedElement)
        ) {
          focusedElement = threadTree.table.body;
          adjustment = event.shiftKey ? 0 : -1;
        } else if (focusedElement == messageBrowser) {
          focusedElement = messageBrowser.contentWindow.getMessagePaneBrowser();
        }
      }
      break;
    }
    case "mailMessageTab": {
      let { content } = currentTabInfo.chromeBrowser.contentWindow;
      panes.push(content);
      if (focusedElement == currentTabInfo.chromeBrowser) {
        focusedElement = content;
      }
      break;
    }
    case "addressBookTab": {
      let { booksList, cardsPane, detailsPane } =
        currentTabInfo.browser.contentWindow;

      if (detailsPane.isEditing) {
        panes.push(currentTabInfo.browser);
      } else {
        let targets = [
          booksList,
          cardsPane.searchInput,
          cardsPane.cardsList.table.body,
        ];
        if (!detailsPane.node.hidden && !detailsPane.editButton.hidden) {
          targets.push(detailsPane.editButton);
        }

        if (focusedElement == currentTabInfo.browser) {
          focusedElement = targets.find(t => t.matches(":focus-within"));
        }
        panes.push(...targets);
      }
      break;
    }
    default:
      if (currentTabInfo.browser) {
        panes.push(currentTabInfo.browser);
      }
      break;
  }

  // Find our focused element in the array.
  let focusedElementIndex = panes.indexOf(focusedElement) + adjustment;
  if (event.shiftKey) {
    focusedElementIndex--;
    if (focusedElementIndex < 0) {
      focusedElementIndex = panes.length - 1;
    }
  } else if (focusedElementIndex == -1) {
    focusedElementIndex = 0;
  } else {
    focusedElementIndex++;
    if (focusedElementIndex == panes.length) {
      focusedElementIndex = 0;
    }
  }

  if (panes[focusedElementIndex]) {
    panes[focusedElementIndex].focus();
  }
}

// Override F6 handling for remote browsers, and use our own logic to
// determine the element to focus.
addEventListener(
  "keypress",
  function (event) {
    if (event.key == "F6" && Services.focus.focusedElement?.isRemoteBrowser) {
      event.preventDefault();
      SwitchPaneFocus(event);
    }
  },
  true
);

/**
 * Check the status of the folder pane, if available.
 *
 * @returns {boolean|undefined} The initialization state of the folder pane,
 *   or undefined if we can't access the document.
 */
function isFolderPaneInitialized() {
  return document.getElementById("tabmail")?.currentAbout3Pane?.folderPane
    .isInitialized;
}
