/-
  Topolei.Obs.Ctx
  ===============
  C2 of the categories-from-the-interface stack:
  **the observation category for peripherals.**

  Observations are typed peripheral configurations — they describe
  *what surface the rendering lands on*, not the rendering itself.
  Window dimensions, pixel format, eventually GPU adapter / mouse
  position / clock tick.  None of these are transports in the
  cubical sense (cells-spec §1.7 calls them "sealed cells whose
  interior cannot be deformed from above"); but they do organise
  themselves into a category, and that category is what every
  transport in the cell calculus eventually has to live over.

  ## What this file proves

  We define `Ctx` as a record (the typed peripheral) and `resize` /
  `reformat` as the basic ops.  We prove the *laws* that say these
  ops compose like morphisms in a category:

  - **Identity**: `c.resize c.width c.height = c`,
                  `c.reformat c.pixelFmt = c`
  - **Idempotence on overwrite**:
        `(c.resize w₁ h₁).resize w₂ h₂ = c.resize w₂ h₂`
        (the second resize wins; the first is forgotten)
  - **Commutativity** of independent ops:
        `(c.resize w h).reformat fmt = (c.reformat fmt).resize w h`

  These laws *make `Ctx` a category* — objects are values of `Ctx`,
  morphisms are equivalence classes of resize/reformat sequences,
  and the laws above quotient the free monoid down to the right
  thing.  We don't materialise the category as a Lean structure
  here because the laws-as-`@[simp]`-rewrites are enough for the
  reasoning we'll need; if H4 (horizontal lifts of transports
  along observation arrows) needs the explicit category, we add it
  then.

  ## What this file does NOT do

  - It does not define **the fibration** `p : Cells → Obs` that
    the cells-spec promises.  That's C5, derived from C2 + C3.
    Once `Ctx` is in place, we lift `compileEMLPath` to take a
    `Ctx` explicitly instead of having `width`/`height`/format
    floating as bare arguments.

  - It does not define **arrows as a typed inductive** (the
    `Arrow : Ctx → Ctx → Type` form I sketched earlier).  That
    would force us to either prove laws *up to a quotient* or
    use a higher-inductive type — overhead that buys nothing
    new at this layer.  Instead, we use the *equational
    presentation*: ops + laws.  When H4 needs to talk about
    "an arrow `f : c → c'`" abstractly, we'll add the typed
    inductive then.

  - It does not yet bridge to `Topolei.Selection.Selection`.  A
    `WiredSelection` would pair `(c : Ctx) (s : Selection)` with
    a compatibility predicate; deferred until we know which
    compatibility actually matters for rendering.

  ## Reference

  Cells-spec §1.5 ("Rendering Context as a Cell"), §15.2
  ("Presheaf of Potential Cells").
-/

namespace Topolei.Obs

-- ── Pixel format ──────────────────────────────────────────────────────────
--
-- The "type" of a framebuffer.  Sealed-cell — its IEEE / sRGB /
-- linear-RGB semantics are determined by the GPU's hardware spec,
-- not by our calculus.  We only carry it as a typed tag so
-- rendering pipelines can refuse to bind into a context whose
-- format they don't support.

inductive PixelFormat where
  /-- 32-bit float per channel; no display clamp.  Used by the
      `render_faithful` probe so CPU/GPU agreement isn't
      dominated by quantisation. -/
  | rgbaF32  : PixelFormat
  /-- 8-bit per channel sRGB.  Display surface; values clamp
      to [0, 1] and gamma-encode.  Standard for live windows. -/
  | rgbaSrgb : PixelFormat
deriving Repr, DecidableEq, Inhabited

-- ── Observation context ───────────────────────────────────────────────────

/-- An observation context: the typed peripheral configuration
    a render lands on.  Objects of the observation category. -/
structure Ctx where
  width    : Nat
  height   : Nat
  pixelFmt : PixelFormat
deriving Repr, DecidableEq, Inhabited

namespace Ctx

-- ── Operations (morphisms in the implicit category) ──────────────────────

/-- Resize the observation surface.  Width × height update. -/
def resize (c : Ctx) (w h : Nat) : Ctx :=
  { c with width := w, height := h }

/-- Change the pixel format. -/
def reformat (c : Ctx) (fmt : PixelFormat) : Ctx :=
  { c with pixelFmt := fmt }

-- ── Identity laws: ops with the current value are no-ops ─────────────────

/-- Resizing a context to its current dimensions is the identity. -/
@[simp] theorem resize_self (c : Ctx) :
    c.resize c.width c.height = c := by
  rcases c with ⟨_, _, _⟩
  rfl

/-- Reformatting a context to its current format is the identity. -/
@[simp] theorem reformat_self (c : Ctx) :
    c.reformat c.pixelFmt = c := by
  rcases c with ⟨_, _, _⟩
  rfl

-- ── Idempotence on overwrite: the last value wins ────────────────────────

/-- Composed resizes collapse: only the outer dimensions matter. -/
@[simp] theorem resize_resize (c : Ctx) (w₁ h₁ w₂ h₂ : Nat) :
    (c.resize w₁ h₁).resize w₂ h₂ = c.resize w₂ h₂ := by
  rcases c with ⟨_, _, _⟩
  rfl

/-- Composed reformats collapse: only the outer format matters. -/
@[simp] theorem reformat_reformat (c : Ctx) (f₁ f₂ : PixelFormat) :
    (c.reformat f₁).reformat f₂ = c.reformat f₂ := by
  rcases c with ⟨_, _, _⟩
  rfl

-- ── Commutativity of independent ops ─────────────────────────────────────

/-- Resize and reformat commute — they touch independent fields, so
    order doesn't matter. -/
@[simp] theorem resize_reformat (c : Ctx) (w h : Nat) (fmt : PixelFormat) :
    (c.resize w h).reformat fmt = (c.reformat fmt).resize w h := by
  rcases c with ⟨_, _, _⟩
  rfl

/-- Symmetric form: reformat-then-resize = resize-then-reformat. -/
theorem reformat_resize (c : Ctx) (fmt : PixelFormat) (w h : Nat) :
    (c.reformat fmt).resize w h = (c.resize w h).reformat fmt :=
  (Ctx.resize_reformat c w h fmt).symm

-- ── Read-back of the field updates ───────────────────────────────────────
-- These are field-update lemmas that downstream proofs will lean on.

@[simp] theorem resize_width (c : Ctx) (w h : Nat) :
    (c.resize w h).width = w := by
  rcases c with ⟨_, _, _⟩
  rfl

@[simp] theorem resize_height (c : Ctx) (w h : Nat) :
    (c.resize w h).height = h := by
  rcases c with ⟨_, _, _⟩
  rfl

@[simp] theorem resize_pixelFmt (c : Ctx) (w h : Nat) :
    (c.resize w h).pixelFmt = c.pixelFmt := by
  rcases c with ⟨_, _, _⟩
  rfl

@[simp] theorem reformat_width (c : Ctx) (fmt : PixelFormat) :
    (c.reformat fmt).width = c.width := by
  rcases c with ⟨_, _, _⟩
  rfl

@[simp] theorem reformat_height (c : Ctx) (fmt : PixelFormat) :
    (c.reformat fmt).height = c.height := by
  rcases c with ⟨_, _, _⟩
  rfl

@[simp] theorem reformat_pixelFmt (c : Ctx) (fmt : PixelFormat) :
    (c.reformat fmt).pixelFmt = fmt := by
  rcases c with ⟨_, _, _⟩
  rfl

end Ctx

-- ── Concrete demo (operational sanity check via `#eval`) ──────────────────

/-- Default rendering context: 800×600, sRGB. -/
def defaultCtx : Ctx :=
  { width := 800, height := 600, pixelFmt := PixelFormat.rgbaSrgb }

#eval defaultCtx.width                   -- expected: 800
#eval defaultCtx.resize 1024 768           |>.width      -- expected: 1024
#eval (defaultCtx.resize 1024 768).reformat PixelFormat.rgbaF32 |>.pixelFmt
                                          -- expected: PixelFormat.rgbaF32
#eval ((defaultCtx.resize 100 100).resize 200 200).width    -- expected: 200

end Topolei.Obs
