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

"use strict";

const FLASH_TARGETS_SELECTOR = "[class*=theme-fg-color], .force-color-on-flash";

/**
 * Apply a 'flashed' background and foreground color to elements. Intended
 * to be used with flashElementOff as a way of drawing attention to an element.
 *
 * @param  {Node} backgroundElt
 *         The element to set the highlighted background color on.
 * @param  {object} options
 * @param  {Node} options.foregroundElt
 *         The element to set the matching foreground color on. This will equal
 *         backgroundElt if not set.
 * @param  {string} options.backgroundClass
 *         The background highlight color class to set on the element.
 */
function flashElementOn(
  backgroundElt,
  { foregroundElt = backgroundElt, backgroundClass = "theme-bg-contrast" } = {}
) {
  if (!backgroundElt || !foregroundElt) {
    return;
  }

  // Make sure the animation class is not here
  backgroundElt.classList.remove("flash-out");

  // Change the background
  backgroundElt.classList.add(backgroundClass);

  foregroundElt.classList.add("theme-fg-contrast");
  foregroundElt
    .querySelectorAll(FLASH_TARGETS_SELECTOR)
    .forEach(span => span.classList.add("theme-fg-contrast"));
}

/**
 * Remove a 'flashed' background and foreground color to elements.
 * See flashElementOn.
 *
 * @param  {Node} backgroundElt
 *         The element to remove the highlighted background color on.
 * @param  {object} options
 * @param  {Node} options.foregroundElt
 *         The element to remove the matching foreground color on. This will equal
 *         backgroundElt if not set.
 * @param  {string} options.backgroundClass
 *         The background highlight color class to remove on the element.
 */
function flashElementOff(
  backgroundElt,
  { foregroundElt = backgroundElt, backgroundClass = "theme-bg-contrast" } = {}
) {
  if (!backgroundElt || !foregroundElt) {
    return;
  }

  // Add the animation class to smoothly remove the background
  backgroundElt.classList.add("flash-out");

  // Remove the background
  backgroundElt.classList.remove(backgroundClass);

  foregroundElt.classList.remove("theme-fg-contrast");
  // Make sure the foreground animation class is removed
  foregroundElt.classList.remove("flash-out");
  foregroundElt
    .querySelectorAll(FLASH_TARGETS_SELECTOR)
    .forEach(span => span.classList.remove("theme-fg-contrast"));
}

/**
 * Retrieve the available width between a provided element left edge and a container right
 * edge. This used can be used as a max-width for inplace-editor (autocomplete) widgets
 * replacing Editor elements of the the markup-view;
 */
function getAutocompleteMaxWidth(element, container) {
  const elementRect = element.getBoundingClientRect();
  const containerRect = container.getBoundingClientRect();
  return containerRect.right - elementRect.left - 2;
}

/**
 * Parse attribute names and values from a string.
 *
 * @param  {string} attr
 *         The input string for which names/values are to be parsed.
 * @param  {HTMLDocument} doc
 *         A document that can be used to test valid attributes.
 * @return {Array}
 *         An array of attribute names and their values.
 */
function parseAttributeValues(attr, doc) {
  attr = attr.trim();

  const parseAndGetNode = str => {
    return new DOMParser().parseFromString(str, "text/html").body.childNodes[0];
  };

  // Handle bad user inputs by appending a " or ' if it fails to parse without
  // them. Also note that a SVG tag is used to make sure the HTML parser
  // preserves mixed-case attributes
  const el =
    parseAndGetNode("<svg " + attr + "></svg>") ||
    parseAndGetNode("<svg " + attr + '"></svg>') ||
    parseAndGetNode("<svg " + attr + "'></svg>");

  // Create <div> in new document to work around CSP blocking inline styles.
  const htmlDoc = doc.implementation.createHTMLDocument();
  const div = htmlDoc.createElement("div");
  const attributes = [];
  for (const { name, value } of el.attributes) {
    // Try to set on an element in the document, throws exception on bad input.
    // Prevents InvalidCharacterError - "String contains an invalid character".
    try {
      div.setAttribute(name, value);
      attributes.push({ name, value });
    } catch (e) {
      // This may throw exceptions on bad input.
      // Prevents InvalidCharacterError - "String contains an invalid
      // character".
    }
  }

  return attributes;
}

module.exports = {
  flashElementOn,
  flashElementOff,
  getAutocompleteMaxWidth,
  parseAttributeValues,
};
