-- File: lean/ShortestPathSpecAeneas.lean
-- Shortest Path (Dijkstra) — Aeneas-compatible specification.
-- Types mirror what Aeneas generates from Vec<(u64,u64,u64)> Rust code.
-- Requires: lake update  (fetches the Aeneas package)

import Aeneas
open Aeneas.Primitives

namespace ShortestPathSpecAeneas

-- Aeneas maps Vec<(u64, u64, u64)> → List (U64 × U64 × U64)
abbrev Graph := List (U64 × U64 × U64)  -- (from, to, weight)

-- Helper: safe list indexing
def listGet (l : List (Option U64)) (i : U64) : Option (Option U64) :=
  l.get? i.val.toNat

-- Update distance at index i if new distance d is smaller
def updateDist (dists : List (Option U64)) (i d : U64) : List (Option U64) :=
  List.zipWith (fun j curr =>
    if j == i.val.toNat then
      match curr with
      | none           => some d
      | some existing  => some (if d.val < existing.val then d else existing)
    else curr)
    (List.range dists.length)
    dists

-- Relax one edge
def relaxEdge (dists : List (Option U64)) (fr to w : U64) : List (Option U64) :=
  match listGet dists fr with
  | some (some df) =>
    match df + w with  -- saturating add avoids overflow for reasonable weights
    | some newD => updateDist dists to newD
    | none      => dists
  | _ => dists

-- One relaxation pass over all edges
def relaxPass (dists : List (Option U64)) (edges : Graph) : List (Option U64) :=
  edges.foldl (fun d e => relaxEdge d e.1 e.2.1 e.2.2) dists

-- Bellman-Ford: n-1 relaxation passes
def dijkstra (graph : Graph) (n src : U64) : Result (List (Option U64)) :=
  let init := (List.range n.val.toNat).map (fun i =>
    if i == src.val.toNat then some (0 : U64) else none)
  let result := (List.range (n.val.toNat - 1)).foldl (fun d _ => relaxPass d graph) init
  .ok result

-- Shortest distance src → dst
def shortestDist (graph : Graph) (n src dst : U64) : Result (Option U64) :=
  do let dists ← dijkstra graph n src
     .ok (match listGet dists dst with
          | some (some d) => some d
          | _             => none)

-- Self-distance is zero when src < n
theorem self_distance_zero (graph : Graph) (n src : U64) (h : src.val < n.val) :
    shortestDist graph n src src = .ok (some 0) := by
  simp only [shortestDist, dijkstra, Result.bind]
  simp only [listGet, List.get?]
  simp only [relaxPass, relaxEdge, updateDist]
  -- The init list at index src is some 0; relaxation preserves it.
  sorry -- Full proof requires showing relaxation doesn't worsen src's own distance

end ShortestPathSpecAeneas
