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

/* import-globals-from ../../../mailnews/extensions/newsblog/newsblogOverlay.js */
/* import-globals-from ../../../mailnews/search/content/searchTerm.js */
/* import-globals-from folderDisplay.js */
/* import-globals-from globalOverlay.js */
/* import-globals-from threadPane.js */

/* globals nsMsgStatusFeedback */ // From mailWindow.js

"use strict";

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

var messenger;
var msgWindow;

var gCurrentFolder;

var gFolderDisplay;

var gFolderPicker;
var gStatusFeedback;
var gSearchBundle;

// Datasource search listener -- made global as it has to be registered
// and unregistered in different functions.
var gDataSourceSearchListener;
var gViewSearchListener;

var gSearchStopButton;

// Should we try to search online?
var gSearchOnline = false;

window.addEventListener("load", searchOnLoad);
window.addEventListener("unload", event => {
  onSearchStop();
  searchOnUnload();
});

// Controller object for search results thread pane
var nsSearchResultsController = {
  supportsCommand(command) {
    switch (command) {
      case "cmd_delete":
      case "cmd_shiftDelete":
      case "button_delete":
      case "cmd_open":
      case "file_message_button":
      case "open_in_folder_button":
      case "saveas_vf_button":
      case "cmd_selectAll":
        return true;
      default:
        return false;
    }
  },

  // this controller only handles commands
  // that rely on items being selected in
  // the search results pane.
  isCommandEnabled(command) {
    var enabled = true;

    switch (command) {
      case "open_in_folder_button":
        if (gFolderDisplay.selectedCount != 1) {
          enabled = false;
        }
        break;
      case "cmd_delete":
      case "cmd_shiftDelete":
      case "button_delete":
        // this assumes that advanced searches don't cross accounts
        if (gFolderDisplay.selectedCount <= 0) {
          enabled = false;
        }
        break;
      case "saveas_vf_button":
        // need someway to see if there are any search criteria...
        return true;
      case "cmd_selectAll":
        return true;
      default:
        if (gFolderDisplay.selectedCount <= 0) {
          enabled = false;
        }
        break;
    }

    return enabled;
  },

  doCommand(command) {
    switch (command) {
      case "cmd_open":
        MsgOpenSelectedMessages();
        return true;

      case "cmd_delete":
      case "button_delete":
        MsgDeleteSelectedMessages(Ci.nsMsgViewCommandType.deleteMsg);
        return true;

      case "cmd_shiftDelete":
        MsgDeleteSelectedMessages(Ci.nsMsgViewCommandType.deleteNoTrash);
        return true;

      case "open_in_folder_button":
        OpenInFolder();
        return true;

      case "saveas_vf_button":
        saveAsVirtualFolder();
        return true;

      case "cmd_selectAll":
        // move the focus to the search results pane
        GetThreadTree().focus();
        gFolderDisplay.doCommand(Ci.nsMsgViewCommandType.selectAll);
        return true;

      default:
        return false;
    }
  },

  onEvent(event) {},
};

function UpdateMailSearch(caller) {
  document.commandDispatcher.updateCommands("mail-search");
}

function SetAdvancedSearchStatusText(aNumHits) {}

/**
 * Subclass the FolderDisplayWidget to deal with UI specific to the search
 *  window.
 */
function SearchFolderDisplayWidget() {
  FolderDisplayWidget.call(this);
}

SearchFolderDisplayWidget.prototype = {
  __proto__: FolderDisplayWidget.prototype,

  // folder display will want to show the thread pane; we need do nothing
  _showThreadPane() {},

  onSearching(aIsSearching) {
    if (aIsSearching) {
      // Search button becomes the "stop" button
      gSearchStopButton.setAttribute(
        "label",
        gSearchBundle.GetStringFromName("labelForStopButton")
      );
      gSearchStopButton.setAttribute(
        "accesskey",
        gSearchBundle.GetStringFromName("labelForStopButton.accesskey")
      );

      // update our toolbar equivalent
      UpdateMailSearch("new-search");
      // spin the meteors
      gStatusFeedback._startMeteors();
      // tell the user that we're searching
      gStatusFeedback.showStatusString(
        gSearchBundle.GetStringFromName("searchingMessage")
      );
    } else {
      // Stop button resumes being the "search" button
      gSearchStopButton.setAttribute(
        "label",
        gSearchBundle.GetStringFromName("labelForSearchButton")
      );
      gSearchStopButton.setAttribute(
        "accesskey",
        gSearchBundle.GetStringFromName("labelForSearchButton.accesskey")
      );

      // update our toolbar equivalent
      UpdateMailSearch("done-search");
      // stop spining the meteors
      gStatusFeedback._stopMeteors();
      // set the result test
      this.updateStatusResultText();
    }
  },

  /**
   * If messages were removed, we might have lost some search results and so
   *  should update our search result text.  Also, defer to our super-class.
   */
  onMessagesRemoved() {
    // result text is only for when we are not searching
    if (!this.view.searching) {
      this.updateStatusResultText();
    }
    this.__proto__.__proto__.onMessagesRemoved.call(this);
  },

  updateStatusResultText() {
    let rowCount = this.view.dbView.rowCount;
    let statusMsg;

    if (rowCount == 0) {
      statusMsg = gSearchBundle.GetStringFromName("noMatchesFound");
    } else {
      statusMsg = PluralForm.get(
        rowCount,
        gSearchBundle.GetStringFromName("matchesFound")
      );
      statusMsg = statusMsg.replace("#1", rowCount);
    }

    gStatusFeedback.showStatusString(statusMsg);
  },
};

function searchOnLoad() {
  TagUtils.loadTagsIntoCSS(document);
  initializeSearchWidgets();
  initializeSearchWindowWidgets();
  // eslint-disable-next-line no-global-assign
  messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);

  gSearchBundle = Services.strings.createBundle(
    "chrome://messenger/locale/search.properties"
  );
  gSearchStopButton.setAttribute(
    "label",
    gSearchBundle.GetStringFromName("labelForSearchButton")
  );
  gSearchStopButton.setAttribute(
    "accesskey",
    gSearchBundle.GetStringFromName("labelForSearchButton.accesskey")
  );

  // eslint-disable-next-line no-global-assign
  gFolderDisplay = new SearchFolderDisplayWidget();
  gFolderDisplay.messenger = messenger;
  gFolderDisplay.msgWindow = msgWindow;
  gFolderDisplay.tree = document.getElementById("threadTree");

  // The view is initially unsorted; get the persisted sortDirection column
  // and set up the user's desired sort. This synthetic view is not backed by
  // a db, so secondary sorts and custom columns are not supported here.
  let sortCol = gFolderDisplay.tree.querySelector("[sortDirection]");
  let sortType, sortOrder;
  if (sortCol) {
    sortType = Ci.nsMsgViewSortType[gFolderDisplay.COLUMNS_MAP.get(sortCol.id)];
    sortOrder =
      sortCol.getAttribute("sortDirection") == "descending"
        ? Ci.nsMsgViewSortOrder.descending
        : Ci.nsMsgViewSortOrder.ascending;
  }

  gFolderDisplay.view.openSearchView();
  gFolderDisplay.makeActive();

  if (sortType) {
    gFolderDisplay.view.sort(sortType, sortOrder);
  }

  if (window.arguments && window.arguments[0]) {
    updateSearchFolderPicker(window.arguments[0].folder);
  }

  // Trigger searchTerm.js to create the first criterion.
  onMore(null);
  // Make sure all the buttons are configured.
  UpdateMailSearch("onload");
}

function searchOnUnload() {
  gFolderDisplay.close();
  top.controllers.removeController(nsSearchResultsController);

  msgWindow.closeWindow();
}

function initializeSearchWindowWidgets() {
  gFolderPicker = document.getElementById("searchableFolders");
  gSearchStopButton = document.getElementById("search-button");
  hideMatchAllItem();

  // eslint-disable-next-line no-global-assign
  msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
    Ci.nsIMsgWindow
  );
  msgWindow.domWindow = window;
  msgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;

  gStatusFeedback = new nsMsgStatusFeedback();
  msgWindow.statusFeedback = gStatusFeedback;

  // functionality to enable/disable buttons using nsSearchResultsController
  // depending of whether items are selected in the search results thread pane.
  top.controllers.insertControllerAt(0, nsSearchResultsController);
}

function onSearchStop() {
  gFolderDisplay.view.search.session.interruptSearch();
}

function onResetSearch(event) {
  onReset(event);
  gFolderDisplay.view.search.clear();

  gStatusFeedback.showStatusString("");
}

function updateSearchFolderPicker(folder) {
  gCurrentFolder = folder;
  gFolderPicker.menupopup.selectFolder(folder);

  var searchOnline = document.getElementById("checkSearchOnline");
  // We will hide and disable the search online checkbox if we are offline, or
  // if the folder does not support online search.

  // Any offlineSupportLevel > 0 is an online server like IMAP or news.
  if (gCurrentFolder?.server.offlineSupportLevel && !Services.io.offline) {
    searchOnline.hidden = false;
    searchOnline.disabled = false;
  } else {
    searchOnline.hidden = true;
    searchOnline.disabled = true;
  }
  if (gCurrentFolder) {
    setSearchScope(GetScopeForFolder(gCurrentFolder));
  }
}

function updateSearchLocalSystem() {
  setSearchScope(GetScopeForFolder(gCurrentFolder));
}

function UpdateAfterCustomHeaderChange() {
  updateSearchAttributes();
}

function onEnterInSearchTerm() {
  // on enter
  // if not searching, start the search
  // if searching, stop and then start again
  if (
    gSearchStopButton.getAttribute("label") ==
    gSearchBundle.GetStringFromName("labelForSearchButton")
  ) {
    onSearch();
  } else {
    onSearchStop();
    onSearch();
  }
}

function onSearch() {
  let viewWrapper = gFolderDisplay.view;
  let searchTerms = getSearchTerms();

  viewWrapper.beginViewUpdate();
  viewWrapper.search.userTerms = searchTerms.length ? searchTerms : null;
  viewWrapper.search.onlineSearch = gSearchOnline;
  viewWrapper.searchFolders = getSearchFolders();
  viewWrapper.endViewUpdate();
}

/**
 * Get the current set of search terms, returning them as a list.  We filter out
 *  dangerous and insane predicates.
 */
function getSearchTerms() {
  let termCreator = gFolderDisplay.view.search.session;

  let searchTerms = [];
  // searchTerm.js stores wrapper objects in its gSearchTerms array.  Pluck
  //  them.
  for (let iTerm = 0; iTerm < gSearchTerms.length; iTerm++) {
    let termWrapper = gSearchTerms[iTerm].obj;
    let realTerm = termCreator.createTerm();
    termWrapper.saveTo(realTerm);
    // A header search of "" is illegal for IMAP and will cause us to
    //  explode.  You don't want that and I don't want that.  So let's check
    //  if the bloody term is a subject search on a blank string, and if it
    //  is, let's secretly not add the term.  Everyone wins!
    if (
      realTerm.attrib != Ci.nsMsgSearchAttrib.Subject ||
      realTerm.value.str != ""
    ) {
      searchTerms.push(realTerm);
    }
  }

  return searchTerms;
}

/**
 * @returns the list of folders the search should cover.
 */
function getSearchFolders() {
  let searchFolders = [];

  if (!gCurrentFolder.isServer && !gCurrentFolder.noSelect) {
    searchFolders.push(gCurrentFolder);
  }

  var searchSubfolders = document.getElementById(
    "checkSearchSubFolders"
  ).checked;
  if (
    gCurrentFolder &&
    (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect)
  ) {
    AddSubFolders(gCurrentFolder, searchFolders);
  }

  return searchFolders;
}

function AddSubFolders(folder, outFolders) {
  for (let nextFolder of folder.subFolders) {
    if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual)) {
      if (!nextFolder.noSelect) {
        outFolders.push(nextFolder);
      }

      AddSubFolders(nextFolder, outFolders);
    }
  }
}

function AddSubFoldersToURI(folder) {
  var returnString = "";

  for (let nextFolder of folder.subFolders) {
    if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual)) {
      if (!nextFolder.noSelect && !nextFolder.isServer) {
        if (returnString.length > 0) {
          returnString += "|";
        }
        returnString += nextFolder.URI;
      }
      var subFoldersString = AddSubFoldersToURI(nextFolder);
      if (subFoldersString.length > 0) {
        if (returnString.length > 0) {
          returnString += "|";
        }
        returnString += subFoldersString;
      }
    }
  }
  return returnString;
}

/**
 * Determine the proper search scope to use for a folder, so that the user is
 *  presented with a correct list of search capabilities. The user may manually
 *  request on online search for certain server types. To determine if the
 *  folder body may be searched, we ignore whether autosync is enabled,
 *  figuring that after the user manually syncs, they would still expect that
 *  body searches would work.
 *
 * The available search capabilities also depend on whether the user is
 *  currently online or offline. Although that is also checked by the server,
 *  we do it ourselves because we have a more complex response to offline
 *  than the server's searchScope attribute provides.
 *
 * This method only works for real folders.
 */
function GetScopeForFolder(folder) {
  let searchOnline = document.getElementById("checkSearchOnline");
  if (!searchOnline.disabled && searchOnline.checked) {
    gSearchOnline = true;
    return folder.server.searchScope;
  }
  gSearchOnline = false;

  // We are going to search offline. The proper search scope may depend on
  // whether we have the body and/or junk available or not.
  let localType;
  try {
    localType = folder.server.localStoreType;
  } catch (e) {} // On error, we'll just assume the default mailbox type

  let hasBody = folder.getFlag(Ci.nsMsgFolderFlags.Offline);
  let nsMsgSearchScope = Ci.nsMsgSearchScope;
  switch (localType) {
    case "news":
      // News has four offline scopes, depending on whether junk and body
      // are available.
      let hasJunk =
        folder.getInheritedStringProperty(
          "dobayes.mailnews@mozilla.org#junk"
        ) == "true";
      if (hasJunk && hasBody) {
        return nsMsgSearchScope.localNewsJunkBody;
      }
      if (hasJunk) {
        // and no body
        return nsMsgSearchScope.localNewsJunk;
      }
      if (hasBody) {
        // and no junk
        return nsMsgSearchScope.localNewsBody;
      }
      // We don't have offline message bodies or junk processing.
      return nsMsgSearchScope.localNews;

    case "imap":
      // Junk is always enabled for imap, so the offline scope only depends on
      // whether the body is available.

      // If we are the root folder, use the server property for body rather
      // than the folder property.
      if (folder.isServer) {
        let imapServer = folder.server.QueryInterface(Ci.nsIImapIncomingServer);
        if (imapServer && imapServer.offlineDownload) {
          hasBody = true;
        }
      }

      if (!hasBody) {
        return nsMsgSearchScope.onlineManual;
      }
    // fall through to default
    default:
      return nsMsgSearchScope.offlineMail;
  }
}

function goUpdateSearchItems(commandset) {
  for (var i = 0; i < commandset.children.length; i++) {
    var commandID = commandset.children[i].getAttribute("id");
    if (commandID) {
      goUpdateCommand(commandID);
    }
  }
}

// used to toggle functionality for Search/Stop button.
function onSearchButton(event) {
  if (
    event.target.label ==
    gSearchBundle.GetStringFromName("labelForSearchButton")
  ) {
    onSearch();
  } else {
    onSearchStop();
  }
}

function MsgDeleteSelectedMessages(aCommandType) {
  gFolderDisplay.hintAboutToDeleteMessages();
  gFolderDisplay.doCommand(aCommandType);
}

/**
 * Move selected messages to the destination folder
 *
 * @param destFolder {nsIMsgFolder} - destination folder
 */
function MoveMessageInSearch(destFolder) {
  gFolderDisplay.hintAboutToDeleteMessages();
  gFolderDisplay.doCommandWithFolder(
    Ci.nsMsgViewCommandType.moveMessages,
    destFolder
  );
}

function OpenInFolder() {
  MailUtils.displayMessageInFolderTab(gFolderDisplay.selectedMessage);
}

function saveAsVirtualFolder() {
  var searchFolderURIs = gCurrentFolder.URI;

  var searchSubfolders = document.getElementById(
    "checkSearchSubFolders"
  ).checked;
  if (
    gCurrentFolder &&
    (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect)
  ) {
    var subFolderURIs = AddSubFoldersToURI(gCurrentFolder);
    if (subFolderURIs.length > 0) {
      searchFolderURIs += "|" + subFolderURIs;
    }
  }

  var searchOnline = document.getElementById("checkSearchOnline");
  var doOnlineSearch = searchOnline.checked && !searchOnline.disabled;

  window.openDialog(
    "chrome://messenger/content/virtualFolderProperties.xhtml",
    "",
    "chrome,titlebar,modal,centerscreen,resizable=yes",
    {
      folder: window.arguments[0].folder,
      searchTerms: getSearchTerms(),
      searchFolderURIs,
      searchOnline: doOnlineSearch,
    }
  );
}

function MsgOpenSelectedMessages() {
  // Toggle message body (feed summary) and content-base url in message pane or
  // load in browser, per pref, otherwise open summary or web page in new window
  // or tab, per that pref.
  if (
    gFolderDisplay.treeSelection &&
    gFolderDisplay.treeSelection.count == 1 &&
    gFolderDisplay.selectedMessageIsFeed
  ) {
    let msgHdr = gFolderDisplay.selectedMessage;
    if (
      document.documentElement.getAttribute("windowtype") == "mail:3pane" &&
      FeedMessageHandler.onOpenPref ==
        FeedMessageHandler.kOpenToggleInMessagePane
    ) {
      let showSummary = FeedMessageHandler.shouldShowSummary(msgHdr, true);
      FeedMessageHandler.setContent(msgHdr, showSummary);
      return;
    }
    if (
      FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenLoadInBrowser
    ) {
      setTimeout(FeedMessageHandler.loadWebPage, 20, msgHdr, { browser: true });
      return;
    }
  }

  // This is somewhat evil. If we're in a 3pane window, we'd have a tabmail
  // element and would pass it in here, ensuring that if we open tabs, we use
  // this tabmail to open them. If we aren't, then we wouldn't, so
  // displayMessages would look for a 3pane window and open tabs there.
  MailUtils.displayMessages(
    gFolderDisplay.selectedMessages,
    gFolderDisplay.view,
    document.getElementById("tabmail")
  );
}
