"""In-process preview cache for FlowPilot resolution-note / escalation-package previews.

Phase 3 implementation per FLOWPILOT-MIGRATION.md Section 5.5:
- Cache key: `(kind, session_id, state_version)` — no TTL needed, state_version
  is the source of truth.
- Invalidation: any write to session_facts, session_suggested_fixes, or
  script_generations bumps `ai_sessions.state_version`. Old entries simply
  stop being looked up and leak harmlessly until process restart.
- Storage: plain dict, single-process. When Session Sharing brings Redis,
  swap the storage without changing the call sites.

Bound: best-effort soft cap of 5000 entries. When exceeded we drop the
oldest insertion. Not a TTL — at current scale, the cap is more about
resident-memory hygiene than correctness.
"""
from __future__ import annotations

from collections import OrderedDict
from typing import Any
from uuid import UUID

_MAX_ENTRIES = 5000


class _PreviewCache:
    def __init__(self) -> None:
        self._store: OrderedDict[tuple[str, UUID, int], Any] = OrderedDict()

    def get(self, kind: str, session_id: UUID, state_version: int) -> Any | None:
        key = (kind, session_id, state_version)
        if key not in self._store:
            return None
        # Touch on access so LRU eviction is meaningful.
        self._store.move_to_end(key)
        return self._store[key]

    def set(self, kind: str, session_id: UUID, state_version: int, value: Any) -> None:
        key = (kind, session_id, state_version)
        self._store[key] = value
        self._store.move_to_end(key)
        # Evict oldest if over cap. OrderedDict.popitem(last=False) is O(1).
        while len(self._store) > _MAX_ENTRIES:
            self._store.popitem(last=False)

    def invalidate_session(self, session_id: UUID) -> None:
        """Drop all entries for a session — used when the session is deleted."""
        keys = [k for k in self._store if k[1] == session_id]
        for k in keys:
            del self._store[k]


preview_cache = _PreviewCache()
