;; Canonical package declaration.
(define-package "oracle/cached-infer"
  :description "Cached LLM inference with golden receipt matching"
  :exports '(cached-infer)
  :effects '(infer.op)
  :requires '(oracle/core)
  :version "0.1.0"
)
;;; lib/cache/cached-infer.lisp
;;; Semantic Cache Library - Job 029
;;;
;;; Provides cached versions of LLM inference to avoid redundant API calls.
;;; Responses are cached by prompt hash and returned instantly on subsequent calls.
;;;
;;; Usage:
;;;   (cached-infer "What is 2+2?")   ; First call: LLM
;;;   (cached-infer "What is 2+2?")   ; Second call: Instant from cache!
;;;
;;;   (cache-stats)   ; Get hit/miss statistics
;;;   (cache-clear)   ; Clear all cached entries

;; ============================================================
;; Core Cached Inference
;; ============================================================

(define (cached-infer prompt)
  "Call LLM with caching. Identical prompts return cached responses.

   First call to a prompt: LLM is called, response is cached.
   Subsequent calls with same prompt: Cached response returned instantly.

   Returns: LLM response string

   Example:
     (cached-infer \"Summarize this document\")
  "
  ;; Check cache first
  (let ((cached (effect cache.get.op prompt)))
    (if (string? cached)
        ;; Cache hit - return cached response
        (begin
          ;; (display \"[cache] HIT\\n\")
          cached)
        ;; Cache miss - call LLM and cache result
        (begin
          ;; (display \"[cache] MISS\\n\")
          (let ((response (effect infer.op prompt)))
            (effect cache.set.op prompt response "unknown")
            response)))))

(define (cached-infer-with-model prompt model)
  "Cached inference with explicit model tracking.

   model: String identifying the model (e.g., \"gpt-4o\", \"claude-sonnet\")

   Example:
     (cached-infer-with-model \"Analyze code\" \"gpt-4o\")
  "
  (let ((cached (effect cache.get.op prompt)))
    (if (string? cached)
        cached
        (let ((response (effect infer.op prompt)))
          (effect cache.set.op prompt response model)
          response))))

;; ============================================================
;; Cache Management
;; ============================================================

(define (cache-stats)
  "Get cache statistics as a record.

   Returns a record with:
     - hits: Number of cache hits
     - misses: Number of cache misses
     - entries: Number of cached entries
     - savedCalls: Number of LLM calls saved (same as hits)
     - hitRate: Ratio of hits to total lookups (0.0-1.0)

   Example:
     (define stats (cache-stats))
     (record-ref stats 'hitRate)  ; => 0.75
  "
  (effect cache.stats.op))

(define (cache-has? prompt)
  "Check if a prompt is in the cache (without counting as hit/miss).

   Returns: #t if cached, #f otherwise
  "
  (effect cache.has.op prompt))

(define (cache-clear)
  "Clear all cached entries and reset statistics.

   Use when you want fresh LLM responses or to free memory.
  "
  (effect cache.clear.op)
  (display "[cache] Cleared\n"))

;; ============================================================
;; Helper: record-ref for cache stats
;; ============================================================

;; Note: record-ref may already be defined if loading other libs
;; This is a local fallback definition
(define (cache-record-ref m key)
  "Access field in a Map value returned by cache-stats."
  (if (eq? m #f)
      #f
      (let ((found (cache-assoc key m)))
        (if (pair? found)
            (cdr found)
            #f))))

(define (cache-assoc key alist)
  (cond
    ((null? alist) #f)
    ((equal? key (car (car alist))) (car alist))
    (else (cache-assoc key (cdr alist)))))

'cache/cached-infer-loaded
