package linguistics import ( "context" "fmt" "sync" "github.com/hashicorp/golang-lru/v2" "tercul/cache" "tercul/logger" "tercul/config" "time" ) // AnalysisCache defines the interface for caching analysis results type AnalysisCache interface { // Get retrieves cached analysis result Get(ctx context.Context, key string) (*AnalysisResult, error) // Set stores analysis result in cache Set(ctx context.Context, key string, result *AnalysisResult) error // IsEnabled returns whether caching is enabled IsEnabled() bool } // MemoryAnalysisCache implements in-memory caching for analysis results type MemoryAnalysisCache struct { cache *lru.Cache[string, *AnalysisResult] mutex sync.RWMutex enabled bool } // NewMemoryAnalysisCache creates a new MemoryAnalysisCache func NewMemoryAnalysisCache(enabled bool) *MemoryAnalysisCache { // capacity from config cap := config.Cfg.NLPMemoryCacheCap if cap <= 0 { cap = 1024 } l, _ := lru.New[string, *AnalysisResult](cap) return &MemoryAnalysisCache{ cache: l, enabled: enabled, } } // Get retrieves cached analysis result from memory func (c *MemoryAnalysisCache) Get(ctx context.Context, key string) (*AnalysisResult, error) { if !c.enabled { return nil, fmt.Errorf("cache disabled") } c.mutex.RLock() defer c.mutex.RUnlock() if result, exists := c.cache.Get(key); exists { return result, nil } return nil, fmt.Errorf("cache miss") } // Set stores analysis result in memory cache func (c *MemoryAnalysisCache) Set(ctx context.Context, key string, result *AnalysisResult) error { if !c.enabled { return nil } c.mutex.Lock() defer c.mutex.Unlock() c.cache.Add(key, result) return nil } // IsEnabled returns whether caching is enabled func (c *MemoryAnalysisCache) IsEnabled() bool { return c.enabled } // RedisAnalysisCache implements Redis-based caching for analysis results type RedisAnalysisCache struct { cache cache.Cache enabled bool } // NewRedisAnalysisCache creates a new RedisAnalysisCache func NewRedisAnalysisCache(cache cache.Cache, enabled bool) *RedisAnalysisCache { return &RedisAnalysisCache{ cache: cache, enabled: enabled, } } // Get retrieves cached analysis result from Redis func (c *RedisAnalysisCache) Get(ctx context.Context, key string) (*AnalysisResult, error) { if !c.enabled || c.cache == nil { return nil, fmt.Errorf("cache disabled or unavailable") } var result AnalysisResult err := c.cache.Get(ctx, key, &result) if err != nil { return nil, fmt.Errorf("cache miss: %w", err) } return &result, nil } // Set stores analysis result in Redis cache func (c *RedisAnalysisCache) Set(ctx context.Context, key string, result *AnalysisResult) error { if !c.enabled || c.cache == nil { return nil } // TTL from config ttlSeconds := config.Cfg.NLPRedisCacheTTLSeconds err := c.cache.Set(ctx, key, result, time.Duration(ttlSeconds)*time.Second) if err != nil { logger.LogWarn("Failed to cache analysis result", logger.F("key", key), logger.F("error", err)) return err } return nil } // IsEnabled returns whether caching is enabled func (c *RedisAnalysisCache) IsEnabled() bool { return c.enabled && c.cache != nil } // CompositeAnalysisCache combines multiple cache layers type CompositeAnalysisCache struct { memoryCache AnalysisCache redisCache AnalysisCache enabled bool } // NewCompositeAnalysisCache creates a new CompositeAnalysisCache func NewCompositeAnalysisCache(memoryCache AnalysisCache, redisCache AnalysisCache, enabled bool) *CompositeAnalysisCache { return &CompositeAnalysisCache{ memoryCache: memoryCache, redisCache: redisCache, enabled: enabled, } } // Get retrieves cached analysis result from memory first, then Redis func (c *CompositeAnalysisCache) Get(ctx context.Context, key string) (*AnalysisResult, error) { if !c.enabled { return nil, fmt.Errorf("cache disabled") } // Try memory cache first if result, err := c.memoryCache.Get(ctx, key); err == nil { return result, nil } // Try Redis cache if result, err := c.redisCache.Get(ctx, key); err == nil { // Populate memory cache with Redis result c.memoryCache.Set(ctx, key, result) return result, nil } return nil, fmt.Errorf("cache miss") } // Set stores analysis result in both memory and Redis caches func (c *CompositeAnalysisCache) Set(ctx context.Context, key string, result *AnalysisResult) error { if !c.enabled { return nil } // Set in memory cache if err := c.memoryCache.Set(ctx, key, result); err != nil { logger.LogWarn("Failed to set memory cache", logger.F("key", key), logger.F("error", err)) } // Set in Redis cache if err := c.redisCache.Set(ctx, key, result); err != nil { logger.LogWarn("Failed to set Redis cache", logger.F("key", key), logger.F("error", err)) return err } return nil } // IsEnabled returns whether caching is enabled func (c *CompositeAnalysisCache) IsEnabled() bool { return c.enabled }