import "../polyfill/dom";
import {isElement} from "./utils";


/**
 * Returns whether the given element is - in fact - an HTMLElement and
 * it matches the optional selector
 *
 * @param {Node|HTMLElement} element
 * @param {?string} selector
 * @return {boolean}
 */
function elementMatches (element, selector)
{
    return isElement(element) && (null === selector || element.matches(selector));
}

/**
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @param {string} method
 * @param {boolean} onlyFirst
 * @return {HTMLElement[]|HTMLElement}
 */
function fetchSiblings (element, selector, method, onlyFirst)
{
    let sibling = element[method];
    const list = [];

    while (sibling)
    {
        if (elementMatches(sibling, selector))
        {
            if (onlyFirst)
            {
                return sibling;
            }

            list.push(sibling);
        }

        sibling = sibling[method];
    }

    return onlyFirst ? null : list;
}


/**
 * Finds all DOM elements matching the selector
 *
 * @param {string} selector
 * @param {Document|HTMLElement} context
 * @return {HTMLElement[]}
 */
export function find (selector, context = document)
{
    return Array.prototype.slice.call(context.querySelectorAll(selector));
}


/**
 * Finds a single DOM node matching the selector
 *
 * @param {string} selector
 * @param {Document|HTMLElement} context
 * @return {?HTMLElement}
 */
export function findOne (selector, context = document)
{
    return context.querySelector(selector);
}


/**
 * Filters a list of DOM elements that match the given selector
 *
 * @param {HTMLElement[]} list
 * @param {string} selector
 * @return {HTMLElement[]}
 */
export function filter (list, selector)
{
    return list.filter(
        (e) => e.matches(selector)
    );
}


/**
 * Filters a list of DOM elements that DO NOT match the given selector
 *
 * @param {HTMLElement[]} list
 * @param {string} selector
 * @return {HTMLElement[]}
 */
export function not (list, selector)
{
    return list.filter(
        (e) => !e.matches(selector)
    );
}


/**
 * Returns all children
 *
 * @param {HTMLElement} parent
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function children (parent, selector = null)
{
    const list = [];
    let child = parent.firstChild;

    while (child)
    {
        if (elementMatches(child, selector))
        {
            list.push(child);
        }

        child = child.nextSibling;
    }

    return list;
}


/**
 * Returns the nearest previous sibling matching
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {?HTMLElement}
 */
export function prev (element, selector = null)
{
    return fetchSiblings(element, selector, "previousSibling", true);
}


/**
 * Returns the nearest following sibling
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {?HTMLElement}
 */
export function next (element, selector = null)
{
    return fetchSiblings(element, selector, "nextSibling", true);
}


/**
 * Returns all previous siblings
 * (optionally matching the given selector)
 *
 * The nearest sibling is the first element in the list.
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function prevAll (element, selector = null)
{
    return fetchSiblings(element, selector, "previousSibling", false);
}


/**
 * Returns all following siblings
 * (optionally matching the given selector)
 *
 * The nearest sibling is the first element in the list.
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function nextAll (element, selector = null)
{
    return fetchSiblings(element, selector, "nextSibling", false);
}


/**
 * Returns all siblings
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function siblings (element, selector = null)
{
    let sibling = element.parentNode.firstChild;
    const list = [];

    while (sibling)
    {
        if (sibling !== element && elementMatches(sibling, selector))
        {
            list.push(sibling);
        }

        sibling = sibling.nextSibling;
    }

    return list;
}


/**
 * Returns the closest parent that matches the selector
 *
 * @param {HTMLElement} element
 * @param {string} selector
 * @return {?HTMLElement}
 */
export function closest (element, selector)
{
    let parent = element.parentNode;

    /** @type {HTMLElement} node */
    while (parent)
    {
        if (parent.matches(selector))
        {
            return parent;
        }

        parent = parent.parentNode;
    }

    return null;
}

@@ -1864,32 +1864,62 @@
 e given selector
+ or are%0A * not the given node.
 %0A *%0A * @param %7BH
@@ -1946,32 +1946,44 @@
 * @param %7Bstring
+%7CHTMLElement
 %7D selector%0A * @r
@@ -2039,32 +2039,80 @@
 st, selector)%0A%7B%0A
+    if (typeof selector === %22string%22)%0A    %7B%0A    
     return list.
@@ -2111,32 +2111,36 @@
 rn list.filter(%0A
+    
         (e) =%3E !
@@ -2151,32 +2151,104 @@
 tches(selector)%0A
+        );%0A    %7D%0A%0A    return list.filter(%0A        (e) =%3E e !== selector%0A
     );%0A%7D%0A%0A%0A/**%0A 

Add support for a node element in not()