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

let { MsgUtils } = ChromeUtils.import(
  "resource:///modules/MimeMessageUtils.jsm"
);

var gCloudAttachmentLinkManager = {
  init() {
    this.cloudAttachments = [];

    let bucket = document.getElementById("attachmentBucket");
    bucket.addEventListener("attachments-removed", this);
    bucket.addEventListener("attachment-converted-to-regular", this);
    bucket.addEventListener("attachment-uploaded", this);
    bucket.addEventListener("attachment-moved", this);
    bucket.addEventListener("attachment-renamed", this);

    // If we're restoring a draft that has some attachments,
    // check to see if any of them are marked to be sent via
    // cloud, and if so, add them to our list.
    for (let i = 0; i < bucket.getRowCount(); ++i) {
      let attachment = bucket.getItemAtIndex(i).attachment;
      if (attachment && attachment.sendViaCloud) {
        this.cloudAttachments.push(attachment);
      }
    }

    gMsgCompose.RegisterStateListener(this);
  },

  NotifyComposeFieldsReady() {},
  NotifyComposeBodyReady() {},
  ComposeProcessDone() {},
  SaveInFolderDone() {},

  async handleEvent(event) {
    let mailDoc = document.getElementById("messageEditor").contentDocument;

    if (
      event.type == "attachment-renamed" ||
      event.type == "attachment-moved"
    ) {
      let cloudFileUpload = event.target.cloudFileUpload;
      let items = [];

      let list = mailDoc.getElementById("cloudAttachmentList");
      if (list) {
        items = list.getElementsByClassName("cloudAttachmentItem");
      }

      for (let item of items) {
        // The original attachment is stored in the events detail property.
        if (item.dataset.contentLocation == event.detail.contentLocation) {
          item.replaceWith(await this._createNode(mailDoc, cloudFileUpload));
        }
      }
      if (event.type == "attachment-moved") {
        await this._updateServiceProviderLinks(mailDoc);
      }
    } else if (event.type == "attachment-uploaded") {
      if (this.cloudAttachments.length == 0) {
        this._insertHeader(mailDoc);
      }

      let cloudFileUpload = event.target.cloudFileUpload;
      let attachment = event.target.attachment;
      this.cloudAttachments.push(attachment);
      await this._insertItem(mailDoc, cloudFileUpload);
    } else if (
      event.type == "attachments-removed" ||
      event.type == "attachment-converted-to-regular"
    ) {
      let items = [];
      let list = mailDoc.getElementById("cloudAttachmentList");
      if (list) {
        items = list.getElementsByClassName("cloudAttachmentItem");
      }

      let attachments = Array.isArray(event.detail)
        ? event.detail
        : [event.detail];
      for (let attachment of attachments) {
        // Remove the attachment from the message body.
        if (list) {
          for (let item of items) {
            if (item.dataset.contentLocation == attachment.contentLocation) {
              item.remove();
            }
          }
        }

        // Now, remove the attachment from our internal list.
        let index = this.cloudAttachments.indexOf(attachment);
        if (index != -1) {
          this.cloudAttachments.splice(index, 1);
        }
      }

      await this._updateAttachmentCount(mailDoc);
      await this._updateServiceProviderLinks(mailDoc);

      if (items.length == 0) {
        if (list) {
          list.remove();
        }
        this._removeRoot(mailDoc);
      }
    }
  },

  /**
   * Removes the root node for an attachment list in an HTML email.
   *
   * @param {Document} aDocument - the document to remove the root node from
   */
  _removeRoot(aDocument) {
    let header = aDocument.getElementById("cloudAttachmentListRoot");
    if (header) {
      header.remove();
    }
  },

  /**
   * Given some node, returns the textual HTML representation for the node
   * and its children.
   *
   * @param {Document} aDocument - the document that the node is embedded in
   * @param {DOMNode} aNode - the node to get the textual representation from
   */
  _getHTMLRepresentation(aDocument, aNode) {
    let tmp = aDocument.createElement("p");
    tmp.appendChild(aNode);
    return tmp.innerHTML;
  },

  /**
   * Returns the plain text equivalent of the given HTML markup, ready to be
   * inserted into a compose editor.
   *
   * @param {string} aMarkup - the HTML markup that should be converted
   */
  _getTextRepresentation(aMarkup) {
    return MsgUtils.convertToPlainText(aMarkup, true).replaceAll("\r\n", "\n");
  },

  /**
   * Generates an appropriately styled link.
   *
   * @param {Document} aDocument - the document to append the link to - doesn't
   *   actually get appended, but is used to generate the anchor node
   * @param {string} aContent - the textual content of the link
   * @param {string} aHref - the HREF attribute for the generated link
   * @param {string} aColor - the CSS color string for the link
   */
  _generateLink(aDocument, aContent, aHref, aColor) {
    let link = aDocument.createElement("a");
    link.href = aHref;
    link.textContent = aContent;
    link.style.cssText = `color: ${aColor} !important`;
    return link;
  },

  _findInsertionPoint(aDocument) {
    let mailBody = aDocument.querySelector("body");
    let editor = GetCurrentEditor();
    let selection = editor.selection;

    let childNodes = mailBody.childNodes;
    let childToInsertAfter, childIndex;

    // First, search for any text nodes that are immediate children of
    // the body.  If we find any, we'll insert after those.
    for (childIndex = childNodes.length - 1; childIndex >= 0; childIndex--) {
      if (childNodes[childIndex].nodeType == Node.TEXT_NODE) {
        childToInsertAfter = childNodes[childIndex];
        break;
      }
    }

    if (childIndex != -1) {
      selection.collapse(
        childToInsertAfter,
        childToInsertAfter.nodeValue ? childToInsertAfter.nodeValue.length : 0
      );
      if (
        childToInsertAfter.nodeValue &&
        childToInsertAfter.nodeValue.length > 0
      ) {
        editor.insertLineBreak();
      }
      editor.insertLineBreak();
      return;
    }

    // If there's a signature, let's get a hold of it now.
    let signature = mailBody.querySelector(".moz-signature");

    // Are we replying?
    let replyCitation = mailBody.querySelector(".moz-cite-prefix");
    if (replyCitation) {
      if (gCurrentIdentity && gCurrentIdentity.replyOnTop == 0) {
        // Replying below quote - we'll select the point right before
        // the signature.  If there's no signature, we'll just use the
        // last node.
        if (signature && signature.previousSibling) {
          selection.collapse(
            mailBody,
            Array.from(childNodes).indexOf(signature.previousSibling)
          );
        } else {
          selection.collapse(mailBody, childNodes.length - 1);
          editor.insertLineBreak();

          if (!gMsgCompose.composeHTML) {
            editor.insertLineBreak();
          }

          selection.collapse(mailBody, childNodes.length - 2);
        }
      } else if (replyCitation.previousSibling) {
        // Replying above quote
        let nodeIndex = Array.from(childNodes).indexOf(
          replyCitation.previousSibling
        );
        if (nodeIndex <= 0) {
          editor.insertLineBreak();
          nodeIndex = 1;
        }
        selection.collapse(mailBody, nodeIndex);
      } else {
        editor.beginningOfDocument();
        editor.insertLineBreak();
      }
      return;
    }

    // Are we forwarding?
    let forwardBody = mailBody.querySelector(".moz-forward-container");
    if (forwardBody) {
      if (forwardBody.previousSibling) {
        let nodeIndex = Array.from(childNodes).indexOf(
          forwardBody.previousSibling
        );
        if (nodeIndex <= 0) {
          editor.insertLineBreak();
          nodeIndex = 1;
        }
        // If we're forwarding, insert just before the forward body.
        selection.collapse(mailBody, nodeIndex);
      } else {
        // Just insert after a linebreak at the top.
        editor.beginningOfDocument();
        editor.insertLineBreak();
        selection.collapse(mailBody, 1);
      }
      return;
    }

    // If we haven't figured it out at this point, let's see if there's a
    // signature, and just insert before it.
    if (signature && signature.previousSibling) {
      let nodeIndex = Array.from(childNodes).indexOf(signature.previousSibling);
      if (nodeIndex <= 0) {
        editor.insertLineBreak();
        nodeIndex = 1;
      }
      selection.collapse(mailBody, nodeIndex);
      return;
    }

    // If we haven't figured it out at this point, let's just put it
    // at the bottom of the message body.  If the "bottom" is also the top,
    // then we'll insert a linebreak just above it.
    let nodeIndex = childNodes.length - 1;
    if (nodeIndex <= 0) {
      editor.insertLineBreak();
      nodeIndex = 1;
    }
    selection.collapse(mailBody, nodeIndex);
  },

  /**
   * Attempts to find any elements with an id in aIDs, and sets those elements
   * id attribute to the empty string, freeing up the ids for later use.
   *
   * @param {Document} aDocument - the document to search for the elements
   * @param {string[]} aIDs - an array of id strings
   */
  _resetNodeIDs(aDocument, aIDs) {
    for (let id of aIDs) {
      let node = aDocument.getElementById(id);
      if (node) {
        node.id = "";
      }
    }
  },

  /**
   * Insert the header for the cloud attachment list, which we'll use to
   * as an insertion point for the individual cloud attachments.
   *
   * @param {Document} aDocument - the document to insert the header into
   */
  _insertHeader(aDocument) {
    // If there already exists a cloudAttachmentListRoot,
    // cloudAttachmentListHeader, cloudAttachmentListFooter or
    // cloudAttachmentList in the document, strip them of their IDs so that we
    // don't conflict with them.
    this._resetNodeIDs(aDocument, [
      "cloudAttachmentListRoot",
      "cloudAttachmentListHeader",
      "cloudAttachmentList",
      "cloudAttachmentListFooter",
    ]);

    let editor = GetCurrentEditor();
    let selection = editor.selection;
    let originalAnchor = selection.anchorNode;
    let originalOffset = selection.anchorOffset;

    // Save off the selection ranges so we can restore them later.
    let ranges = [];
    for (let i = 0; i < selection.rangeCount; i++) {
      ranges.push(selection.getRangeAt(i));
    }

    this._findInsertionPoint(aDocument);

    let root = editor.createElementWithDefaults("div");
    let header = editor.createElementWithDefaults("div");
    let list = editor.createElementWithDefaults("div");
    let footer = editor.createElementWithDefaults("div");

    if (gMsgCompose.composeHTML) {
      root.style.padding = "15px";
      root.style.backgroundColor = "#D9EDFF";

      header.style.marginBottom = "15px";

      list = editor.createElementWithDefaults("ul");
      list.style.backgroundColor = "#FFFFFF";
      list.style.padding = "15px";
      list.style.listStyleType = "none";
      list.display = "inline-block";
    }

    root.id = "cloudAttachmentListRoot";
    header.id = "cloudAttachmentListHeader";
    list.id = "cloudAttachmentList";
    footer.id = "cloudAttachmentListFooter";

    // It's really quite strange, but if we don't set
    // the innerHTML of each element to be non-empty, then
    // the nodes fail to be added to the compose window.
    root.innerHTML = " ";
    header.innerHTML = " ";
    list.innerHTML = " ";
    footer.innerHTML = " ";

    root.appendChild(header);
    root.appendChild(list);
    root.appendChild(footer);
    editor.insertElementAtSelection(root, false);
    if (!root.previousSibling || root.previousSibling.localName == "span") {
      root.parentNode.insertBefore(editor.document.createElement("br"), root);
    }

    // Remove the space, which would end up in the plain text converted
    // version.
    list.innerHTML = "";
    selection.collapse(originalAnchor, originalOffset);

    // Restore the selection ranges.
    for (let range of ranges) {
      selection.addRange(range);
    }
  },

  /**
   * Updates the count of how many attachments have been added
   * in HTML emails.
   *
   * @param {Document} aDocument - the document that contains the header node
   */
  async _updateAttachmentCount(aDocument) {
    let header = aDocument.getElementById("cloudAttachmentListHeader");
    if (!header) {
      return;
    }

    let entries = aDocument.querySelectorAll(
      "#cloudAttachmentList > .cloudAttachmentItem"
    );

    header.textContent = await l10nCompose.formatValue(
      "cloud-file-count-header",
      {
        count: entries.length,
      }
    );
  },

  /**
   * Updates the service provider links in the footer.
   *
   * @param {Document} aDocument - the document that contains the footer node
   */
  async _updateServiceProviderLinks(aDocument) {
    let footer = aDocument.getElementById("cloudAttachmentListFooter");
    if (!footer) {
      return;
    }

    let providers = [];
    let entries = aDocument.querySelectorAll(
      "#cloudAttachmentList > .cloudAttachmentItem"
    );
    for (let entry of entries) {
      if (!entry.dataset.serviceUrl) {
        continue;
      }

      let link_markup = this._generateLink(
        aDocument,
        entry.dataset.serviceName,
        entry.dataset.serviceUrl,
        "dark-grey"
      ).outerHTML;

      if (!providers.includes(link_markup)) {
        providers.push(link_markup);
      }
    }

    let content = "";
    if (providers.length == 1) {
      content = await l10nCompose.formatValue(
        "cloud-file-service-provider-footer-single",
        {
          link: providers[0],
        }
      );
    } else if (providers.length > 1) {
      let lastLink = providers.pop();
      let firstLinks = providers.join(", ");
      content = await l10nCompose.formatValue(
        "cloud-file-service-provider-footer-multiple",
        {
          firstLinks,
          lastLink,
        }
      );
    }

    if (gMsgCompose.composeHTML) {
      // eslint-disable-next-line no-unsanitized/property
      footer.innerHTML = content;
    } else {
      footer.textContent = this._getTextRepresentation(content);
    }
  },

  /**
   * Insert the information for a cloud attachment.
   *
   * @param {Document} aDocument - the document to insert the item into
   * @param {CloudFileTemplate} aCloudFileUpload - object with information about
   *   the uploaded file
   */
  async _insertItem(aDocument, aCloudFileUpload) {
    let list = aDocument.getElementById("cloudAttachmentList");

    if (!list) {
      this._insertHeader(aDocument);
      list = aDocument.getElementById("cloudAttachmentList");
    }
    list.appendChild(await this._createNode(aDocument, aCloudFileUpload));
    await this._updateAttachmentCount(aDocument);
    await this._updateServiceProviderLinks(aDocument);
  },

  /**
   * @typedef CloudFileDate
   * @property {integer} timestamp - milliseconds since epoch
   * @property {DateTimeFormat} format - format object of Intl.DateTimeFormat
   */

  /**
   * @typedef CloudFileTemplate
   * @property {string} serviceName - name of the upload service provider
   * @property {string} serviceIcon - icon of the upload service provider
   * @property {string} serviceUrl - web interface of the upload service provider
   * @property {boolean} downloadPasswordProtected - link is password protected
   * @property {integer} downloadLimit - download limit of the link
   * @property {CloudFileDate} downloadExpiryDate - expiry date of the link
   */

  /**
   * Create the link node for a cloud attachment.
   *
   * @param {Document} aDocument - the document to insert the item into
   * @param {CloudFileTemplate} aCloudFileUpload - object with information about
   *   the uploaded file
   * @param {boolean} composeHTML - override gMsgCompose.composeHTML
   */
  async _createNode(
    aDocument,
    aCloudFileUpload,
    composeHTML = gMsgCompose.composeHTML
  ) {
    const iconSize = 32;
    const locales = {
      service: 0,
      size: 1,
      link: 2,
      "password-protected-link": 3,
      "expiry-date": 4,
      "download-limit": 5,
      "tooltip-password-protected-link": 6,
    };

    let l10n_values = await l10nCompose.formatValues([
      { id: "cloud-file-template-service-name" },
      { id: "cloud-file-template-size" },
      { id: "cloud-file-template-link" },
      { id: "cloud-file-template-password-protected-link" },
      { id: "cloud-file-template-expiry-date" },
      { id: "cloud-file-template-download-limit" },
      { id: "cloud-file-tooltip-password-protected-link" },
    ]);

    let node = aDocument.createElement("li");
    node.style.border = "1px solid #CDCDCD";
    node.style.borderRadius = "5px";
    node.style.marginTop = "10px";
    node.style.marginBottom = "10px";
    node.style.padding = "15px";
    node.style.display = "grid";
    node.style.gridTemplateColumns = "0fr 1fr 0fr 0fr";
    node.style.alignItems = "center";

    const statsRow = (name, content, contentLink) => {
      let entry = aDocument.createElement("span");
      entry.style.gridColumn = `2 / span 3`;
      entry.style.fontSize = "small";

      let description = aDocument.createElement("span");
      description.style.color = "dark-grey";
      description.textContent = `${l10n_values[locales[name]]} `;
      entry.appendChild(description);

      let value;
      if (composeHTML && contentLink) {
        value = this._generateLink(aDocument, content, contentLink, "#595959");
      } else {
        value = aDocument.createElement("span");
        value.style.color = "#595959";
        value.textContent = content;
      }
      value.classList.add(`cloudfile-${name}`);
      entry.appendChild(value);

      entry.appendChild(aDocument.createElement("br"));
      return entry;
    };

    const serviceRow = () => {
      let service = aDocument.createDocumentFragment();

      let description = aDocument.createElement("span");
      description.style.display = "none";
      description.textContent = `${l10n_values[locales.service]} `;
      service.appendChild(description);

      let providerName = aDocument.createElement("span");
      providerName.style.gridArea = "1 / 4";
      providerName.style.color = "#595959";
      providerName.style.fontSize = "small";
      providerName.textContent = aCloudFileUpload.serviceName;
      providerName.classList.add("cloudfile-service-name");
      service.appendChild(providerName);

      service.appendChild(aDocument.createElement("br"));
      return service;
    };

    // If this message is send in plain text only, do not add a link to the file
    // name.
    let name = aDocument.createElement("span");
    name.textContent = aCloudFileUpload.name;
    if (composeHTML) {
      name = this._generateLink(
        aDocument,
        aCloudFileUpload.name,
        aCloudFileUpload.url,
        "#0F7EDB"
      );
      name.setAttribute("moz-do-not-send", "true");
      name.style.gridArea = "1 / 2";
    }
    name.classList.add("cloudfile-name");
    node.appendChild(name);

    let paperclip = aDocument.createElement("img");
    paperclip.classList.add("paperClipIcon");
    paperclip.style.gridArea = "1 / 1";
    paperclip.alt = "";
    paperclip.style.marginRight = "5px";
    paperclip.width = `${iconSize}`;
    paperclip.height = `${iconSize}`;
    if (aCloudFileUpload.downloadPasswordProtected) {
      paperclip.title = l10n_values[locales["tooltip-password-protected-link"]];
      paperclip.src =
        "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIfSURBVFhH7ZfLK0RRHMfvNd6PMV4Lj5UkO5bslJIdf4ClRw2TlY2yt2EhsZO9DYoFoiSvJBZkI6SsNMyIiLnH93vmXDF5HNe9pHzqM797fufMPb+Zc4Z7jC+QBnvgJryD93AddkH2eUop3IPiHXdgCfSEdLgLOdE+bIFFSl4zZxeRAl2HXzsn2IIZTCTAHPs4hsvhOlxz3rxRtt6GfRyzJlsucw1582zZehv2cUxEtlyGN6afkThuFa7EL7+H0wK03pek4q/xJwtYVv4YumurO+4V/3vgvwAvC5iHTfHL9zFV/Ah7J9tjE9s2r/K3YwWlD8IaREP+ExPCWBDJVl+gM3LEto0nBURHCiuNpBiflvLjqWcufDFfdVbo4ly1PVoC0xrAaz4qnLdiVjk1hVhArvDRFxuSYxQeFSAaGHzCbAuEIsf0URjtsithX3i1Cf18yewKn8kWyOu+OlWXuSpKnBRwpWKxioTXi7BCtr6Ak004BZvhJAwyAUZhb3Q0bwKxXmY+xVzyB8MNOgXwE/NrC0A+clXBDZV7iYkC7GK18AcvTZ0lOFGRE5NDWAtn4A28hdPQEToFcG1Jq4qERXAZ+DCaBXk+cIROAePQgh2whgk30SngAA7CVDgLq6Fr6P4M++Ec5PmPp6BhWAdzIA+m3BOO0C2AJ2GuMyfme0KQp6Ao5EmZf/fLDGFuI2oi+EEcUQm5JDywhpWc2MFGNIwn/WmcKhqF50UAAAAASUVORK5CYII=";
    } else {
      paperclip.src =
        "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAVFJREFUWIXtl8FKw0AQhj8EbQ/p0Ut8AVEPgYLUB+i5L6J9E0Wtr1HPgl48WU8K1Tfw4LktxUAhHvZfiMXUbdhVhB0Yms78M/NldwkJuFsD6AMjYCYfASfKBbUd4BkoKvxJmiDWKA1/AXrAtrynmIUIshJ9DXgEmt/km8oVwHEIANu8u0LTleYhBMBUzZMVmkSaSQgAe9DW1d3L/wzAqW6jJpQ3+5cA3vbW1Vz3Np6BCBABIkAE+DWAmX7TUixdynm15Wf6jf5fa3Cq60K5qrraNuHrK1kbmJcGWJ8rB9DC4yvaq5odlmK7wBB4lw8Vs9ZRzdgHwLmaXa5RM1DNmQ+AA2ABfACZgz4DctXs+QAAuMLc0dsPEJk0BXDhazjAFnCnxjlmiTuYg5kAR4rl0twCmz4BLMQAs7RVH6kLzJ17H162fczhGmO+mqa6PqXGnn8CxMN0PcC9DrQAAAAASUVORK5CYII=";
    }
    node.appendChild(paperclip);

    let serviceIcon = aDocument.createElement("img");
    serviceIcon.classList.add("cloudfile-service-icon");
    serviceIcon.style.gridArea = "1 / 3";
    serviceIcon.alt = "";
    serviceIcon.style.margin = "0 5px";
    serviceIcon.width = `${iconSize}`;
    serviceIcon.height = `${iconSize}`;
    node.appendChild(serviceIcon);

    if (aCloudFileUpload.serviceIcon) {
      if (!/^(chrome|moz-extension):\/\//i.test(aCloudFileUpload.serviceIcon)) {
        serviceIcon.src = aCloudFileUpload.serviceIcon;
      } else {
        try {
          // Let's use the goodness from MsgComposeCommands.js since we're
          // sitting right in a compose window.
          serviceIcon.src = window.loadBlockedImage(
            aCloudFileUpload.serviceIcon,
            true
          );
        } catch (e) {
          // Couldn't load the referenced image.
          console.error(e);
        }
      }
    }
    node.appendChild(aDocument.createElement("br"));

    node.appendChild(
      statsRow("size", gMessenger.formatFileSize(aCloudFileUpload.size))
    );

    if (aCloudFileUpload.downloadExpiryDate) {
      node.appendChild(
        statsRow(
          "expiry-date",
          new Date(
            aCloudFileUpload.downloadExpiryDate.timestamp
          ).toLocaleString(
            undefined,
            aCloudFileUpload.downloadExpiryDate.format || {
              day: "2-digit",
              month: "2-digit",
              year: "numeric",
              hour: "2-digit",
              minute: "2-digit",
              timeZoneName: "short",
            }
          )
        )
      );
    }

    if (aCloudFileUpload.downloadLimit) {
      node.appendChild(
        statsRow("download-limit", aCloudFileUpload.downloadLimit)
      );
    }

    if (composeHTML || aCloudFileUpload.serviceUrl) {
      node.appendChild(serviceRow());
    }

    let linkElementLocaleId = aCloudFileUpload.downloadPasswordProtected
      ? "password-protected-link"
      : "link";
    node.appendChild(
      statsRow(linkElementLocaleId, aCloudFileUpload.url, aCloudFileUpload.url)
    );

    // An extra line break is needed for the converted plain text version, if it
    // should have a gap between its <li> elements.
    if (composeHTML) {
      node.appendChild(aDocument.createElement("br"));
    }

    // Generate the plain text version from the HTML. The used method needs a <ul>
    // element wrapped around the <li> element to produce the correct content.
    if (!composeHTML) {
      let ul = aDocument.createElement("ul");
      ul.appendChild(node);
      node = aDocument.createElement("p");
      node.textContent = this._getTextRepresentation(ul.outerHTML);
    }

    node.className = "cloudAttachmentItem";
    node.dataset.contentLocation = aCloudFileUpload.url;
    node.dataset.serviceName = aCloudFileUpload.serviceName;
    node.dataset.serviceUrl = aCloudFileUpload.serviceUrl;
    return node;
  },

  /**
   * Event handler for when mail is sent.  For mail that is being sent
   * (and not saved!), find any cloudAttachmentList* nodes that we've created,
   * and strip their IDs out.  That way, if the receiving user replies by
   * sending some BigFiles, we don't run into ID conflicts.
   */
  send(aEvent) {
    let msgType = parseInt(aEvent.target.getAttribute("msgtype"));

    if (
      msgType == Ci.nsIMsgCompDeliverMode.Now ||
      msgType == Ci.nsIMsgCompDeliverMode.Later ||
      msgType == Ci.nsIMsgCompDeliverMode.Background
    ) {
      const kIDs = [
        "cloudAttachmentListRoot",
        "cloudAttachmentListHeader",
        "cloudAttachmentList",
        "cloudAttachmentListFooter",
      ];
      let mailDoc = document.getElementById("messageEditor").contentDocument;

      for (let id of kIDs) {
        let element = mailDoc.getElementById(id);
        if (element) {
          element.removeAttribute("id");
        }
      }
    }
  },
};

window.addEventListener(
  "compose-window-init",
  gCloudAttachmentLinkManager.init.bind(gCloudAttachmentLinkManager),
  true
);
window.addEventListener(
  "compose-send-message",
  gCloudAttachmentLinkManager.send.bind(gCloudAttachmentLinkManager),
  true
);
