/-
  Topolei.EML.Path
  ================
  Connection between EML expressions and the cubical interval.

  An EML expression with a free "dimension variable" (a named string)
  that is evaluated at 0.0 or 1.0 is a path in the Float domain.
  This is the bridge between:

    · the cubical interval I (Bool endpoints: false = 0, true = 1)
    · EML evaluation (Float-valued)

  Key definitions:
    · EMLExpr.evalWithEnv  — generalised evaluator with custom var resolver
    · EMLExpr.varAbsent    — syntactic check that a variable does not appear
    · EMLPath              — an EML expression with a distinguished dim var
    · EMLPath.atBool/at0/at1 — evaluation at Bool endpoints
    · EMLPath.const_endpoints — absent dim var → at0 = at1

  The GPU-layer connection (linking baseEnv to shaderVar) lives in
  GPU/Spec.lean rather than here, to avoid a circular import.
-/

import Topolei.EML
import Topolei.Cubical.DimLine

-- ── Bool → Float endpoint map ─────────────────────────────────────────────────

/-- Map Bool to its Float interval endpoint: false ↦ 0.0, true ↦ 1.0 -/
def boolToFloat : Bool → Float
  | false => 0.0
  | true  => 1.0

@[simp] theorem boolToFloat_false : boolToFloat false = 0.0 := rfl
@[simp] theorem boolToFloat_true  : boolToFloat true  = 1.0 := rfl

-- ── Generalised EML evaluator ─────────────────────────────────────────────────

/-- Evaluate an EML expression using a custom variable resolver.
    This generalises EMLExpr.evalAt (which uses shaderVar) so we can
    override the dimension variable without touching shader-layer types. -/
def EMLExpr.evalWithEnv (env : String → Float) : EMLExpr → Float
  | .one      => 1.0
  | .var name => env name
  | .eml l r  =>
      let lv := l.evalWithEnv env
      let rv := r.evalWithEnv env
      Float.exp lv - Float.log (max rv 1e-9)

-- ── Variable absence predicate ────────────────────────────────────────────────

/-- Syntactic check: named variable does not appear in the expression. -/
def EMLExpr.varAbsent (name : String) : EMLExpr → Bool
  | .one      => true
  | .var n    => n != name
  | .eml l r  => l.varAbsent name && r.varAbsent name

/-- Two environments that agree on all names except `name` give the same
    evaluation on expressions that don't mention `name`. -/
theorem EMLExpr.evalWithEnv_congr
    (e : EMLExpr) (name : String)
    (habs : e.varAbsent name = true)
    (env1 env2 : String → Float)
    (henv : ∀ n, n ≠ name → env1 n = env2 n) :
    e.evalWithEnv env1 = e.evalWithEnv env2 := by
  induction e with
  | one => rfl
  | var n =>
      simp only [varAbsent, bne_iff_ne] at habs
      simp [evalWithEnv, henv n habs]
  | eml l r ihl ihr =>
      simp only [varAbsent, Bool.and_eq_true] at habs
      simp [evalWithEnv, ihl habs.1, ihr habs.2]

-- ── EMLPath ───────────────────────────────────────────────────────────────────

/-- An EML path: an expression parametric in a named dimension variable.
    The `dimName` variable ranges over {0.0, 1.0} ≅ Bool. -/
structure EMLPath where
  dimName : String    -- dimension variable (e.g. "t")
  body    : EMLExpr   -- parametric expression

/-- Evaluate an EMLPath at a Bool endpoint, given a base variable resolver for
    all variables other than dimName. -/
def EMLPath.atBool (path : EMLPath) (b : Bool) (baseEnv : String → Float) : Float :=
  path.body.evalWithEnv (fun name =>
    if name = path.dimName then boolToFloat b else baseEnv name)

def EMLPath.at0 (path : EMLPath) (baseEnv : String → Float) : Float :=
  path.atBool false baseEnv

def EMLPath.at1 (path : EMLPath) (baseEnv : String → Float) : Float :=
  path.atBool true baseEnv

-- ── Endpoint reduction lemmas ─────────────────────────────────────────────────

@[simp] theorem EMLPath.at0_def (path : EMLPath) (baseEnv : String → Float) :
    path.at0 baseEnv =
    path.body.evalWithEnv (fun name =>
      if name = path.dimName then 0.0 else baseEnv name) := by
  simp [at0, atBool, boolToFloat]

@[simp] theorem EMLPath.at1_def (path : EMLPath) (baseEnv : String → Float) :
    path.at1 baseEnv =
    path.body.evalWithEnv (fun name =>
      if name = path.dimName then 1.0 else baseEnv name) := by
  simp [at1, atBool, boolToFloat]

-- ── Constant path ─────────────────────────────────────────────────────────────

/-- When the body does not mention dimName, at0 = at1 for any baseEnv. -/
theorem EMLPath.const_endpoints (path : EMLPath)
    (habs : path.body.varAbsent path.dimName = true)
    (baseEnv : String → Float) :
    path.at0 baseEnv = path.at1 baseEnv := by
  simp only [at0_def, at1_def]
  apply EMLExpr.evalWithEnv_congr _ _ habs
  intro n hn
  simp [if_neg hn]

-- ── Example: exp path ────────────────────────────────────────────────────────

/-- The exponential path: f(t) = exp(t) for t ∈ {0, 1}. -/
def expPath : EMLPath :=
  { dimName := "t"
    body    := EMLExpr.expOf (.var "t") }

/-- At t = 0, expPath evaluates to exp(0) - log(max 1.0 1e-9). -/
theorem expPath_at0 (baseEnv : String → Float) :
    expPath.at0 baseEnv =
    Float.exp 0.0 - Float.log (max 1.0 1e-9) := by
  simp [expPath, EMLExpr.expOf, EMLExpr.evalWithEnv]

/-- At t = 1, expPath evaluates to exp(1) - log(max 1.0 1e-9). -/
theorem expPath_at1 (baseEnv : String → Float) :
    expPath.at1 baseEnv =
    Float.exp 1.0 - Float.log (max 1.0 1e-9) := by
  simp [expPath, EMLExpr.expOf, EMLExpr.evalWithEnv]

-- ── Connection to DimLine ─────────────────────────────────────────────────────
/-
  Structural parallel:

    DimLine (cubical, CType-valued)   ↔   EMLPath (rendering, Float-valued)
    ───────────────────────────────────────────────────────────────────────
    DimLine.binder : DimVar            ↔   EMLPath.dimName : String
    DimLine.at0    : CType             ↔   EMLPath.at0     : Float
    DimLine.at1    : CType             ↔   EMLPath.at1     : Float
    transp_const_id (T2)               ↔   EMLPath.const_endpoints

  Transport along a constant DimLine is the identity (T2).
  EML analogue: evaluation of a constant EMLPath (dimName absent) gives
  the same Float value at both endpoints.

  The rendering correctness claim:
    "If the GPU evaluates shader at the 1-end of a DimLine,
     the Float result equals EMLPath.at1 baseEnv."
  This is the bridge axiom in GPU/Spec.lean (to be added there).
-/

theorem EMLPath_const_mirrors_T2
    (path : EMLPath)
    (habs : path.body.varAbsent path.dimName = true)
    (baseEnv : String → Float) :
    path.at0 baseEnv = path.at1 baseEnv :=
  EMLPath.const_endpoints path habs baseEnv

-- ── PlotConfig → EMLPath ──────────────────────────────────────────────────────
/-
  A `PlotConfig` carries an `EMLExpr` and a distinguished `dimName` which is
  the path dimension.  Viewing a plot as an `EMLPath` is a direct projection:
  drop the display metadata, keep the expression and the dimension.

  When the plot's expression does not mention `dimName`, the resulting path
  is constant (T2 analogue); when it does, the plot is genuinely time-varying
  and `at0`, `at1` differ.
-/

/-- Project a `PlotConfig` to its `EMLPath` view. -/
def PlotConfig.toEMLPath (cfg : PlotConfig) : EMLPath :=
  { dimName := cfg.dimName
    body    := cfg.expr }

@[simp] theorem PlotConfig.toEMLPath_dimName (cfg : PlotConfig) :
    cfg.toEMLPath.dimName = cfg.dimName := rfl

@[simp] theorem PlotConfig.toEMLPath_body (cfg : PlotConfig) :
    cfg.toEMLPath.body = cfg.expr := rfl

/-- A plot is a *constant path* exactly when its expression does not mention
    the plot's declared dimension variable. -/
theorem PlotConfig.const_path_of_varAbsent
    (cfg : PlotConfig)
    (habs : cfg.expr.varAbsent cfg.dimName = true)
    (baseEnv : String → Float) :
    cfg.toEMLPath.at0 baseEnv = cfg.toEMLPath.at1 baseEnv :=
  EMLPath.const_endpoints cfg.toEMLPath habs baseEnv

-- ── Demo expressions as paths ─────────────────────────────────────────────────
/-
  `plotExp` and `plotLn` (in `EML.lean`) use `px` as their free variable,
  not the dimension variable `t`.  They are therefore *constant paths* — the
  same value at both endpoints — under the default `dimName := "t"`.
-/

theorem plotExp_body_varAbsent : plotExp.expr.varAbsent "t" = true := by decide

theorem plotExp_is_const_path (baseEnv : String → Float) :
    plotExp.toEMLPath.at0 baseEnv = plotExp.toEMLPath.at1 baseEnv :=
  plotExp.const_path_of_varAbsent plotExp_body_varAbsent baseEnv

theorem plotLn_body_varAbsent : plotLn.expr.varAbsent "t" = true := by decide

theorem plotLn_is_const_path (baseEnv : String → Float) :
    plotLn.toEMLPath.at0 baseEnv = plotLn.toEMLPath.at1 baseEnv :=
  plotLn.const_path_of_varAbsent plotLn_body_varAbsent baseEnv

-- ── Parametric example (a genuinely non-constant path) ───────────────────────

/-- A parametric plot that actually uses the dimension variable `t`:
    `eml(t, 1) = exp(t)` — a path from `exp(0)` at `t=0` to `exp(1)` at `t=1`. -/
def plotExpT : PlotConfig :=
  { expr    := EMLExpr.expOf (.var "t")
    dimName := "t" }

theorem plotExpT_at0 (baseEnv : String → Float) :
    plotExpT.toEMLPath.at0 baseEnv =
    Float.exp 0.0 - Float.log (max 1.0 1e-9) := by
  simp [plotExpT, PlotConfig.toEMLPath, EMLPath.at0, EMLPath.atBool,
        EMLExpr.expOf, EMLExpr.evalWithEnv, boolToFloat]

theorem plotExpT_at1 (baseEnv : String → Float) :
    plotExpT.toEMLPath.at1 baseEnv =
    Float.exp 1.0 - Float.log (max 1.0 1e-9) := by
  simp [plotExpT, PlotConfig.toEMLPath, EMLPath.at1, EMLPath.atBool,
        EMLExpr.expOf, EMLExpr.evalWithEnv, boolToFloat]

-- The shader whose semantic IS `EMLPath.toColor` is now built directly
-- as a `naga::Module` (no GLSL string intermediary) on the Rust side
-- by `native/canvas-rs/src/emit_naga.rs::build_probe_module`.  See
-- `NAGA_IR_PLAN.md` for the construction; `Topolei.GPU.Spec`'s
-- `compileEMLPath_correct` axiom states the contract that builder
-- must satisfy.
