const createTraverse = () => {
    let stop = false;
    
    return function traverse(eleList, callback) {
        const childList = [];
        if (!stop && typeof callback === 'function') {
            for (let index = 0; !stop && index < eleList.length; index++) {
                let element = eleList[index];
                stop = callback(element) === true;
                childList.push(...element.$children);
            }
            
            if (childList.length) {
                traverse(childList, callback);
            }
        }
    };
};

const match = (node, selector) => {
    const vnode = node._vnode;
    const $vnode = node.$vnode;
    let attrs = vnode.data && vnode.data.attrs || {};
    let $attrs = $vnode.data && $vnode.data.attrs || {};
    let staticClass = vnode.data && vnode.data.staticClass || '';
    let $staticClass = $vnode.data && $vnode.data.staticClass || '';
    const id = attrs.id || '';
    const $id = $attrs.id || '';
    if (selector[0] === '#') {
        return selector.substr(1) === id || selector.substr(1) === $id;
    } else {
        staticClass = staticClass.trim().split(' ');
        $staticClass = $staticClass.trim().split(' ');
        selector = selector.substr(1).split('.');
        return selector.some(a => staticClass.includes(a) || $staticClass.includes(a));
    }
};

const selectorBuilder = (selector) => {
    selector = selector.replace(/>>>/g, '>');
    selector = selector.split('>').map(s => {
        return s.trim().split(' ').join(`').descendant('`);
    }).join(`').child('`);
    return new Function('Selector', 'node', 'all', `return new Selector(node, all).descendant('` + selector + `')`);
};

class Selector {
    constructor(node, all = false) {
        this.nodes = [node];
        this.all = all;
    }

    child(selector) {
        let matches = [];
        if (this.all) {
            this.nodes.forEach(node => {
                matches.push(...node.$children.filter(node => match(node, selector)));
            });
        } else {
            if (this.nodes.length > 0) {
                let node = this.nodes[0].$children.find(node => match(node, selector));
                matches = node ? [node] : [];
            }
        }
        this.nodes = matches;
        return this;
    }

    descendant(selector) {
        let matches = [];
        this.nodes.forEach(root => {
            createTraverse()(root.$children, (node) => {
                if (match(node, selector)) {
                    matches.push(node);
                    return !this.all;
                }
            });
        });
        this.nodes = matches;
        return this;
    }
}

Vue.prototype.selectComponent = function (selector) {
    const selectors = selector.split(',').map(s => s.trim());
    if (!selectors[0]) {
        return null;
    }
    const querySelector = selectorBuilder(selectors[0]);
    return querySelector(Selector, this, false, selector).nodes[0];
};

Vue.prototype.selectAllComponents = function (selector) {
    const selectors = selector.split(',').map(s => s.trim());
    let selected = [];
    selectors.forEach(selector => {
        const querySelector = selectorBuilder(selector);
        selected = selected.concat(querySelector(Selector, this, true, selector).nodes);
    });
    return selected;
};
