<script>
export const patternTypes = [String, RegExp, Array];
export default {
  name: 'AppKeepAlive',
  props: {
    // 根据组件 name 进行匹配。如果 name 选项不可用，则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number],
    keyList: [Array],
  },
  data() {
    return {
      // eslint-disable-next-line vue/no-reserved-keys
      _toString: Object.prototype.toString,
    };
  },
  watch: {
    include(val) {
      const _this = this;
      _this.pruneCache(function (name) {
        return _this.matches(val, name);
      });
    },
    exclude(val) {
      const _this = this;
      _this.pruneCache(function (name) {
        return !_this.matches(val, name);
      });
    },
    keyList(val) {
      const _this = this;
      // 配置了keyList但是下方插件key
      _this.pruneCache2(function (name) {
        return !_this.matches(val, name);
      });
    },
  },
  created() {
    // 保存缓存的组件
    this.cache = Object.create(null);
    // 保存缓存的组件的key
    this.keys = [];
  },
  destroyed() {
    const _this = this;
    // eslint-disable-next-line no-restricted-syntax, guard-for-in
    for (const key in _this.cache) {
      _this.pruneCacheEntry(_this.cache, key, _this.keys);
    }
  },
  methods: {
    pruneCacheEntry(cache, key, keys, _current) {
      const cached = cache[key];
      if (cached) {
        cached.componentInstance.$destroy();
      }
      // eslint-disable-next-line no-param-reassign
      cache[key] = null;
      this.remove(keys, key);
    },
    pruneCache(filter) {
      const _this = this;
      const cache = _this.cache;
      const keys = _this.keys;
      const _vnode = _this._vnode;
      // eslint-disable-next-line no-restricted-syntax, guard-for-in
      for (const key in cache) {
        const cachedNode = cache[key];
        if (cachedNode) {
          const name = _this.getComponentName(cachedNode.componentOptions);
          if (name && !filter(name)) {
            _this.pruneCacheEntry(cache, key, keys, _vnode);
          }
        }
      }
    },
    pruneCache2(filter) {
      const _this = this;
      const cache = _this.cache;
      const keys = _this.keys;
      const _vnode = _this._vnode;
      // eslint-disable-next-line no-restricted-syntax, guard-for-in
      for (const key in cache) {
        const cachedNode = cache[key];
        if (cachedNode) {
          const name = cachedNode.data.curPath;
          if (name && filter(name)) {
            _this.pruneCacheEntry(cache, key, keys, _vnode);
          }
        }
      }
    },
    matches(pattern, name) {
      if (Array.isArray(pattern)) {
        return pattern.indexOf(name) > -1;
      }
      if (typeof pattern === 'string') {
        return pattern.split(',').indexOf(name) > -1;
      }
      if (this.isRegExp(pattern)) {
        return pattern.test(name);
      }
      /* istanbul ignore next */
      return false;
    },
    getComponentName(opts) {
      return opts && (opts.Ctor.options.name || opts.tag);
    },
    getFirstComponentChild(children) {
      const _this = this;
      if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
          const c = children[i];
          if (
            _this.isDef(c) &&
            (_this.isDef(c.componentOptions) || _this.isAsyncPlaceholder(c))
          ) {
            return c;
          }
        }
      }
    },
    isAsyncPlaceholder(node) {
      return node.isComment && node.asyncFactory;
    },
    isDef(v) {
      return v !== undefined && v !== null;
    },
    isRegExp(v) {
      return this._toString.call(v) === '[object RegExp]';
    },
    remove(arr, item) {
      if (arr.length) {
        const index = arr.indexOf(item);
        if (index > -1) {
          return arr.splice(index, 1);
        }
      }
    },
  },
  render: function render() {
    // this.$slots.default 包含了所有没有被包含在具名插槽中的节点
    // 这里取得第一个子组件
    const _this = this;
    const slot = _this.$slots.default;
    const vnode = _this.getFirstComponentChild(slot);
    const componentOptions = vnode && vnode.componentOptions;
    if (componentOptions) {
      // check pattern
      const name = _this.getComponentName(componentOptions);
      const ref = _this;
      const include = ref.include;
      const exclude = ref.exclude;
      const keyList = ref.keyList;
      // 获取第一个子组件上面的key
      const slotKey = vnode.key;
      // 如果 componentName 没有作为keep-alive被包含进来，直接返回
      if (
        // 包括并且不匹配的
        (include && (!name || !_this.matches(include, name))) ||
        // 排除并且匹配的
        (exclude && name && _this.matches(exclude, name)) ||
        // keyList中存在并且不匹配的
        (keyList && !slotKey && !_this.matches(keyList, slotKey))
      ) {
        return vnode;
      }

      const ref$1 = _this;
      const cache = ref$1.cache;
      const keys = ref$1.keys;
      const key =
        vnode.key == null
          ? // 相同的构造器（constructor）可能会注册为不同的本地组件，所以仅有一个 cid 是不够的
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : '')
          : vnode.key;
      if (cache[key]) {
        // 如果已经缓存了，需要保持当前的key 是最新的
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        _this.remove(keys, key);
        keys.push(key);
      } else {
        // 否则，进行缓存并更新keys数组。
        cache[key] = vnode;
        keys.push(key);
        // 缓存组件超出最大值，将队首的组件销毁（先进先出）
        if (_this.max && keys.length > parseInt(_this.max, 10)) {
          _this.pruneCacheEntry(cache, keys[0], keys, _this._vnode);
        }
      }

      vnode.data.keepAlive = true;
      vnode.data.curPath = slotKey;
    }
    return vnode || (slot && slot[0]);
  },
};
</script>
