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

/* globals goUpdateCommand */ // From globalOverlay.js

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

var gShowBiDi = false;

function getBrowserURL() {
  return AppConstants.BROWSER_CHROME_URL;
}

// update menu items that rely on focus
function goUpdateGlobalEditMenuItems() {
  goUpdateCommand("cmd_undo");
  goUpdateCommand("cmd_redo");
  goUpdateCommand("cmd_cut");
  goUpdateCommand("cmd_copy");
  goUpdateCommand("cmd_paste");
  goUpdateCommand("cmd_selectAll");
  goUpdateCommand("cmd_delete");
  if (gShowBiDi) {
    goUpdateCommand("cmd_switchTextDirection");
  }
}

// update menu items that rely on the current selection
function goUpdateSelectEditMenuItems() {
  goUpdateCommand("cmd_cut");
  goUpdateCommand("cmd_copy");
  goUpdateCommand("cmd_delete");
  goUpdateCommand("cmd_selectAll");
}

// update menu items that relate to undo/redo
function goUpdateUndoEditMenuItems() {
  goUpdateCommand("cmd_undo");
  goUpdateCommand("cmd_redo");
}

// update menu items that depend on clipboard contents
function goUpdatePasteMenuItems() {
  goUpdateCommand("cmd_paste");
}

// update Find As You Type menu items, they rely on focus
function goUpdateFindTypeMenuItems() {
  goUpdateCommand("cmd_findTypeText");
  goUpdateCommand("cmd_findTypeLinks");
}

/**
 * Gather all descendent text under given node.
 *
 * @param {Node} root - The root node to gather text from.
 * @returns {string} The text data under the node.
 */
function gatherTextUnder(root) {
  var text = "";
  var node = root.firstChild;
  var depth = 1;
  while (node && depth > 0) {
    // See if this node is text.
    if (node.nodeType == Node.TEXT_NODE) {
      // Add this text to our collection.
      text += " " + node.data;
    } else if (HTMLImageElement.isInstance(node)) {
      // If it has an alt= attribute, add that.
      var altText = node.getAttribute("alt");
      if (altText && altText != "") {
        text += " " + altText;
      }
    }
    // Find next node to test.
    if (node.firstChild) {
      // If it has children, go to first child.
      node = node.firstChild;
      depth++;
    } else if (node.nextSibling) {
      // No children, try next sibling.
      node = node.nextSibling;
    } else {
      // Last resort is a sibling of an ancestor.
      while (node && depth > 0) {
        node = node.parentNode;
        depth--;
        if (node.nextSibling) {
          node = node.nextSibling;
          break;
        }
      }
    }
  }
  // Strip leading and trailing whitespace.
  text = text.trim();
  // Compress remaining whitespace.
  text = text.replace(/\s+/g, " ");
  return text;
}

function GenerateValidFilename(filename, extension) {
  if (filename) {
    // we have a title; let's see if it's usable
    // clean up the filename to make it usable and
    // then trim whitespace from beginning and end
    filename = validateFileName(filename).trim();
    if (filename.length > 0) {
      return filename + extension;
    }
  }
  return null;
}

function validateFileName(aFileName) {
  var re = /[\/]+/g;
  if (navigator.appVersion.includes("Windows")) {
    re = /[\\\/\|]+/g;
    aFileName = aFileName.replace(/[\"]+/g, "'");
    aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
    aFileName = aFileName.replace(/[\<]+/g, "(");
    aFileName = aFileName.replace(/[\>]+/g, ")");
  } else if (navigator.appVersion.includes("Macintosh")) {
    re = /[\:\/]+/g;
  }

  if (
    Services.prefs.getBoolPref("mail.save_msg_filename_underscores_for_space")
  ) {
    aFileName = aFileName.replace(/ /g, "_");
  }

  return aFileName.replace(re, "_");
}

function goToggleToolbar(id, elementID) {
  var toolbar = document.getElementById(id);
  var element = document.getElementById(elementID);
  if (toolbar) {
    const isHidden = toolbar.getAttribute("hidden") === "true";
    toolbar.setAttribute("hidden", !isHidden);
    Services.xulStore.persist(toolbar, "hidden");
    if (element) {
      element.setAttribute("checked", isHidden);
      Services.xulStore.persist(element, "checked");
    }
  }
}

/**
 * Toggle a splitter to show or hide some piece of UI (e.g. the message preview
 * pane).
 *
 * @param splitterId the splliter that should be toggled
 */
function togglePaneSplitter(splitterId) {
  var splitter = document.getElementById(splitterId);
  var state = splitter.getAttribute("state");
  if (state == "collapsed") {
    splitter.setAttribute("state", "open");
  } else {
    splitter.setAttribute("state", "collapsed");
  }
}

// openUILink handles clicks on UI elements that cause URLs to load.
// We currently only react to left click in Thunderbird.
function openUILink(url, event) {
  if (!event.button) {
    PlacesUtils.history
      .insert({
        url,
        visits: [
          {
            date: new Date(),
          },
        ],
      })
      .catch(console.error);
    Cc["@mozilla.org/uriloader/external-protocol-service;1"]
      .getService(Ci.nsIExternalProtocolService)
      .loadURI(Services.io.newURI(url));
  }
}

function openLinkText(event, what) {
  switch (what) {
    case "getInvolvedURL":
      openUILink("https://www.thunderbird.net/get-involved/", event);
      break;
    case "keyboardShortcutsURL":
      openUILink("https://support.mozilla.org/kb/keyboard-shortcuts/", event);
      break;
    case "donateURL":
      openUILink(
        "https://give.thunderbird.net/?utm_source=thunderbird-client&utm_medium=referral&utm_content=help-menu",
        event
      );
      break;
    case "tourURL":
      openUILink("https://www.thunderbird.net/features/", event);
      break;
    case "feedbackURL":
      openUILink("https://connect.mozilla.org/", event);
      break;
  }
}

/**
 * Open a web search in the default browser for a given query.
 *
 * @param query the string to search for
 * @param engine (optional) the search engine to use
 */
function openWebSearch(query, engine) {
  return Services.search.init().then(async () => {
    if (!engine) {
      engine = await Services.search.getDefault();
      openLinkExternally(engine.getSubmission(query).uri.spec);

      Services.telemetry.keyedScalarAdd(
        "tb.websearch.usage",
        engine.name.toLowerCase(),
        1
      );
    }
  });
}

/**
 * Open the specified tab type (possibly in a new window)
 *
 * @param tabType the tab type to open (e.g. "contentTab")
 * @param tabParams the parameters to pass to the tab
 * @param where 'tab' to open in a new tab (default) or 'window' to open in a
 *        new window
 */
function openTab(tabType, tabParams, where) {
  if (where != "window") {
    let tabmail = document.getElementById("tabmail");
    if (!tabmail) {
      // Try opening new tabs in an existing 3pane window
      let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
      if (mail3PaneWindow) {
        tabmail = mail3PaneWindow.document.getElementById("tabmail");
        mail3PaneWindow.focus();
      }
    }

    if (tabmail) {
      return tabmail.openTab(tabType, tabParams);
    }
  }

  // Either we explicitly wanted to open in a new window, or we fell through to
  // here because there's no 3pane.
  return window.openDialog(
    "chrome://messenger/content/messenger.xhtml",
    "_blank",
    "chrome,dialog=no,all",
    null,
    {
      tabType,
      tabParams,
    }
  );
}

/**
 * Open the specified URL as a content tab (or window)
 *
 * @param {string} url - The location to open.
 * @param {string} [where="tab"] - 'tab' to open in a new tab or 'window' to
 *     open in a new window
 * @param {string} [linkHandler] - See specialTabs.contentTabType.openTab.
 */
function openContentTab(url, where, linkHandler) {
  return openTab("contentTab", { url, linkHandler }, where);
}

/**
 * Open the preferences page for the specified query in a new tab.
 *
 * @param paneID       ID of prefpane to select automatically.
 * @param scrollPaneTo ID of the element to scroll into view.
 * @param otherArgs    other prefpane specific arguments.
 */
function openPreferencesTab(paneID, scrollPaneTo, otherArgs) {
  openTab("preferencesTab", {
    paneID,
    scrollPaneTo,
    otherArgs,
    onLoad(aEvent, aBrowser) {
      aBrowser.contentWindow.selectPrefPane(paneID, scrollPaneTo, otherArgs);
    },
  });
}

/**
 * Open the dictionary list in a new content tab, if possible in an available
 * mail:3pane window, otherwise by opening a new mail:3pane.
 *
 * @param where the context to open the dictionary list in (e.g. 'tab',
 *        'window'). See openContentTab for more details.
 */
function openDictionaryList(where) {
  let dictUrl = Services.urlFormatter.formatURLPref(
    "spellchecker.dictionaries.download.url"
  );

  openContentTab(dictUrl, where);
}

/**
 * Open the privacy policy in a new content tab, if possible in an available
 * mail:3pane window, otherwise by opening a new mail:3pane.
 *
 * @param where the context to open the privacy policy in (e.g. 'tab',
 *        'window'). See openContentTab for more details.
 */
function openPrivacyPolicy(where) {
  const kTelemetryInfoUrl = "toolkit.telemetry.infoURL";
  let url = Services.prefs.getCharPref(kTelemetryInfoUrl);
  openContentTab(url, where);
}

/**
 * Used by the developer tools (in the toolbox process) and a few toolkit pages
 * for opening URLs.
 *
 * Thunderbird code should avoid using this function.
 *
 * This is similar, but not identical, to the same function in Firefox.
 *
 * @param {string} url - The URL to load.
 * @param {string} [where] - Ignored, only here for compatibility.
 * @param {object} [openParams] - Optional parameters for changing behaviour.
 */
function openTrustedLinkIn(url, where, params = {}) {
  if (!params.triggeringPrincipal) {
    params.triggeringPrincipal =
      Services.scriptSecurityManager.getSystemPrincipal();
  }

  openLinkIn(url, where, params);
}

/**
 * Used by the developer tools (in the toolbox process) for opening URLs.
 * MDN URLs get send to a browser, all others are displayed in a new window.
 *
 * Thunderbird code should avoid using this function.
 *
 * This is similar, but not identical, to the same function in Firefox.
 *
 * @param {string} url - The URL to load.
 * @param {string} [where] - Ignored, only here for compatibility.
 * @param {object} [openParams] - Optional parameters for changing behaviour.
 */
function openWebLinkIn(url, where, params = {}) {
  if (url.startsWith("https://developer.mozilla.org/")) {
    openLinkExternally(url);
    return;
  }

  if (!params.triggeringPrincipal) {
    params.triggeringPrincipal =
      Services.scriptSecurityManager.createNullPrincipal({});
  }
  if (params.triggeringPrincipal.isSystemPrincipal) {
    throw new Error(
      "System principal should never be passed into openWebLinkIn()"
    );
  }

  openLinkIn(url, where, params);
}

// Thunderbird itself is not using this function. It is however called for the
// "contribute" button for add-ons in the add-on manager. We ignore all additional
// parameters including "where" and always open the link externally. We don't
// want to open donation pages in a tab due to their complexity, and we don't
// want to handle them inside Thunderbird.
function openUILinkIn(
  url,
  where,
  aAllowThirdPartyFixup,
  aPostData,
  aReferrerInfo
) {
  openLinkExternally(url);
}

/**
 * Loads a URL in Thunderbird. If this is a mail:3pane window, the URL opens
 * in a content tab, otherwise a new window is opened.
 *
 * This is similar, but not identical, to the same function in Firefox.
 *
 * @param {string} url - The URL to load.
 * @param {string} [where] - Ignored, only here for compatibility.
 * @param {object} [openParams] - Optional parameters for changing behaviour.
 */
function openLinkIn(url, where, openParams) {
  if (!url) {
    return;
  }

  if ("switchToTabHavingURI" in window) {
    window.switchToTabHavingURI(url, true);
    return;
  }

  // If we get here, this isn't a mail:3pane window, which means it's probably
  // the developer tools window and therefore a completely separate program
  // from the rest of Thunderbird. Be careful what you do here.

  let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  let uri = Cc["@mozilla.org/supports-string;1"].createInstance(
    Ci.nsISupportsString
  );
  uri.data = url;
  args.appendElement(uri);

  let win = Services.ww.openWindow(
    window,
    AppConstants.BROWSER_CHROME_URL,
    null,
    "chrome,dialog=no,all",
    args
  );

  if (openParams.resolveOnContentBrowserCreated) {
    win.addEventListener("load", () =>
      openParams.resolveOnContentBrowserCreated(win.gBrowser.selectedBrowser)
    );
  }
}

/**
 * Forces a url to open in an external application according to the protocol
 * service settings.
 *
 * @param url  A url string or an nsIURI containing the url to open.
 */
function openLinkExternally(url) {
  let uri = url;
  if (!(uri instanceof Ci.nsIURI)) {
    uri = Services.io.newURI(url);
  }

  // This can fail if there is a problem with the places database.
  PlacesUtils.history
    .insert({
      url, // accepts both string and nsIURI
      visits: [
        {
          date: new Date(),
        },
      ],
    })
    .catch(console.error);

  Cc["@mozilla.org/uriloader/external-protocol-service;1"]
    .getService(Ci.nsIExternalProtocolService)
    .loadURI(uri);
}

/**
 * Moved from toolkit/content/globalOverlay.js.
 * For details see bug 1422720 and bug 1422721.
 */
function goSetMenuValue(aCommand, aLabelAttribute) {
  var commandNode = top.document.getElementById(aCommand);
  if (commandNode) {
    var label = commandNode.getAttribute(aLabelAttribute);
    if (label) {
      commandNode.setAttribute("label", label);
    }
  }
}

function goSetAccessKey(aCommand, aAccessKeyAttribute) {
  var commandNode = top.document.getElementById(aCommand);
  if (commandNode) {
    var value = commandNode.getAttribute(aAccessKeyAttribute);
    if (value) {
      commandNode.setAttribute("accesskey", value);
    }
  }
}

function buildHelpMenu() {
  let helpTroubleshootModeItem = document.getElementById(
    "helpTroubleshootMode"
  );
  if (helpTroubleshootModeItem) {
    helpTroubleshootModeItem.disabled =
      !Services.policies.isAllowed("safeMode");
  }
  let appmenu_troubleshootModeItem = document.getElementById(
    "appmenu_troubleshootMode"
  );
  if (appmenu_troubleshootModeItem) {
    appmenu_troubleshootModeItem.disabled =
      !Services.policies.isAllowed("safeMode");
  }
}
