const hasRecentlyConnected = Symbol();
const effects = Symbol();
const cleanups = Symbol();
const cleanupsDisconnected = Symbol();
const cleanup = Symbol();

const UseEffectMixin = (base) => {
  return class extends base {
    [effects] = [];
    [cleanups] = [];
    [cleanupsDisconnected] = [];
    connectedCallback() {
      this[hasRecentlyConnected] = true;
      super.connectedCallback(); 
    }
    disconnectedCallback() {
      super.disconnectedCallback();
      this[cleanups] = this[cleanup](this[cleanups]);
      this[cleanupsDisconnected] = this[cleanup](this[cleanupsDisconnected]);
    }
    updated(changedProperties) {
      super.updated(changedProperties);
      this[cleanups] = this[cleanup](this[cleanups]);
      this[effects].map(({effect, tests}) => {
        if (tests && ((!tests.length && !this[hasRecentlyConnected]) || !tests.every(test => changedProperties.has(test)))) return;
        const cleanup = effect(this);
        if (cleanup && (!tests || tests.length)) this[cleanups].push(cleanup);
        else if (cleanup) this[cleanupsDisconnected].push(cleanup);
      });
      this[hasRecentlyConnected] = false;
    }
    [cleanup](cleanups) {
      cleanups.map(cleanup => cleanup());
      return [];
    }
    useEffect(effect, tests) {
      this[effects].push({effect, tests}); 
    }
  }
}