export class RelationCaches {
  constructor(manager) {
    this.manager = manager;
    this.maxCacheSize = 10000;
    this.relationLookupCacheKeys = new Set();
    this.valueLookupCacheKeys = new Set();
    this.valueRelationsBySrcCache = new Map();
    this.valueRelationsByDstCache = new Map();
    this.valueRelationsByNameCache = new Map();
    this.relationLookupCache = this.manager.arbiter.cacheFactory(this.maxCacheSize, {
      onEvict: (key, value) => this.relationLookupCacheKeys.delete(key),
      sampleSize: 8,
      sketchEpsilon: 0.005,
      sketchDelta: 0.01
    });
    this.valueLookupCache = this.manager.arbiter.cacheFactory(this.maxCacheSize, {
      onEvict: (key, value) => this.valueLookupCacheKeys.delete(key),
      sampleSize: 8,
      sketchEpsilon: 0.005,
      sketchDelta: 0.01
    });
    this.lastCacheCleanup = Date.now();
    this.cacheCleanupInterval = 300000;
  }

  makeDirectCacheKey(srcId, relation, dstId) {
    return this.manager.arbiter.keyManager.createCompositeKey(srcId, relation, dstId);
  }

  makeSrcRelCacheKey(srcId, relation) {
    return this.manager.arbiter.keyManager.createSrcRelKey(srcId, relation);
  }

  makeDstRelCacheKey(dstId, relation) {
    return this.manager.arbiter.keyManager.createDstRelKey(dstId, relation);
  }

  makeValueCacheKey(srcId, relation, dstId) {
    return this.manager.arbiter.keyManager.createCompositeKey(srcId, relation, dstId);
  }

  makeDirectCacheKeyFromStrings(srcKey, relation, dstKey) {
    const srcId = this.manager._resolveNodeId(srcKey);
    const dstId = this.manager._resolveNodeId(dstKey);
    if (srcId === undefined || dstId === undefined) return null;
    return this.makeDirectCacheKey(srcId, relation, dstId);
  }

  makeSrcRelCacheKeyFromStrings(srcKey, relation) {
    const srcId = this.manager._resolveNodeId(srcKey);
    if (srcId === undefined) return null;
    return this.makeSrcRelCacheKey(srcId, relation);
  }

  makeDstRelCacheKeyFromStrings(dstKey, relation) {
    const dstId = this.manager._resolveNodeId(dstKey);
    if (dstId === undefined) return null;
    return this.makeDstRelCacheKey(dstId, relation);
  }

  makeValueCacheKeyFromStrings(srcKey, relation, dstKey) {
    const srcId = this.manager._resolveNodeId(srcKey);
    const dstId = this.manager._resolveNodeId(dstKey);
    if (srcId === undefined || dstId === undefined) return null;
    return this.makeValueCacheKey(srcId, relation, dstId);
  }

  makeValueCacheKeyBySrc(srcId, relationId) {
    return this.manager.arbiter.keyManager._rollingHash(srcId, relationId, 0x5352); // 'SR'
  }

  makeValueCacheKeyByDst(dstId, relationId) {
    return this.manager.arbiter.keyManager._rollingHash(dstId, relationId, 0x4454); // 'DT'
  }

  deleteFromCache(cache, key) {
    if (!cache) return;
    if (key === null || key === undefined) return;
    if (typeof cache.delete === 'function') {
      cache.delete(key);
      return;
    }
    if (typeof cache._deleteKey === 'function') {
      cache._deleteKey(key);
      return;
    }
    if (cache.cache && typeof cache.cache.delete === 'function') {
      cache.cache.delete(key);
    }
  }

  invalidateRelationCaches(srcId, relation, dstId) {
    const srcIdNum = typeof srcId === 'number' ? srcId : this.manager.arbiter.nodeIdByKey.get(srcId);
    const dstIdNum = typeof dstId === 'number' ? dstId : this.manager.arbiter.nodeIdByKey.get(dstId);
    if (srcIdNum === undefined || dstIdNum === undefined) return;
    const srcRelKey = this.makeSrcRelCacheKey(srcIdNum, relation);
    const dstRelKey = this.makeDstRelCacheKey(dstIdNum, relation);
    const directKey = this.makeDirectCacheKey(srcIdNum, relation, dstIdNum);
    const valuePattern = this.makeValueCacheKey(srcIdNum, relation, dstIdNum);
    this.deleteFromCache(this.relationLookupCache, srcRelKey);
    this.deleteFromCache(this.relationLookupCache, dstRelKey);
    this.deleteFromCache(this.relationLookupCache, directKey);
    this.relationLookupCacheKeys.delete(srcRelKey);
    this.relationLookupCacheKeys.delete(dstRelKey);
    this.relationLookupCacheKeys.delete(directKey);
    this.deleteFromCache(this.valueLookupCache, valuePattern);
    this.valueLookupCacheKeys.delete(valuePattern);
  }

  invalidateValueRelationCaches(srcId, dstId, relation) {
    const relationId = this.manager._getRelationId(relation);
    this.valueRelationsBySrcCache.delete(this.makeValueCacheKeyBySrc(srcId, relationId));
    this.valueRelationsByDstCache.delete(this.makeValueCacheKeyByDst(dstId, relationId));
    this.valueRelationsByNameCache.delete(relationId);
  }

  batchInvalidateCaches(cacheKeys) {
    if (!cacheKeys) return;
    const iterable = typeof cacheKeys.size === 'number' ? cacheKeys : 
                     Array.isArray(cacheKeys) ? cacheKeys :
                     null;
    if (!iterable || (typeof iterable.length === 'number' && iterable.length === 0)) return;
    if (typeof iterable.size === 'number' && iterable.size === 0) return;
    for (const cacheKey of iterable) {
      if (typeof cacheKey === 'object' && cacheKey !== null) {
        if (cacheKey.relationLookupKey !== undefined && cacheKey.relationLookupKey !== null) {
          this.deleteFromCache(this.relationLookupCache, cacheKey.relationLookupKey);
          this.relationLookupCacheKeys.delete(cacheKey.relationLookupKey);
        }
        if (cacheKey.valueLookupKey !== undefined && cacheKey.valueLookupKey !== null) {
          this.deleteFromCache(this.valueLookupCache, cacheKey.valueLookupKey);
          this.valueLookupCacheKeys.delete(cacheKey.valueLookupKey);
        }
        continue;
      }
      this.deleteFromCache(this.relationLookupCache, cacheKey);
      this.relationLookupCacheKeys.delete(cacheKey);
      this.deleteFromCache(this.valueLookupCache, cacheKey);
      this.valueLookupCacheKeys.delete(cacheKey);
    }
  }

  cleanupCaches() {
    const now = Date.now();
    if (now - this.lastCacheCleanup < this.cacheCleanupInterval) return;
    this.lastCacheCleanup = now;
  }

  clearAll() {
    this.relationLookupCache.clear();
    this.relationLookupCacheKeys.clear();
    this.valueLookupCache.clear();
    this.valueLookupCacheKeys.clear();
    this.valueRelationsBySrcCache.clear();
    this.valueRelationsByDstCache.clear();
    this.valueRelationsByNameCache.clear();
  }
}
