/-
Port of `Lattice.agda`.

Most of the Agda module is *lifted* into mathlib, since we now work with
propositional equality instead of a setoid:

  IsSemilattice A _≈_ _⊔_   ↦  SemilatticeSup α
  IsLattice A _≈_ _⊔_ _⊓_   ↦  Lattice α
  _≼_ (a ⊔ b ≈ b)           ↦  a ≤ b           (bridge: `sup_eq_right`)
  _≺_                       ↦  a < b
  Monotonic                 ↦  Monotone
  ⊔-assoc/⊔-comm/⊔-idemp    ↦  sup_assoc/sup_comm/sup_idem
  absorb-⊔-⊓/absorb-⊓-⊔     ↦  sup_inf_self/inf_sup_self
  ≼-refl/≼-trans/≼-antisym  ↦  le_refl/le_trans/le_antisymm
  x≼x⊔y                     ↦  le_sup_left
  ⊔-Monotonicˡ/ʳ            ↦  sup_le_sup_left/sup_le_sup_right
  id-Mono/const-Mono        ↦  monotone_id/monotone_const
  IsDecidable               ↦  DecidableEq (kept only where computation needs it)
  Chain (Chain.agda)        ↦  LTSeries (chains of `<`); concat ↦ RelSeries.smash
  ChainMapping.Chain-map    ↦  LTSeries.map (Monotone + Injective ⇒ StrictMono)

What remains custom is exactly what mathlib does not have:
  * monotonicity of folds over pairwise-related lists (foldr-Mono & friends),
  * the fixed-height machinery (Chain.Height ↦ FixedHeight, Bounded),
  * the proof that the bottom of the longest chain is a least element (⊥≼).
-/
import Mathlib.Order.Lattice
import Mathlib.Order.RelSeries

namespace Spa

/-! ### Monotonicity helpers (Lattice.agda, `Monotonic₂` and fold lemmas) -/

/-- Agda: `Monotonic₂` (a pair of one-sided monotonicity proofs). -/
def Monotone₂ {α β γ : Type*} [Preorder α] [Preorder β] [Preorder γ]
    (f : α → β → γ) : Prop :=
  (∀ b, Monotone fun a => f a b) ∧ (∀ a, Monotone (f a))

section Folds

variable {α β : Type*} [Preorder α] [Preorder β]

/-- Agda: `foldr-Mono`. `Pairwise _≼₁_` becomes `List.Forall₂ (· ≤ ·)`. -/
theorem foldr_mono {l₁ l₂ : List α} (f : α → β → β) {b₁ b₂ : β}
    (hl : List.Forall₂ (· ≤ ·) l₁ l₂) (hb : b₁ ≤ b₂)
    (hf₁ : ∀ b, Monotone fun a => f a b) (hf₂ : ∀ a, Monotone (f a)) :
    l₁.foldr f b₁ ≤ l₂.foldr f b₂ := by
  induction hl with
  | nil => exact hb
  | cons hxy _ ih =>
    exact le_trans (hf₁ _ hxy) (hf₂ _ ih)

/-- Agda: `foldl-Mono`. -/
theorem foldl_mono {l₁ l₂ : List α} (f : β → α → β) {b₁ b₂ : β}
    (hl : List.Forall₂ (· ≤ ·) l₁ l₂) (hb : b₁ ≤ b₂)
    (hf₁ : ∀ a, Monotone fun b => f b a) (hf₂ : ∀ b, Monotone (f b)) :
    l₁.foldl f b₁ ≤ l₂.foldl f b₂ := by
  induction hl generalizing b₁ b₂ with
  | nil => exact hb
  | cons hxy _ ih =>
    exact ih (le_trans (hf₁ _ hb) (hf₂ _ hxy))

omit [Preorder α] in
/-- Agda: `foldr-Mono'` (fixed list, varying accumulator). -/
theorem foldr_mono' (l : List α) (f : α → β → β)
    (hf : ∀ a, Monotone (f a)) : Monotone fun b => l.foldr f b := by
  intro b₁ b₂ hb
  induction l with
  | nil => exact hb
  | cons x xs ih => exact hf x ih

omit [Preorder α] in
/-- Agda: `foldl-Mono'`. -/
theorem foldl_mono' (l : List α) (f : β → α → β)
    (hf : ∀ a, Monotone fun b => f b a) : Monotone fun b => l.foldl f b := by
  intro b₁ b₂ hb
  induction l generalizing b₁ b₂ with
  | nil => exact hb
  | cons x xs ih => exact ih (hf x hb)

end Folds

/-! ### Fixed height (Chain.agda `Bounded`/`Height`, Lattice.agda `FixedHeight`) -/

/-- Agda: `Chain.Bounded`. Every `<`-chain has length at most `n`. -/
def BoundedChains (α : Type*) [Preorder α] (n : ℕ) : Prop :=
  ∀ c : LTSeries α, c.length ≤ n

/-- Agda: `Chain.Height` (with `FixedHeight h = Height h` from Lattice.agda).
A longest chain runs from `⊥` to `⊤` and has length exactly `height`;
no chain is longer. -/
structure FixedHeight (α : Type*) [Preorder α] (height : ℕ) where
  bot : α
  top : α
  longestChain : LTSeries α
  head_longestChain : longestChain.head = bot
  last_longestChain : longestChain.last = top
  length_longestChain : longestChain.length = height
  bounded : BoundedChains α height

/-- Agda: `Chain.Bounded-suc-n` (a bounded order admits no chain one longer). -/
theorem BoundedChains.no_longer {α : Type*} [Preorder α] {n : ℕ}
    (h : BoundedChains α n) (c : LTSeries α) : c.length ≠ n + 1 :=
  fun hc => absurd (h c) (by omega)

/-- Re-index a `FixedHeight` along an equality of heights (used where Agda
just rewrites with arithmetic identities). -/
def FixedHeight.cast {α : Type*} [Preorder α] {m n : ℕ} (h : m = n)
    (fh : FixedHeight α m) : FixedHeight α n where
  bot := fh.bot
  top := fh.top
  longestChain := fh.longestChain
  head_longestChain := fh.head_longestChain
  last_longestChain := fh.last_longestChain
  length_longestChain := h ▸ fh.length_longestChain
  bounded := h ▸ fh.bounded

@[simp] theorem FixedHeight.cast_bot {α : Type*} [Preorder α] {m n : ℕ}
    (h : m = n) (fh : FixedHeight α m) : (fh.cast h).bot = fh.bot := rfl

/-- Agda: `IsFiniteHeightLattice` / `FiniteHeightLattice` (bundled). -/
class FiniteHeightLattice (α : Type*) [Lattice α] where
  height : ℕ
  fixedHeight : FixedHeight α height

namespace FixedHeight

variable {α : Type*} [Lattice α] {h : ℕ}

/-- Agda: `Known-⊥`. -/
def KnownBot (fh : FixedHeight α h) : Prop := ∀ a : α, fh.bot ≤ a

/-- Agda: `Known-⊤`. -/
def KnownTop (fh : FixedHeight α h) : Prop := ∀ a : α, a ≤ fh.top

/-- Agda: `⊥≼` — the bottom of the longest chain is a least element.
Same proof: if `⊥ ⊓ a ≠ ⊥` then `⊥ ⊓ a < ⊥` prepends to the longest chain,
contradicting boundedness. (The decidability hypothesis of the Agda proof is
not needed classically.) -/
theorem bot_le (fh : FixedHeight α h) : fh.KnownBot := by
  intro a
  by_cases heq : fh.bot ⊓ a = fh.bot
  · exact inf_eq_left.mp heq
  · exfalso
    have hlt : fh.bot ⊓ a < fh.bot :=
      lt_of_le_of_ne inf_le_left heq
    exact fh.bounded.no_longer
      (fh.longestChain.cons (fh.bot ⊓ a) (fh.head_longestChain ▸ hlt))
      (by simp [RelSeries.cons, fh.length_longestChain])

end FixedHeight

end Spa
