/-
Port of `Fixedpoint.agda`.

Same gas-based algorithm: iterate `f` starting at the chain-bottom `⊥`; since
the lattice has fixed height `h`, a fixed point must be reached within `h + 1`
steps, or we would build a `<`-chain longer than the longest one. We
deliberately do *not* use mathlib's `OrderHom.lfp` (different proof approach,
and not computable).

As in Agda — where the module took `{{flA : IsFiniteHeightLattice A h …}}` —
the finite-height structure arrives by instance resolution
(`[FiniteHeightLattice α]`); only `f` and its monotonicity are explicit.

Correspondence:
  doStep             ↦ Spa.Fixedpoint.doStep   (the chain argument now carries
                                                 `a₁ = ⊥` and its length in the
                                                 `LTSeries` structure itself)
  fix                ↦ Spa.Fixedpoint.fix
  aᶠ                 ↦ Spa.Fixedpoint.aFix
  aᶠ≈faᶠ             ↦ Spa.Fixedpoint.aFix_eq
  stepPreservesLess  ↦ Spa.Fixedpoint.doStep_le
  aᶠ≼                ↦ Spa.Fixedpoint.aFix_le
-/
import Spa.Lattice

namespace Spa.Fixedpoint

open FiniteHeightLattice (height fixedHeight)

variable {α : Type*} [Lattice α] [DecidableEq α] [FiniteHeightLattice α]

/-- Agda: `doStep`. `g` is gas; the invariant `c.length + g = h + 1` guarantees
that when gas runs out the chain contradicts boundedness. -/
def doStep (f : α → α) (hf : Monotone f) :
    ∀ (g : ℕ) (c : LTSeries α), c.length + g = height (α := α) + 1 →
      c.last ≤ f c.last → {a : α // a = f a}
  | 0, c, hlen, _ =>
    absurd ((fixedHeight (α := α)).bounded c) (by omega)
  | g + 1, c, hlen, hle =>
    if heq : c.last = f c.last then
      ⟨c.last, heq⟩
    else
      doStep f hf g (c.snoc (f c.last) (lt_of_le_of_ne hle heq))
        (by simp [RelSeries.snoc]; omega)
        (by rw [RelSeries.last_snoc]; exact hf hle)

/-- Agda: `fix`. Start iterating from `⊥`. -/
def fix (f : α → α) (hf : Monotone f) : {a : α // a = f a} :=
  doStep f hf (height (α := α) + 1) (RelSeries.singleton _ (FiniteHeightLattice.bot α))
    (by simp)
    (by simpa [RelSeries.last_singleton]
          using FiniteHeightLattice.bot_le α (f (FiniteHeightLattice.bot α)))

/-- Agda: `aᶠ`. -/
def aFix (f : α → α) (hf : Monotone f) : α :=
  (fix f hf).1

/-- Agda: `aᶠ≈faᶠ`. -/
theorem aFix_eq (f : α → α) (hf : Monotone f) :
    aFix f hf = f (aFix f hf) :=
  (fix f hf).2

/-- Agda: `stepPreservesLess` — iteration stays below any fixed point. -/
theorem doStep_le (f : α → α) (hf : Monotone f)
    {b : α} (hb : b = f b) :
    ∀ (g : ℕ) (c : LTSeries α) (hlen : c.length + g = height (α := α) + 1)
      (hle : c.last ≤ f c.last), c.last ≤ b →
      (doStep f hf g c hlen hle : α) ≤ b
  | 0, c, hlen, _ => fun _ => absurd ((fixedHeight (α := α)).bounded c) (by omega)
  | g + 1, c, hlen, hle => fun hcb => by
    rw [doStep]
    split
    · exact hcb
    · exact doStep_le f hf hb g _ _ _
        (by rw [RelSeries.last_snoc]; exact le_of_le_of_eq (hf hcb) hb.symm)

/-- Agda: `aᶠ≼` — `aFix` is below every fixed point of `f`. -/
theorem aFix_le (f : α → α) (hf : Monotone f)
    {a : α} (ha : a = f a) : aFix f hf ≤ a :=
  doStep_le f hf ha _ _ _ _ (by simpa using FiniteHeightLattice.bot_le α a)

end Spa.Fixedpoint
