-- File: lean/LruSpec.lean
-- LRU Cache Specification
-- Agents migrate a C++ LRU (std::list + unordered_map) to Rust Vec<(u64,u64)>.
-- Representation: List (Nat × Nat), head = MRU (most recently used).

namespace LruSpec

abbrev CacheState := List (Nat × Nat)   -- (key, value), head = MRU

-- Trim to capacity (keep MRU end)
def lruEvict (cache : CacheState) (cap : Nat) : CacheState :=
  cache.take cap

-- Insert/update key-val at MRU position; evict LRU tail if over capacity
def lruPut (cache : CacheState) (cap : Nat) (key val : Nat) : CacheState :=
  ((key, val) :: cache.filter (fun p => p.1 ≠ key)).take cap

-- Look up key; move to front (MRU) on hit
def lruGet (cache : CacheState) (key : Nat) : Option Nat × CacheState :=
  match cache.find? (fun p => p.1 == key) with
  | none => (none, cache)
  | some (_, v) =>
    let newCache := (key, v) :: cache.filter (fun p => p.1 ≠ key)
    (some v, newCache)

-- lruPut never exceeds capacity
theorem lruPut_bounded (cache : CacheState) (cap key val : Nat) :
    (lruPut cache cap key val).length ≤ cap := by
  simp only [lruPut]
  exact List.length_take_le cap _

-- DRT checks
example : lruPut [(1, 10), (2, 20)] 2 1 99 = [(1, 99), (2, 20)] := by native_decide
example : lruPut [(1, 10), (2, 20)] 2 3 30 = [(3, 30), (1, 10)] := by native_decide
example : lruEvict [(1, 10), (2, 20), (3, 30)] 2 = [(1, 10), (2, 20)] := by native_decide
example : lruGet [(1, 10), (2, 20), (3, 30)] 2 =
    (some 20, [(2, 20), (1, 10), (3, 30)]) := by native_decide
example : lruGet [(1, 10)] 2 = (none, [(1, 10)]) := by native_decide

end LruSpec
