-- EML tree: S → 1 | eml(S, S)
-- eml(x, y) = exp(x) − ln(y)
--
-- Single binary primitive that generates all elementary functions.
-- (Odrzywolek 2026, arXiv:2603.21852)

-- ── Core inductive ────────────────────────────────────────────────────────────

inductive EMLExpr where
  | one                    : EMLExpr
  | var  (name : String)   : EMLExpr   -- free variable resolved in the rendering env
  | eml  (l r : EMLExpr)  : EMLExpr

-- ── Derived forms ─────────────────────────────────────────────────────────────

-- exp(x) = eml(x, 1)   since exp(x) − ln(1) = exp(x) − 0 = exp(x)
def EMLExpr.expOf (x : EMLExpr) : EMLExpr := .eml x .one

-- ln(x) = eml(1, eml(eml(1, x), 1))
def EMLExpr.lnOf (x : EMLExpr) : EMLExpr :=
  .eml .one (.eml (.eml .one x) .one)

-- ── Plot configuration ────────────────────────────────────────────────────────

/-- A path config: an EML expression plus its distinguished path-dimension
    variable.  `dimName` is the name that ranges over `{0, 1}` when the
    config is interpreted as an `EMLPath` (see `EML/Path.lean`'s
    `PlotConfig.toEMLPath`).  When `dimName` does not occur in `expr`,
    the path is *constant* (its value is the same at both endpoints);
    when it does, the path is genuinely parametric. -/
structure PlotConfig where
  expr    : EMLExpr
  dimName : String := "t"

-- ── Named demo expressions (probe test fixtures) ─────────────────────────────

-- exp(x): depth-1 EML tree, one node.
def plotExp : PlotConfig := {
  expr := EMLExpr.expOf (.var "px")
}

-- ln(x): depth-3 EML tree, three nodes.
--
-- Historical note: an earlier `plotLn` used the variable name
-- `"max(px, 0.001)"` to clamp negative inputs on the GPU side.  That
-- made the GPU shader evaluate `max(px, 0.001)` as a GLSL expression
-- but left Lean's `shaderVar` hitting the fallback `0.0` — the two
-- sides disagreed on the semantic of the variable.  The probe test
-- surfaced the divergence; the fix is to use a real variable `px`
-- and accept that `ln` of negative `px` produces `NaN` on both sides
-- (the two sides agree, which is what `render_faithful` cares about).
def plotLn : PlotConfig := {
  expr := EMLExpr.lnOf (.var "px")
}

-- ── Continuous-homotopy demo: a genuinely parametric path ────────────────────
-- `exp(px) − t` translates the exponential curve down by `t`.  At `t=0`
-- the curve is `y = exp(x)`; at `t=1` it's `y = exp(x) − 1`; in between it
-- smoothly slides.  EML-expressible because `exp(px) - log(exp(t))` reduces
-- (via `log ∘ exp = id`) to `exp(px) - t`; body is
-- `eml(var "px", eml(var "t", one))`.

def plotTransp : PlotConfig := {
  expr    := .eml (.var "px") (.eml (.var "t") .one)
  dimName := "t"
}

-- ── Clean fibers in [0, 1] for greyscale demos ──────────────────────────────
-- These bodies are picked specifically so their image lies in `[0, 1]`,
-- matching the framebuffer's natural display range.  Each is a 1-cell;
-- their shape under different `pathParam` values shows the transport
-- in action with no clamping artifacts.

/-- The 1-cell whose body IS the dim variable: at `pathParam = c` every
    pixel evaluates to `c`.  Different fibers display as solid-color
    panels with brightness equal to the fiber's parameter.
    `at0 = solid black`, `at1 = solid white`, `mid = solid 50% grey`. -/
def plotT : PlotConfig := {
  expr    := .var "t"
  dimName := "t"
}

/-- The 1-cell whose body is `px`: image is the horizontal coordinate
    itself, a black-to-white left-to-right gradient.  Constant
    1-cell (no `t` dependence) — every fiber is the same gradient.
    Useful as a sanity check: fibers should NOT differ. -/
def plotPx : PlotConfig := {
  expr    := .var "px"
  dimName := "t"
}
