// ==========================================================================
// Element utils
// ==========================================================================

import { toggleListener } from './events';
import is from './is';

// Wrap an element
export function wrap(elements, wrapper) {
    // Convert `elements` to an array, if necessary.
    const targets = elements.length ? elements : [elements];

    // Loops backwards to prevent having to clone the wrapper on the
    // first element (see `child` below).
    Array.from(targets)
        .reverse()
        .forEach((element, index) => {
            const child = index > 0 ? wrapper.cloneNode(true) : wrapper;

            // Cache the current parent and sibling.
            const parent = element.parentNode;
            const sibling = element.nextSibling;

            // Wrap the element (is automatically removed from its current
            // parent).
            child.appendChild(element);

            // If the element had a sibling, insert the wrapper before
            // the sibling to maintain the HTML structure; otherwise, just
            // append it to the parent.
            if (sibling) {
                parent.insertBefore(child, sibling);
            } else {
                parent.appendChild(child);
            }
        });
}

// Set attributes
export function setAttributes(element, attributes) {
    if (!is.element(element) || is.empty(attributes)) {
        return;
    }

    // Assume null and undefined attributes should be left out,
    // Setting them would otherwise convert them to "null" and "undefined"
    Object.entries(attributes)
        .filter(([, value]) => !is.nullOrUndefined(value))
        .forEach(([key, value]) => element.setAttribute(key, value));
}

// Create a DocumentFragment
export function createElement(type, attributes, text) {
    // Create a new <element>
    const element = document.createElement(type);

    // Set all passed attributes
    if (is.object(attributes)) {
        setAttributes(element, attributes);
    }

    // Add text node
    if (is.string(text)) {
        element.innerText = text;
    }

    // Return built element
    return element;
}

// Inaert an element after another
export function insertAfter(element, target) {
    if (!is.element(element) || !is.element(target)) {
        return;
    }

    target.parentNode.insertBefore(element, target.nextSibling);
}

// Insert a DocumentFragment
export function insertElement(type, parent, attributes, text) {
    if (!is.element(parent)) {
        return;
    }

    parent.appendChild(createElement(type, attributes, text));
}

// Remove element(s)
export function removeElement(element) {
    if (is.nodeList(element) || is.array(element)) {
        Array.from(element).forEach(removeElement);
        return;
    }

    if (!is.element(element) || !is.element(element.parentNode)) {
        return;
    }

    element.parentNode.removeChild(element);
}

// Remove all child elements
export function emptyElement(element) {
    if (!is.element(element)) {
        return;
    }

    let { length } = element.childNodes;

    while (length > 0) {
        element.removeChild(element.lastChild);
        length -= 1;
    }
}

// Replace element
export function replaceElement(newChild, oldChild) {
    if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) {
        return null;
    }

    oldChild.parentNode.replaceChild(newChild, oldChild);

    return newChild;
}

// Get an attribute object from a string selector
export function getAttributesFromSelector(sel, existingAttributes) {
    // For example:
    // '.test' to { class: 'test' }
    // '#test' to { id: 'test' }
    // '[data-test="test"]' to { 'data-test': 'test' }

    if (!is.string(sel) || is.empty(sel)) {
        return {};
    }

    const attributes = {};
    const existing = existingAttributes;

    sel.split(',').forEach(s => {
        // Remove whitespace
        const selector = s.trim();
        const className = selector.replace('.', '');
        const stripped = selector.replace(/[[\]]/g, '');

        // Get the parts and value
        const parts = stripped.split('=');
        const key = parts[0];
        const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';

        // Get the first character
        const start = selector.charAt(0);

        switch (start) {
            case '.':
                // Add to existing classname
                if (is.object(existing) && is.string(existing.class)) {
                    existing.class += ` ${className}`;
                }

                attributes.class = className;
                break;

            case '#':
                // ID selector
                attributes.id = selector.replace('#', '');
                break;

            case '[':
                // Attribute selector
                attributes[key] = value;

                break;

            default:
                break;
        }
    });

    return attributes;
}

// Toggle hidden
export function toggleHidden(element, hidden) {
    if (!is.element(element)) {
        return;
    }

    let hide = hidden;

    if (!is.boolean(hide)) {
        hide = !element.hidden;
    }

    if (hide) {
        element.setAttribute('hidden', '');
    } else {
        element.removeAttribute('hidden');
    }
}

// Mirror Element.classList.toggle, with IE compatibility for "force" argument
export function toggleClass(element, className, force) {
    if (is.nodeList(element)) {
        return Array.from(element).map(e => toggleClass(e, className, force));
    }

    if (is.element(element)) {
        let method = 'toggle';
        if (typeof force !== 'undefined') {
            method = force ? 'add' : 'remove';
        }

        element.classList[method](className);
        return element.classList.contains(className);
    }

    return false;
}

// Has class name
export function hasClass(element, className) {
    return is.element(element) && element.classList.contains(className);
}

// Element matches selector
export function matches(element, selector) {
    const prototype = { Element };

    function match() {
        return Array.from(document.querySelectorAll(selector)).includes(this);
    }

    const matches =
        prototype.matches ||
        prototype.webkitMatchesSelector ||
        prototype.mozMatchesSelector ||
        prototype.msMatchesSelector ||
        match;

    return matches.call(element, selector);
}

// Find all elements
export function getElements(selector) {
    return this.elements.container.querySelectorAll(selector);
}

// Find a single element
export function getElement(selector) {
    return this.elements.container.querySelector(selector);
}

// Trap focus inside container
export function trapFocus(element = null, toggle = false) {
    if (!is.element(element)) {
        return;
    }

    const focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
    const first = focusable[0];
    const last = focusable[focusable.length - 1];

    const trap = event => {
        // Bail if not tab key or not fullscreen
        if (event.key !== 'Tab' || event.keyCode !== 9) {
            return;
        }

        // Get the current focused element
        const focused = document.activeElement;

        if (focused === last && !event.shiftKey) {
            // Move focus to first element that can be tabbed if Shift isn't used
            first.focus();
            event.preventDefault();
        } else if (focused === first && event.shiftKey) {
            // Move focus to last element that can be tabbed if Shift is used
            last.focus();
            event.preventDefault();
        }
    };

    toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
}

// Set focus and tab focus class
export function setFocus(element = null, tabFocus = false) {
    if (!is.element(element)) {
        return;
    }

    // Set regular focus
    element.focus({ preventScroll: true });

    // If we want to mimic keyboard focus via tab
    if (tabFocus) {
        toggleClass(element, this.config.classNames.tabFocus);
    }
}
