#!/usr/bin/env python3
"""
Thread-safe LRU cache for tensor regeneration receipts/blobs.

Usage:
    from helix_cdc.regen_cache import RegenCache
    cache = RegenCache(max_items=1024, code_version=GIT_COMMIT)
    key = cache.make_key(tensor_id, size_bytes, backend="c")
    blob = cache.get(key)
    if blob is None:
        blob = regen_tensor(...)
        cache.set(key, blob)
"""
from collections import OrderedDict
import threading
from typing import Any, Optional

class RegenCache:
    def __init__(self, max_items: int = 1024, code_version: Optional[str] = None):
        self._max_items = max_items
        self._code_version = code_version or ""
        self._lock = threading.RLock()
        self._data = OrderedDict()  # key -> value (most-recently-used at end)
        self.hits = 0
        self.misses = 0

    def make_key(self, tensor_id: str, size_bytes: int, backend: str = "c") -> str:
        # Compose a deterministic key. Include code_version so a new commit invalidates old entries.
        return f"{self._code_version}|{backend}|{tensor_id}|{size_bytes}"

    def get(self, key: str) -> Optional[Any]:
        with self._lock:
            if key in self._data:
                value = self._data.pop(key)
                self._data[key] = value  # move to end (most recently used)
                self.hits += 1
                return value
            else:
                self.misses += 1
                return None

    def set(self, key: str, value: Any) -> None:
        with self._lock:
            if key in self._data:
                # replace and mark as recently used
                self._data.pop(key)
            self._data[key] = value
            # Evict oldest if over size
            while len(self._data) > self._max_items:
                self._data.popitem(last=False)

    def clear(self) -> None:
        with self._lock:
            self._data.clear()
            self.hits = 0
            self.misses = 0

    def stats(self) -> dict:
        with self._lock:
            return {"items": len(self._data), "hits": self.hits, "misses": self.misses}
