/-
  Topolei.Trace
  =============
  H5 of the foundation stack: the **trace map** (inverse projection),
  in its polymorphic form.

  ## Polymorphic from day 1

  A `Trace α` is a list of contributing items of type `α`.  The
  parameter `α` is what the trace is *about*:

  - `Trace WCell`     — workspace-tree-level traces (rendered → workspace cells)
  - `Trace CTerm`     — cubical-syntax-level traces (rendered → CTerm sub-terms)
  - `Trace SourceLoc` — file/line provenance traces
  - `Trace HandleId`  — GPU resource provenance

  All four are the same algebraic structure (a free monoid on `α`) with
  the same theorems.  Polymorphism captures the "consolidate
  abstractions" principle: ONE Trace type, instantiated wherever
  needed.

  ## Why this layer

  H1+H2 (Selection) gave us *focus + history* — pointing at one cell
  with a path back to the root.  H3 (Subobject) gave us the *algebra
  of scopes*.  C2 (Obs.Ctx) gave us the typed peripheral category.

  H5 closes the loop: every rendered element carries a typed pointer
  back to the source items that contributed to producing it.  This is
  the **inverse projection**: given an output (a pixel, region, curve
  on the screen), recover the items whose values projected to it.

  ## Geometry, sheaves, bundles, differential structure: ALL derived

  Differential geometry is NOT a separate framework bolted onto cells.
  It is a *property of trace coherence* — particular shapes of how
  `Trace`s relate across the rendered space:

  - Trace varies smoothly across adjacent rendered elements →
    differential structure (Jacobian / connection / parallel transport).
  - Trace shares open sets in coherent overlaps → sheaf structure.
  - Multiple sources project to one rendered point → fiber bundle's
    preimage at that point.

  The same `Trace α` carries all three; the geometry is in the
  *predicates* / *projections* over traces, not in the type itself.

  ## What this file does NOT contain

  - **The cubical-trace function** `CTerm → Trace CTerm`.  That's a
    sibling file `Topolei.Cubical.Trace`, which uses *this* `Trace`.
  - **DecidableEq-dependent operations** (diff, intersect).  Add when
    needed; they require `DecidableEq α`.

  ## Reference

  Cells-spec §1.5 ("Rendering Context as a Cell"), §17.1
  ("Vulnerabilities as Topological Failures": side channels are
  unwanted traces — same abstraction, security layer).
-/

namespace Topolei.Trace

-- ── The Trace structure ───────────────────────────────────────────────────

/-- A trace: the typed list of items that contributed to producing a
    rendered element.  Polymorphic in the item type — instantiate
    with `WCell` for workspace-tree traces, `CTerm` for cubical-
    syntax traces, etc.  Plural because at singularities, multiple
    sources project to one rendered point — and even at non-singular
    points the trace can include intermediate items (the sub-things
    visited en route to the final output).

    The list order records *contribution order*: the first item is
    the deepest contributor, the last is the most recently seen.  This
    order matters when stacking traces during rendering. -/
structure Trace (α : Type) where
  items : List α
deriving Repr, Inhabited

namespace Trace

/-- The empty trace — no items contributed.  Identity element for
    `union`, witness that some rendered element has no source-item
    provenance (e.g., a clear-color background pixel). -/
def empty {α : Type} : Trace α := { items := [] }

/-- The single-item trace — exactly one source-item contributed.
    Used when lifting a primitive into a traced render. -/
def single {α : Type} (a : α) : Trace α := { items := [a] }

/-- Combine two traces by concatenating their item lists.  Order is
    preserved: the second trace's items append after the first's.
    This is *not* deduplicating — if an item appears in both traces
    we keep both occurrences (the list is multi-set-like).
    Deduplication is a separate operation that requires `DecidableEq`. -/
def union {α : Type} (t₁ t₂ : Trace α) : Trace α :=
  { items := t₁.items ++ t₂.items }

-- ── Monoid laws (empty is unit, union is associative) ────────────────────

@[simp] theorem empty_union {α : Type} (t : Trace α) :
    Trace.empty.union t = t := by
  cases t; simp [union, empty]

@[simp] theorem union_empty {α : Type} (t : Trace α) :
    t.union Trace.empty = t := by
  cases t; simp [union, empty]

theorem union_assoc {α : Type} (t₁ t₂ t₃ : Trace α) :
    (t₁.union t₂).union t₃ = t₁.union (t₂.union t₃) := by
  cases t₁; cases t₂; cases t₃
  simp [union, List.append_assoc]

end Trace

-- ── TracedRender: rendered element + its trace ────────────────────────────

/-- A `TracedRender R α` is a rendered element of type `R` paired
    with its `Trace α`.  Every rendered output carries its provenance.

    `R` is the rendered-element type (e.g. `PixelColor`, a region
    label, a frame).  `α` is the trace-item type (e.g. `WCell`,
    `CTerm`, …).  Polymorphism lets the same abstraction work for
    every rendering granularity and every provenance shape. -/
structure TracedRender (R α : Type) where
  render : R
  trace  : Trace α
deriving Repr, Inhabited

namespace TracedRender

/-- Lift a rendered value with a single-item trace. -/
def lift {R α : Type} (r : R) (a : α) : TracedRender R α :=
  { render := r, trace := Trace.single a }

/-- Lift a rendered value with no trace — for outputs without source-
    item provenance (clear color, vertex-stage outputs, sealed-cell
    rasterizer products). -/
def liftEmpty {R α : Type} (r : R) : TracedRender R α :=
  { render := r, trace := Trace.empty }

/-- Combine two traced renders by combining their traces (left-biased
    on the rendered value). -/
def merge {R α : Type} (a b : TracedRender R α) : TracedRender R α :=
  { render := a.render, trace := a.trace.union b.trace }

@[simp] theorem merge_liftEmpty {R α : Type} (a : TracedRender R α) (r' : R) :
    a.merge (liftEmpty r') = a := by
  cases a; simp [merge, liftEmpty, Trace.union_empty]

@[simp] theorem liftEmpty_merge {R α : Type} (r : R) (b : TracedRender R α) :
    (liftEmpty r).merge b = { render := r, trace := b.trace } := by
  simp [merge, liftEmpty, Trace.empty_union]

end TracedRender

-- ── Operational sanity (polymorphic on `String` items as the simplest demo) ─

/-- Demo trace over `String` items. -/
def demoTrace : Trace String :=
  (Trace.single "alpha").union (Trace.single "beta")

#eval demoTrace.items.length      -- expected: 2
#eval demoTrace.items             -- expected: ["alpha", "beta"]

/-- Empty-union round-trip. -/
example : (Trace.empty : Trace String).union demoTrace = demoTrace :=
  Trace.empty_union _

/-- Union associativity exercised on three traces. -/
example :
    let t₁ := (Trace.single "a" : Trace String)
    let t₂ := Trace.single "b"
    let t₃ := Trace.empty
    (t₁.union t₂).union t₃ = t₁.union (t₂.union t₃) :=
  Trace.union_assoc _ _ _

/-- `TracedRender` lift carries the trace it's given. -/
example : (TracedRender.lift (5 : Nat) "src").trace = Trace.single "src" := rfl

/-- Merging into a no-trace render leaves the trace alone. -/
example :
    let a : TracedRender Nat String := TracedRender.lift 5 "src"
    a.merge (TracedRender.liftEmpty 7) = a :=
  TracedRender.merge_liftEmpty _ _

end Topolei.Trace
