/**
@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;
  }
}