"use strict";

exports.__esModule = true;
exports.diffNodes = diffNodes;
exports.filterHeadProps = filterHeadProps;
exports.headExportValidator = headExportValidator;
exports.isEqualNode = isEqualNode;
exports.warnForInvalidTags = warnForInvalidTags;

var _constants = require("./constants");

/**
 * Filter the props coming from a page down to just the ones that are relevant for head.
 * This e.g. filters out properties that are undefined during SSR.
 */
function filterHeadProps(input) {
  return {
    location: {
      pathname: input.location.pathname
    },
    params: input.params,
    data: input.data || {},
    pageContext: input.pageContext
  };
}
/**
 * Throw error if Head export is not a valid
 */


function headExportValidator(head) {
  if (typeof head !== `function`) throw new Error(`Expected "Head" export to be a function got "${typeof head}".`);
}
/**
 * Warn once for same messsage
 */


let warnOnce = _ => {};

if (process.env.NODE_ENV !== `production`) {
  const warnings = new Set();

  warnOnce = msg => {
    if (!warnings.has(msg)) {
      console.warn(msg);
    }

    warnings.add(msg);
  };
}
/**
 * Warn for invalid tags in head.
 * @param {string} tagName
 */


function warnForInvalidTags(tagName) {
  if (process.env.NODE_ENV !== `production`) {
    const warning = `<${tagName}> is not a valid head element. Please use one of the following: ${_constants.VALID_NODE_NAMES.join(`, `)}`;
    warnOnce(warning);
  }
}
/**
 * When a `nonce` is present on an element, browsers such as Chrome and Firefox strip it out of the
 * actual HTML attributes for security reasons *when the element is added to the document*. Thus,
 * given two equivalent elements that have nonces, `Element,isEqualNode()` will return false if one
 * of those elements gets added to the document. Although the `element.nonce` property will be the
 * same for both elements, the one that was added to the document will return an empty string for
 * its nonce HTML attribute value.
 *
 * This custom `isEqualNode()` function therefore removes the nonce value from the `newTag` before
 * comparing it to `oldTag`, restoring it afterwards.
 *
 * For more information, see:
 * https://bugs.chromium.org/p/chromium/issues/detail?id=1211471#c12
 */


function isEqualNode(oldTag, newTag) {
  if (oldTag instanceof HTMLElement && newTag instanceof HTMLElement) {
    const nonce = newTag.getAttribute(`nonce`); // Only strip the nonce if `oldTag` has had it stripped. An element's nonce attribute will not
    // be stripped if there is no content security policy response header that includes a nonce.

    if (nonce && !oldTag.getAttribute(`nonce`)) {
      const cloneTag = newTag.cloneNode(true);
      cloneTag.setAttribute(`nonce`, ``);
      cloneTag.nonce = nonce;
      return nonce === oldTag.nonce && oldTag.isEqualNode(cloneTag);
    }
  }

  return oldTag.isEqualNode(newTag);
}

function diffNodes({
  oldNodes,
  newNodes,
  onStale,
  onNew
}) {
  for (const existingHeadElement of oldNodes) {
    const indexInNewNodes = newNodes.findIndex(e => isEqualNode(e, existingHeadElement));

    if (indexInNewNodes === -1) {
      onStale(existingHeadElement);
    } else {
      // this node is re-created as-is, so we keep old node, and remove it from list of new nodes (as we handled it already here)
      newNodes.splice(indexInNewNodes, 1);
    }
  } // remaing new nodes didn't have matching old node, so need to be added


  for (const newNode of newNodes) {
    onNew(newNode);
  }
}