const _toString = Object.prototype.toString;
function isRegExp(v) {
    return _toString.call(v) === '[object RegExp]';
}
export function remove(arr, item) {
    if (arr.length) {
        const index = arr.indexOf(item);
        if (index > -1) {
            return arr.splice(index, 1);
        }
    }
}

function isDef(v) {
    return v !== undefined && v !== null;
}
function isAsyncPlaceholder(node) {
    return node.isComment && node.asyncFactory;
}
function getFirstComponentChild(children) {
    if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
            const c = children[i];
            if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
                return c;
            }
        }
    }
}
function matches(pattern, name) {
    if (Array.isArray(pattern)) {
        return pattern.indexOf(name) > -1;
    } else if (typeof pattern === 'string') {
        return pattern.split(',').indexOf(name) > -1;
    } else if (isRegExp(pattern)) {
        return pattern.test(name);
    }
    return false;
}

function pruneCache(keepAliveInstance, filter) {
    const { cache, keys, _vnode } = keepAliveInstance;
    for (const key in cache) {
        const cachedNode = cache[key];
        if (cachedNode) {
            const name = key;
            if (name && !filter(name)) {
                pruneCacheEntry(cache, key, keys, _vnode);
            }
        }
    }
}

function pruneCacheEntry(
    cache,
    key,
    keys,
    current
) {
    const cached = cache[key];
    if (cached && (!current || cached.tag !== current.tag)) {
        cached.componentInstance.$destroy();
    }
    cache[key] = null;
    remove(keys, key);
}

const patternTypes = [String, RegExp, Array];

export default {
    name: 'my-keep-alive',
    // abstract: true,
    props: {
        include: patternTypes,
        exclude: patternTypes,
        max: [String, Number]
    },

    created() {
        this.cache = Object.create(null);
        this.keys = [];
    },

    destroyed() {
        for (const key in this.cache) {
            pruneCacheEntry(this.cache, key, this.keys);
        }
    },

    mounted() {
        this.$watch('include', val => {
            pruneCache(this, name => matches(val, name));
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => !matches(val, name));
        })
    },

    render() {
        const slot = this.$slots.default;
        const vnode = getFirstComponentChild(slot);
        const componentOptions = vnode && vnode.componentOptions;
        if (componentOptions) {
            const name = vnode.key;
            if (!componentOptions.Ctor.options.name) {
                vnode.componentOptions.Ctor.options.name;
            }
            const { include, exclude } = this;
            if (
                (include && (!name || !matches(include, name))) ||
                (exclude && name && matches(exclude, name))
            ) {
                return vnode;
            }

            const { cache, keys } = this;
            const key = name;
            if (cache[key]) {
                vnode.componentInstance = cache[key].componentInstance;
                remove(keys, key);
                keys.push(key);
            } else {
                cache[key] = vnode;
                keys.push(key);
                if (this.max && keys.length > parseInt(this.max)) {
                    pruneCacheEntry(cache, keys[0], keys, this._vnode);
                }
            }
            vnode.data.keepAlive = true;
        }
        return vnode || (slot && slot[0]);
    }
}