/**
@license
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
import './boot.js';
import { wrap } from './wrap.js';
const ShadyDOM = window.ShadyDOM;
const ShadyCSS = window.ShadyCSS;
/**
 * Return true if node scope is correct.
 *
 * @param {!Element} node Node to check scope
 * @param {!Node} scope Scope reference
 * @return {boolean} True if node is in scope
 */

function sameScope(node, scope) {
  return wrap(node).getRootNode() === scope;
}
/**
 * Ensure that elements in a ShadowDOM container are scoped correctly.
 * This function is only needed when ShadyDOM is used and unpatched DOM APIs are used in third party code.
 * This can happen in noPatch mode or when specialized APIs like ranges or tables are used to mutate DOM.
 *
 * @param  {!Element} container Container element to scope
 * @param  {boolean=} shouldObserve if true, start a mutation observer for added nodes to the container
 * @return {?MutationObserver} Returns a new MutationObserver on `container` if `shouldObserve` is true.
 */


export function scopeSubtree(container, shouldObserve = false) {
  // If using native ShadowDOM, abort
  if (!ShadyDOM || !ShadyCSS) {
    return null;
  } // ShadyCSS handles DOM mutations when ShadyDOM does not handle scoping itself


  if (!ShadyDOM['handlesDynamicScoping']) {
    return null;
  }

  const ScopingShim = ShadyCSS['ScopingShim']; // if ScopingShim is not available, abort

  if (!ScopingShim) {
    return null;
  } // capture correct scope for container


  const containerScope = ScopingShim['scopeForNode'](container);
  const root = wrap(container).getRootNode();

  const scopify = node => {
    if (!sameScope(node, root)) {
      return;
    } // NOTE: native qSA does not honor scoped DOM, but it is faster, and the same behavior as Polymer v1


    const elements = Array.from(ShadyDOM['nativeMethods']['querySelectorAll'].call(node, '*'));
    elements.push(node);

    for (let i = 0; i < elements.length; i++) {
      const el = elements[i];

      if (!sameScope(el, root)) {
        continue;
      }

      const currentScope = ScopingShim['currentScopeForNode'](el);

      if (currentScope !== containerScope) {
        if (currentScope !== '') {
          ScopingShim['unscopeNode'](el, currentScope);
        }

        ScopingShim['scopeNode'](el, containerScope);
      }
    }
  }; // scope everything in container


  scopify(container);

  if (shouldObserve) {
    const mo = new MutationObserver(mxns => {
      for (let i = 0; i < mxns.length; i++) {
        const mxn = mxns[i];

        for (let j = 0; j < mxn.addedNodes.length; j++) {
          const addedNode = mxn.addedNodes[j];

          if (addedNode.nodeType === Node.ELEMENT_NODE) {
            scopify(addedNode);
          }
        }
      }
    });
    mo.observe(container, {
      childList: true,
      subtree: true
    });
    return mo;
  } else {
    return null;
  }
}