/* 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 ../../../mail/components/addrbook/content/abCommon.js */
/* import-globals-from ../../../mail/components/compose/content/addressingWidgetOverlay.js */

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

top.MAX_RECIPIENTS = 1;

var gListCard;
var gEditList;
var gOldListName = "";

var gAWContentHeight = 0;
var gAWRowHeight = 0;
var gNumberOfCols = 0;

var test_addresses_sequence = false;

if (
  Services.prefs.getPrefType("mail.debug.test_addresses_sequence") ==
  Ci.nsIPrefBranch.PREF_BOOL
) {
  test_addresses_sequence = Services.prefs.getBoolPref(
    "mail.debug.test_addresses_sequence"
  );
}

try {
  var gDragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
    Ci.nsIDragService
  );
} catch (e) {}

// Returns the load context for the current window
function getLoadContext() {
  return window.docShell.QueryInterface(Ci.nsILoadContext);
}

function mailingListExists(listname) {
  if (MailServices.ab.mailListNameExists(listname)) {
    let bundle = Services.strings.createBundle(
      "chrome://messenger/locale/addressbook/addressBook.properties"
    );
    Services.prompt.alert(
      window,
      bundle.GetStringFromName("mailListNameExistsTitle"),
      bundle.GetStringFromName("mailListNameExistsMessage")
    );
    return true;
  }
  return false;
}

/**
 * Get the new inputs from the create/edit mailing list dialog and use them to
 * update the mailing list that was passed in as an argument.
 *
 * @param {nsIAbDirectory} mailList - The mailing list object to update. When
 *   creating a new list it will be newly created and empty.
 * @param {boolean} isNewList - Whether we are populating a new list.
 * @returns {boolean} - Whether the operation succeeded or not.
 */
function updateMailList(mailList, isNewList) {
  let bundle = Services.strings.createBundle(
    "chrome://messenger/locale/addressbook/addressBook.properties"
  );
  let listname = document.getElementById("ListName").value.trim();

  if (listname.length == 0) {
    alert(bundle.GetStringFromName("emptyListName"));
    return false;
  }

  if (listname.match("  ")) {
    alert(bundle.GetStringFromName("badListNameSpaces"));
    return false;
  }

  for (let char of ',;"<>') {
    if (listname.includes(char)) {
      alert(bundle.GetStringFromName("badListNameCharacters"));
      return false;
    }
  }

  let canonicalNewListName = listname.toLowerCase();
  let canonicalOldListName = gOldListName.toLowerCase();
  if (isNewList || canonicalOldListName != canonicalNewListName) {
    if (mailingListExists(listname)) {
      // After showing the "Mailing List Already Exists" error alert,
      // focus ListName input field for user to choose a different name.
      document.getElementById("ListName").focus();
      return false;
    }
  }

  mailList.isMailList = true;
  mailList.dirName = listname;
  mailList.listNickName = document.getElementById("ListNickName").value;
  mailList.description = document.getElementById("ListDescription").value;

  return true;
}

/**
 * Updates the members of the mailing list.
 *
 * @param {nsIAbDirectory} mailList - The mailing list object to
 *   update. When creating a new list it will be newly created and empty.
 * @param {nsIAbDirectory} parentDirectory - The address book containing the
 *   mailing list.
 */
function updateMailListMembers(mailList, parentDirectory) {
  // Gather email address inputs into a single string (comma-separated).
  let addresses = Array.from(
    document.querySelectorAll(".textbox-addressingWidget"),
    element => element.value
  )
    .filter(value => value.trim())
    .join();

  // Convert the addresses string into address objects.
  let addressObjects =
    MailServices.headerParser.makeFromDisplayAddress(addresses);
  let existingCards = mailList.childCards;

  // Work out which addresses need to be added...
  let existingCardAddresses = existingCards.map(card => card.primaryEmail);
  let addressObjectsToAdd = addressObjects.filter(
    aObj => !existingCardAddresses.includes(aObj.email)
  );

  // ... and which need to be removed.
  let addressObjectAddresses = addressObjects.map(aObj => aObj.email);
  let cardsToRemove = existingCards.filter(
    card => !addressObjectAddresses.includes(card.primaryEmail)
  );

  for (let { email, name } of addressObjectsToAdd) {
    let card = parentDirectory.cardForEmailAddress(email);
    if (!card) {
      card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
        Ci.nsIAbCard
      );
      card.primaryEmail = email;
      card.displayName = name || email;
    }
    mailList.addCard(card);
  }

  if (cardsToRemove.length > 0) {
    mailList.deleteCards(cardsToRemove);
  }
}

function MailListOKButton(event) {
  var popup = document.getElementById("abPopup");
  if (popup) {
    var uri = popup.getAttribute("value");

    // FIX ME - hack to avoid crashing if no ab selected because of blank option bug from template
    // should be able to just remove this if we are not seeing blank lines in the ab popup
    if (!uri) {
      event.preventDefault();
      return; // don't close window
    }
    // -----

    // Add mailing list to database
    var mailList =
      Cc["@mozilla.org/addressbook/directoryproperty;1"].createInstance();
    mailList = mailList.QueryInterface(Ci.nsIAbDirectory);

    if (updateMailList(mailList, true)) {
      var parentDirectory = GetDirectoryFromURI(uri);
      mailList = parentDirectory.addMailList(mailList);
      updateMailListMembers(mailList, parentDirectory);
      window.arguments[0].newListUID = mailList.UID;
      window.arguments[0].newListURI = mailList.URI;
    } else {
      event.preventDefault();
    }
  }
}

function OnLoadNewMailList() {
  var selectedAB = null;

  if ("arguments" in window && window.arguments[0]) {
    var abURI = window.arguments[0].selectedAB;
    if (abURI && abURI != kAllDirectoryRoot + "?") {
      var directory = GetDirectoryFromURI(abURI);
      if (directory.isMailList) {
        var parentURI = GetParentDirectoryFromMailingListURI(abURI);
        if (parentURI) {
          selectedAB = parentURI;
        }
      } else if (directory.readOnly) {
        selectedAB = kPersonalAddressbookURI;
      } else {
        selectedAB = abURI;
      }
    }

    let cards = window.arguments[0].cards;
    if (cards && cards.length > 0) {
      let listbox = document.getElementById("addressingWidget");
      let newListBoxNode = listbox.cloneNode(false);
      let templateNode = listbox.querySelector("richlistitem");

      top.MAX_RECIPIENTS = 0;
      for (let card of cards) {
        let address = MailServices.headerParser
          .makeMailboxObject(card.displayName, card.primaryEmail)
          .toString();
        SetInputValue(address, newListBoxNode, templateNode);
      }
      listbox.parentNode.replaceChild(newListBoxNode, listbox);
    }
  }

  if (!selectedAB) {
    selectedAB = kPersonalAddressbookURI;
  }

  // set popup with address book names
  var abPopup = document.getElementById("abPopup");
  abPopup.value = selectedAB;

  AppendNewRowAndSetFocus();
  awFitDummyRows(1);

  if (AppConstants.MOZ_APP_NAME == "seamonkey") {
    /* global awDocumentKeyPress */
    document.addEventListener("keypress", awDocumentKeyPress, true);
  }

  // focus on first name
  var listName = document.getElementById("ListName");
  if (listName) {
    setTimeout(
      function (firstTextBox) {
        firstTextBox.focus();
      },
      0,
      listName
    );
  }

  let input = document.getElementById("addressCol1#1");
  input.popup.addEventListener("click", () => {
    awReturnHit(input);
  });

  document.addEventListener("dialogaccept", MailListOKButton);
}

function EditListOKButton(event) {
  // edit mailing list in database
  if (updateMailList(gEditList, false)) {
    let parentURI = GetParentDirectoryFromMailingListURI(gEditList.URI);
    let parentDirectory = GetDirectoryFromURI(parentURI);
    updateMailListMembers(gEditList, parentDirectory);
    if (gListCard) {
      // modify the list card (for the results pane) from the mailing list
      gListCard.displayName = gEditList.dirName;
      gListCard.lastName = gEditList.dirName;
      gListCard.setProperty("NickName", gEditList.listNickName);
      gListCard.setProperty("Notes", gEditList.description);
    }

    gEditList.editMailListToDatabase(gListCard);

    window.arguments[0].refresh = true;
    return; // close the window
  }
  event.preventDefault();
}

function OnLoadEditList() {
  gListCard = window.arguments[0].abCard;
  var listUri = window.arguments[0].listURI;

  gEditList = GetDirectoryFromURI(listUri);

  document.getElementById("ListName").value = gEditList.dirName;
  document.getElementById("ListNickName").value = gEditList.listNickName;
  document.getElementById("ListDescription").value = gEditList.description;
  gOldListName = gEditList.dirName;

  let bundle = Services.strings.createBundle(
    "chrome://messenger/locale/addressbook/addressBook.properties"
  );
  document.title = bundle.formatStringFromName("mailingListTitleEdit", [
    gOldListName,
  ]);

  let cards = gEditList.childCards;
  if (cards.length > 0) {
    let listbox = document.getElementById("addressingWidget");
    let newListBoxNode = listbox.cloneNode(false);
    let templateNode = listbox.querySelector("richlistitem");

    top.MAX_RECIPIENTS = 0;
    for (let card of cards) {
      let address = MailServices.headerParser
        .makeMailboxObject(card.displayName, card.primaryEmail)
        .toString();
      SetInputValue(address, newListBoxNode, templateNode);
    }
    listbox.parentNode.replaceChild(newListBoxNode, listbox);
  }

  // Is this directory read-only? If so, we now need to set all the fields to
  // read-only.
  if (gEditList.readOnly) {
    const kMailListFields = ["ListName", "ListNickName", "ListDescription"];

    for (let i = 0; i < kMailListFields.length; ++i) {
      document.getElementById(kMailListFields[i]).readOnly = true;
    }

    document.querySelector("dialog").buttons = "accept";

    // Getting a sane read-only implementation for the addressing widget would
    // basically need a separate dialog. Given I'm not sure about the future of
    // the mailing list dialog in its current state, let's just disable it
    // completely.
    document.getElementById("addressingWidget").disabled = true;
  } else {
    document.addEventListener("dialogaccept", EditListOKButton);
  }

  if (AppConstants.MOZ_APP_NAME == "seamonkey") {
    document.addEventListener("keypress", awDocumentKeyPress, true);
  }

  // workaround for bug 118337 - for mailing lists that have more rows than fits inside
  // the display, the value of the textbox inside the new row isn't inherited into the input -
  // the first row then appears to be duplicated at the end although it is actually empty.
  // see awAppendNewRow which copies first row and clears it
  setTimeout(AppendLastRow, 0);

  document.querySelectorAll(`input[is="autocomplete-input"]`).forEach(input => {
    input.popup.addEventListener("click", () => {
      awReturnHit(input);
    });
  });
}

function AppendLastRow() {
  AppendNewRowAndSetFocus();
  awFitDummyRows(1);

  // focus on first name
  let listName = document.getElementById("ListName");
  if (listName) {
    listName.focus();
  }
}

function AppendNewRowAndSetFocus() {
  let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
  if (lastInput && lastInput.value) {
    awAppendNewRow(true);
  } else {
    awSetFocusTo(lastInput);
  }
}

function SetInputValue(inputValue, parentNode, templateNode) {
  top.MAX_RECIPIENTS++;

  var newNode = templateNode.cloneNode(true);
  parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!

  var input = newNode.querySelector(`input[is="autocomplete-input"]`);
  let label = newNode.querySelector(`label.person-icon`);
  if (input) {
    input.value = inputValue;
    input.setAttribute("id", "addressCol1#" + top.MAX_RECIPIENTS);
    label.setAttribute("for", "addressCol1#" + top.MAX_RECIPIENTS);
    input.popup.addEventListener("click", () => {
      awReturnHit(input);
    });
  }
}

function awClickEmptySpace(target, setFocus) {
  if (target == null || target.localName != "hbox") {
    return;
  }

  let lastInput = awGetInputElement(top.MAX_RECIPIENTS);

  if (lastInput && lastInput.value) {
    awAppendNewRow(setFocus);
  } else if (setFocus) {
    awSetFocusTo(lastInput);
  }
}

function awReturnHit(inputElement) {
  let row = awGetRowByInputElement(inputElement);
  if (inputElement.value) {
    let nextInput = awGetInputElement(row + 1);
    if (!nextInput) {
      awAppendNewRow(true);
    } else {
      awSetFocusTo(nextInput);
    }
  }
}

function awDeleteRow(rowToDelete) {
  /* When we delete a row, we must reset the id of others row in order to not break the sequence */
  var maxRecipients = top.MAX_RECIPIENTS;
  awRemoveRow(rowToDelete);

  var numberOfCols = awGetNumberOfCols();
  for (var row = rowToDelete + 1; row <= maxRecipients; row++) {
    for (var col = 1; col <= numberOfCols; col++) {
      awGetElementByCol(row, col).setAttribute(
        "id",
        "addressCol" + col + "#" + (row - 1)
      );
    }
  }

  awTestRowSequence();
}

/**
 * Append a new row.
 *
 * @param {boolean} setFocus - Whether to set the focus on the new row.
 * @returns {Element?} The input element from the new row.
 */
function awAppendNewRow(setFocus) {
  let body = document.getElementById("addressingWidget");
  let listitem1 = awGetListItem(1);
  let input;
  let label;

  if (body && listitem1) {
    let nextDummy = awGetNextDummyRow();
    let newNode = listitem1.cloneNode(true);
    if (nextDummy) {
      body.replaceChild(newNode, nextDummy);
    } else {
      body.appendChild(newNode);
    }

    top.MAX_RECIPIENTS++;

    input = newNode.querySelector(`input[is="autocomplete-input"]`);
    label = newNode.querySelector(`label.person-icon`);
    if (input) {
      input.value = "";
      input.setAttribute("id", "addressCol1#" + top.MAX_RECIPIENTS);
      label.setAttribute("for", "addressCol1#" + top.MAX_RECIPIENTS);
      input.popup.addEventListener("click", () => {
        awReturnHit(input);
      });
    }
    // Focus the new input widget.
    if (setFocus && input) {
      awSetFocusTo(input);
    }
  }
  return input;
}

// functions for accessing the elements in the addressing widget

/**
 * Returns the recipient inputbox for a row.
 *
 * @param {integer} row - Index of the recipient row to return. Starts at 1.
 * @returns {Element} This returns the input element.
 */
function awGetInputElement(row) {
  return document.getElementById("addressCol1#" + row);
}

function awGetElementByCol(row, col) {
  var colID = "addressCol" + col + "#" + row;
  return document.getElementById(colID);
}

function awGetListItem(row) {
  var listbox = document.getElementById("addressingWidget");
  if (listbox && row > 0) {
    return listbox.getItemAtIndex(row - 1);
  }

  return null;
}

/**
 * @param {Element} inputElement - The recipient input element.
 * @returns {integer} The row index (starting from 1) where the input element
 *   is found. 0 if the element is not found.
 */
function awGetRowByInputElement(inputElement) {
  if (!inputElement) {
    return 0;
  }

  var listitem = inputElement.parentNode.parentNode;
  return (
    document.getElementById("addressingWidget").getIndexOfItem(listitem) + 1
  );
}

function DragOverAddressListTree(event) {
  var dragSession = gDragService.getCurrentSession();

  // XXX add support for other flavors here
  if (dragSession.isDataFlavorSupported("text/x-moz-address")) {
    dragSession.canDrop = true;
  }
}

function DropOnAddressListTree(event) {
  let dragSession = gDragService.getCurrentSession();
  let trans;

  try {
    trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
      Ci.nsITransferable
    );
    trans.init(getLoadContext());
    trans.addDataFlavor("text/x-moz-address");
  } catch (ex) {
    return;
  }

  for (let i = 0; i < dragSession.numDropItems; ++i) {
    dragSession.getData(trans, i);
    let dataObj = {};
    let bestFlavor = {};
    trans.getAnyTransferData(bestFlavor, dataObj);
    if (dataObj) {
      dataObj = dataObj.value.QueryInterface(Ci.nsISupportsString);
    }
    if (!dataObj) {
      continue;
    }

    // pull the URL out of the data object
    let address = dataObj.data.substring(0, dataObj.length);
    if (!address) {
      continue;
    }

    DropListAddress(event.target, address);
  }
}

function DropListAddress(target, address) {
  // Set focus on a new available, visible row.
  awClickEmptySpace(target, true);
  if (top.MAX_RECIPIENTS == 0) {
    top.MAX_RECIPIENTS = 1;
  }

  // Break apart the MIME-ready header address into individual addressees to
  // add to the dialog.
  let addresses = MailServices.headerParser.parseEncodedHeader(address);
  for (let addr of addresses) {
    let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
    lastInput.value = addr.toString();
    awAppendNewRow(true);
  }
}

/**
 * Handles keypress events for the email address inputs (that auto-fill)
 * in the Address Book Mailing List dialogs. When a comma-separated list of
 * addresses is entered on one row, split them into one address per row. Only
 * add a new blank row on "Enter" key. On "Tab" key focus moves to the "Cancel"
 * button.
 *
 * @param {KeyboardEvent} event - The DOM keypress event.
 * @param {Element} element - The element that triggered the keypress event.
 */
function awAbRecipientKeyPress(event, element) {
  if (event.key != "Enter" && event.key != "Tab") {
    return;
  }

  if (!element.value) {
    if (event.key == "Enter") {
      awReturnHit(element);
    }
  } else {
    let inputElement = element;
    let originalRow = awGetRowByInputElement(element);
    let row;
    let addresses = MailServices.headerParser.makeFromDisplayAddress(
      element.value
    );

    if (addresses.length > 1) {
      // Collect any existing addresses from the following rows so we don't
      // simply overwrite them.
      row = originalRow + 1;
      inputElement = awGetInputElement(row);

      while (inputElement) {
        if (inputElement.value) {
          addresses.push(inputElement.value);
          inputElement.value = "";
        }
        row += 1;
        inputElement = awGetInputElement(row);
      }
    }

    // Insert the addresses, adding new rows if needed.
    row = originalRow;
    let needNewRows = false;

    for (let address of addresses) {
      if (needNewRows) {
        inputElement = awAppendNewRow(false);
      } else {
        inputElement = awGetInputElement(row);
        if (!inputElement) {
          needNewRows = true;
          inputElement = awAppendNewRow(false);
        }
      }

      if (inputElement) {
        inputElement.value = address;
      }
      row += 1;
    }

    if (event.key == "Enter") {
      // Prevent the dialog from closing. "Enter" inserted a new row instead.
      event.preventDefault();
      awReturnHit(inputElement);
    } else if (event.key == "Tab") {
      // Focus the last row to let "Tab" move focus to the "Cancel" button.
      let lastRow = row - 1;
      awGetInputElement(lastRow).focus();
    }
  }
}

/**
 * Handle keydown event on a recipient input.
 * Enables recipient row deletion with DEL or BACKSPACE and
 * recipient list navigation with cursor up/down.
 *
 * Note that the keydown event fires for ALL keys, so this may affect
 * autocomplete as user enters a recipient text.
 *
 * @param {KeyboardEvent} event - The keydown event fired on a recipient input.
 * @param {HTMLInputElement} inputElement - The recipient input element
 *   on which the event fired (textbox-addressingWidget).
 */
function awRecipientKeyDown(event, inputElement) {
  switch (event.key) {
    // Enable deletion of empty recipient rows.
    case "Delete":
    case "Backspace":
      if (inputElement.value.length == 1 && event.repeat) {
        // User is holding down Delete or Backspace to delete recipient text
        // inline and is now deleting the last character: Set flag to
        // temporarily block row deletion.
        top.awRecipientInlineDelete = true;
      }
      if (!inputElement.value && !event.altKey) {
        // When user presses DEL or BACKSPACE on an empty row, and it's not an
        // ongoing inline deletion, and not ALT+BACKSPACE for input undo,
        // we delete the row.
        if (top.awRecipientInlineDelete && !event.repeat) {
          // User has released and re-pressed Delete or Backspace key
          // after holding them down to delete recipient text inline:
          // unblock row deletion.
          top.awRecipientInlineDelete = false;
        }
        if (!top.awRecipientInlineDelete) {
          let deleteForward = event.key == "Delete";
          awDeleteHit(inputElement, deleteForward);
        }
      }
      break;

    // Enable browsing the list of recipients up and down with cursor keys.
    case "ArrowDown":
    case "ArrowUp":
      // Only browse recipients if the autocomplete popup is not open.
      if (!inputElement.popupOpen) {
        let row = awGetRowByInputElement(inputElement);
        let down = event.key == "ArrowDown";
        let noEdgeRow = down ? row < top.MAX_RECIPIENTS : row > 1;
        if (noEdgeRow) {
          let targetRow = down ? row + 1 : row - 1;
          awSetFocusTo(awGetInputElement(targetRow));
        }
      }
      break;
  }
}

/**
 * Delete recipient row (addressingWidgetItem) from UI.
 *
 * @param {HTMLInputElement} inputElement - The recipient input element.
 *   textbox-addressingWidget) whose parent row (addressingWidgetItem) will be
 *   deleted.
 * @param {boolean} deleteForward - true: focus next row after deleting the row
 *   false: focus previous row after deleting the row
 */
function awDeleteHit(inputElement, deleteForward = false) {
  let row = awGetRowByInputElement(inputElement);

  // Don't delete the row if it's the last one remaining; just reset it.
  if (top.MAX_RECIPIENTS <= 1) {
    inputElement.value = "";
    return;
  }

  // Set the focus to the input field of the next/previous row according to
  // the direction of deleting if possible.
  // Note: awSetFocusTo() is asynchronous, i.e. we'll focus after row removal.
  if (
    (!deleteForward && row > 1) ||
    (deleteForward && row == top.MAX_RECIPIENTS)
  ) {
    // We're deleting backwards, but not the first row,
    // or forwards on the last row: Focus previous row.
    awSetFocusTo(awGetInputElement(row - 1));
  } else {
    // We're deleting forwards, but not the last row,
    // or backwards on the first row: Focus next row.
    awSetFocusTo(awGetInputElement(row + 1));
  }

  // Delete the row.
  awDeleteRow(row);
}

function awTestRowSequence() {
  /*
    This function is for debug and testing purpose only, normal user should not run it!

    Every time we insert or delete a row, we must be sure we didn't break the ID sequence of
    the addressing widget rows. This function will run a quick test to see if the sequence still ok

    You need to define the pref mail.debug.test_addresses_sequence to true in order to activate it
  */

  if (!test_addresses_sequence) {
    return true;
  }

  // Debug code to verify the sequence is still good.

  let listbox = document.getElementById("addressingWidget");
  let listitems = listbox.itemChildren;
  if (listitems.length >= top.MAX_RECIPIENTS) {
    for (let i = 1; i <= listitems.length; i++) {
      let item = listitems[i - 1];
      let inputID = item
        .querySelector(`input[is="autocomplete-input"]`)
        .id.split("#")[1];
      let menulist = item.querySelector("menulist");
      // In some places like the mailing list dialog there is no menulist,
      // and so no popupID that needs to be kept in sequence.
      let popupID = menulist && menulist.id.split("#")[1];
      if (inputID != i || (popupID && popupID != i)) {
        dump(
          `#ERROR: sequence broken at row ${i}, ` +
            `inputID=${inputID}, popupID=${popupID}\n`
        );
        return false;
      }
      dump("---SEQUENCE OK---\n");
      return true;
    }
  } else {
    dump(
      `#ERROR: listitems.length(${listitems.length}) < ` +
        `top.MAX_RECIPIENTS(${top.MAX_RECIPIENTS})\n`
    );
  }

  return false;
}

function awRemoveRow(row) {
  awGetListItem(row).remove();
  awFitDummyRows();

  top.MAX_RECIPIENTS--;
}

function awGetNumberOfCols() {
  if (gNumberOfCols == 0) {
    var listbox = document.getElementById("addressingWidget");
    var listCols = listbox.getElementsByTagName("treecol");
    gNumberOfCols = listCols.length;
    if (!gNumberOfCols) {
      // If no cols defined, that means we have only one!
      gNumberOfCols = 1;
    }
  }

  return gNumberOfCols;
}

function awCreateDummyItem(aParent) {
  var listbox = document.getElementById("addressingWidget");
  var item = listbox.getItemAtIndex(0);

  var titem = document.createXULElement("richlistitem");
  titem.setAttribute("_isDummyRow", "true");
  titem.setAttribute("class", "dummy-row");
  titem.style.height = item.getBoundingClientRect().height + "px";

  for (let i = 0; i < awGetNumberOfCols(); i++) {
    let cell = awCreateDummyCell(titem);
    if (item.children[i].hasAttribute("style")) {
      cell.setAttribute("style", item.children[i].getAttribute("style"));
    }
    if (item.children[i].hasAttribute("flex")) {
      cell.setAttribute("flex", item.children[i].getAttribute("flex"));
    }
  }

  if (aParent) {
    aParent.appendChild(titem);
  }

  return titem;
}

function awFitDummyRows() {
  awCalcContentHeight();
  awCreateOrRemoveDummyRows();
}

function awCreateOrRemoveDummyRows() {
  let listbox = document.getElementById("addressingWidget");
  let listboxHeight = listbox.getBoundingClientRect().height;

  // remove rows to remove scrollbar
  let kids = listbox.querySelectorAll("[_isDummyRow]");
  for (
    let i = kids.length - 1;
    gAWContentHeight > listboxHeight && i >= 0;
    --i
  ) {
    gAWContentHeight -= gAWRowHeight;
    kids[i].remove();
  }

  // add rows to fill space
  if (gAWRowHeight) {
    while (gAWContentHeight + gAWRowHeight < listboxHeight) {
      awCreateDummyItem(listbox);
      gAWContentHeight += gAWRowHeight;
    }
  }
}

function awCalcContentHeight() {
  var listbox = document.getElementById("addressingWidget");
  var items = listbox.itemChildren;

  gAWContentHeight = 0;
  if (items.length > 0) {
    // all rows are forced to a uniform height in xul listboxes, so
    // find the first listitem with a boxObject and use it as precedent
    var i = 0;
    do {
      gAWRowHeight = items[i].getBoundingClientRect().height;
      ++i;
    } while (i < items.length && !gAWRowHeight);
    gAWContentHeight = gAWRowHeight * items.length;
  }
}

/* ::::::::::: addressing widget dummy rows ::::::::::::::::: */

function awCreateDummyCell(aParent) {
  var cell = document.createXULElement("hbox");
  cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
  if (aParent) {
    aParent.appendChild(cell);
  }

  return cell;
}

function awGetNextDummyRow() {
  // gets the next row from the top down
  return document.querySelector("#addressingWidget > [_isDummyRow]");
}

/**
 * Set focus to the specified element, typically a recipient input element.
 * We do this asynchronously to allow other processes like adding or removing rows
 * to complete before shifting focus.
 *
 * @param {Element} element - The element to receive focus asynchronously.
 */
function awSetFocusTo(element) {
  // Remember the (input) element to focus for asynchronous focusing, so that we
  // play safe if this gets called again and the original element gets removed
  // before we can focus it.
  top.awInputToFocus = element;
  setTimeout(_awSetFocusTo, 0);
}

function _awSetFocusTo() {
  top.awInputToFocus.focus();
}

// returns null if abURI is not a mailing list URI
function GetParentDirectoryFromMailingListURI(abURI) {
  var abURIArr = abURI.split("/");
  /*
   Turn "jsaddrbook://abook.sqlite/MailList6"
   into ["jsaddrbook:","","abook.sqlite","MailList6"],
   then into "jsaddrbook://abook.sqlite".

   Turn "moz-aboutlookdirectory:///<top dir ID>/<ML dir ID>"
   into ["moz-aboutlookdirectory:","","","<top dir ID>","<ML dir ID>"],
   and then into: "moz-aboutlookdirectory:///<top dir ID>".
  */
  if (
    abURIArr.length == 4 &&
    ["jsaddrbook:", "moz-abmdbdirectory:"].includes(abURIArr[0]) &&
    abURIArr[3] != ""
  ) {
    return abURIArr[0] + "//" + abURIArr[2];
  } else if (
    abURIArr.length == 5 &&
    abURIArr[0] == "moz-aboutlookdirectory:" &&
    abURIArr[4] != ""
  ) {
    return abURIArr[0] + "///" + abURIArr[3];
  }

  return null;
}
