/-
Copyright (c) 2026 Edwin Fernando. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Edwin Fernando
-/

import Bppl.Lilac.MeasureOnSpace
import Bppl.Lilac.Std

/-! # KRMs on Probability Spaces and Probability Spaces with Finite Footprint

Here we define a Kripke Resource Monoid (KRM), instantiate it to with probability spaces and
instantiate it again with the subspaces of probability spaces with finite footprint.

In this file the decision to use a KRM as is, without further building an Ordered Unital Resource
Algebra. The latter introduces some more structure to the latter to avoid working with `Option`
types. In restrospect after witnessing the messiness of the `Option` type, perhaps the project
should be refactored to using Order Unital Resource Algebras afterall.

## Main Definitions
- `Krm`: Extends `Pcm` (Partial Commutative Monoid) with a `Preorder` and imposes some additional
laws. Note that we add the law `one_le` which is an optional law which turns the `Krm` into a
generator of only Affine separatin logics.
- `PSpace.Krm.instKrm`: `PSpace` (Probability Spaces) form a `Krm`
- `PSp`: A subtype of `PSpace` which additionally enforces `FiniteFootprint` on its
`MeasurableSpace`
- `PSp.instKrm`: Using `closed_subtype_Krm` to show that `PSp` is also a `Krm`

## Main Statements
- `closed_subtype_Krm`: If in a subspace of an existing `Krm`, the binary operation is closed, then
the subspace is a `Krm`

The namespace `Krm` at the end of the file provides various helpers to form an API of
lemmas derived from the Krm laws.
-/

open MeasureTheory MeasurableSpace
-- set_option quotPrecheck false
/-- Partial Commutative Monoid -/
class PcmBase (α : Type*) extends One α where
  binop : α → α → Option α -- partial binary operation

notation a:arg " ⋆ " b:arg => PcmBase.binop a b

class Pcm (α : Type*) extends PcmBase α where
  one_mul (a : α) : 1 ⋆ a = a
  comm (a b : α) : a ⋆ b = b ⋆ a
  assoc (a b c : α) :
    (binop <$> (a ⋆ b) <*> (some c)).join = (binop <$> (some a) <*> (b ⋆ c)).join

class Krm (α : Type*) extends Pcm α, Preorder α where
  one_le : ∀ (a : α), 1 ≤ a
  le_mul_mono : ∀ x x' y y' p' : α,
    x ≤ x' → y ≤ y' →
    (x' ⋆ y' = some p') → ∃ p, (x ⋆ y = some p) ∧ p ≤ p'

namespace PSpace
open Classical PcmBase PSpace

variable {α : Type*} [Inhabited α]
noncomputable instance instPcmBase : PcmBase (PSpace α) where
  binop p q := if h: ∃! r, r =ᵢ p ⊕ᵢ q then some h.choose else none

namespace Pcm

@[grind =]
lemma one_mul (p : PSpace α) : binop 1 p = p := by
  change (if h: ∃! r, r =ᵢ unit ⊕ᵢ p then some h.choose else none) = some p
  have comb : ∃! r, r =ᵢ unit ⊕ᵢ p := existsUnique_of_exists_of_unique
      ⟨p, indepenendentProduct_identity⟩ (uniqueness unit p)
  rw [dif_pos comb]
  simp only [Option.some.injEq]
  rw [ExistsUnique.choose_eq_iff comb]
  exact indepenendentProduct_identity

@[grind =]
lemma comm (p q : PSpace α) : binop p q = binop q p := by
  by_cases h₁: ∃ r, r =ᵢ p ⊕ᵢ q
  · by_cases h₂: ∃ r, r =ᵢ q ⊕ᵢ p
    · -- The nontrivial case where both the independent product exists
      have h₁' : ∃! r, r =ᵢ p ⊕ᵢ q := existsUnique_of_exists_of_unique h₁ (uniqueness p q)
      have h₂' : ∃! r, r =ᵢ q ⊕ᵢ p := existsUnique_of_exists_of_unique h₂ (uniqueness q p)
      dsimp [PcmBase.binop]
      rw [dif_pos h₁', dif_pos h₂',
        Option.some.injEq, ExistsUnique.choose_eq_iff h₁']
      apply independentProduct_comm
      rw [← ExistsUnique.choose_eq_iff h₂']
    · -- The product can't exist one way but not the other
      have : ∃ r, r =ᵢ q ⊕ᵢ p := by
        obtain ⟨r, hr⟩ := h₁
        exact ⟨r, independentProduct_comm hr⟩
      contradiction
  · by_cases h₂: ∃ r, r =ᵢ q ⊕ᵢ p
    · -- The product can't exist one way but not the other
      have : ∃ r, r =ᵢ p ⊕ᵢ q := by
        obtain ⟨r, hr⟩ := h₂
        exact ⟨r, independentProduct_comm hr⟩
      contradiction
    · -- The product doesn't exist
      have h_none₁ : q ⋆ p = none := by
        apply dite_cond_eq_false
        simp_all only [not_exists, existsUnique_false]
      have h_none₂ : p ⋆ q = none := by
        apply dite_cond_eq_false
        simp_all only [not_exists, existsUnique_false]
      rw [h_none₁, h_none₂]

lemma assoc (p q r : PSpace α) :
    (binop <$> (binop p q) <*> (some r)).join = (binop <$> (some p) <*> (binop q r)).join := by
  by_cases h_pq: ∃ pq, pq =ᵢ p ⊕ᵢ q
  · by_cases h_qr: ∃ qr, qr =ᵢ q ⊕ᵢ r
    · have h_pq' : ∃! pq, pq =ᵢ p ⊕ᵢ q := existsUnique_of_exists_of_unique h_pq (uniqueness p q)
      obtain ⟨pq, h_pq⟩ := h_pq'
      by_cases h_pq_r: ∃ s, s =ᵢ pq ⊕ᵢ r
      · have h_qr' : ∃! qr, qr =ᵢ q ⊕ᵢ r := existsUnique_of_exists_of_unique h_qr (uniqueness q r)
        obtain ⟨qr, h_qr⟩ := h_qr'
        by_cases h_p_qr: ∃ s, s =ᵢ p ⊕ᵢ qr
        · -- (1) The "interesting" case where all independent products exist
          have h_pq_r' : ∃! s, s =ᵢ pq ⊕ᵢ r
            := existsUnique_of_exists_of_unique h_pq_r (uniqueness pq r)
          have h_p_qr' : ∃! s, s =ᵢ p ⊕ᵢ qr
            := existsUnique_of_exists_of_unique h_p_qr (uniqueness p qr)
          dsimp [PcmBase.binop]
          rw [dif_pos ⟨pq, h_pq⟩, dif_pos ⟨qr, h_qr⟩]
          dsimp only [Option.map_some, Option.seq_some, Option.join_some]
          have h_pq' : ∃! pq, pq =ᵢ p ⊕ᵢ q := ⟨pq, h_pq⟩
          have h_qr' : ∃! qr, qr =ᵢ q ⊕ᵢ r := ⟨qr, h_qr⟩
          have heq₁ : h_pq'.choose = pq := (ExistsUnique.choose_eq_iff h_pq').2 h_pq.1
          have heq₂ : h_qr'.choose = qr := (ExistsUnique.choose_eq_iff h_qr').2 h_qr.1
          rw [heq₁, heq₂]
          rw [dif_pos h_pq_r', dif_pos h_p_qr', Option.some.injEq]
          rw [ExistsUnique.choose_eq_iff h_pq_r']
          suffices ∃ pq, pq =ᵢ p ⊕ᵢ q ∧ h_p_qr'.choose =ᵢ pq ⊕ᵢ r by
            obtain ⟨pq', h_pq'⟩ := this
            have eq : pq' = pq := uniqueness p q pq' pq h_pq'.1 h_pq.1
            exact eq ▸ h_pq'.2
          apply independentProduct_assoc_right h_qr.1
          exact (Exists.choose_spec h_p_qr').1
        · -- (2) lhs = some rhs = none
          obtain ⟨s, h_s⟩ := h_pq_r
          have : ∃ s, s =ᵢ p ⊕ᵢ qr := by
            obtain ⟨qr', h_qr'⟩ := independentProduct_assoc h_pq.1 h_s
            have eq : qr' = qr := uniqueness q r qr' qr h_qr'.1 h_qr.1
            exact ⟨s, eq ▸ h_qr'.2⟩
          contradiction
      · have h_qr' : ∃! qr, qr =ᵢ q ⊕ᵢ r := existsUnique_of_exists_of_unique h_qr (uniqueness q r)
        obtain ⟨qr, h_qr⟩ := h_qr'
        by_cases h_p_qr : ∃ s, s =ᵢ p ⊕ᵢ qr
        · -- (3) lhs = none rhs = some. dual to (2)
          obtain ⟨s, h_s⟩ := h_p_qr
          have : ∃ s, s =ᵢ pq ⊕ᵢ r := by
            obtain ⟨pq', h_pq'⟩ := independentProduct_assoc_right h_qr.1 h_s
            have eq : pq' = pq := uniqueness p q pq' pq h_pq'.1 h_pq.1
            exact ⟨s, eq ▸ h_pq'.2⟩
          contradiction
        · -- (4) none_left copied from (6) none_right from (8)
          have none_left : (binop <$> p ⋆ q <*> some r).join = none := by
            have some_pq : p ⋆ q = some pq := by
              dsimp [binop]
              rw [dif_pos ⟨pq, h_pq⟩, Option.some.injEq,
                ExistsUnique.choose_eq_iff ⟨pq, h_pq⟩]
              exact h_pq.1
            rw [some_pq]
            dsimp only [Option.map_eq_map, Option.map_some, Option.seq_some, Option.join_some]
            apply dite_cond_eq_false
            simp_all only [not_exists, existsUnique_false]
          have none_right : (binop <$> some p <*> q ⋆ r).join = none := by
            have some_qr : q ⋆ r = some qr := by
              dsimp [binop]
              rw [dif_pos ⟨qr, h_qr⟩, Option.some.injEq,
                ExistsUnique.choose_eq_iff ⟨qr, h_qr⟩]
              exact h_qr.1
            rw [some_qr]
            dsimp only [Option.map_eq_map, Option.map_some, Option.seq_some, Option.join_some]
            apply dite_cond_eq_false
            simp_all only [not_exists, existsUnique_false]
          rw [none_left, none_right]
    · -- rhs = none
      have h_pq' : ∃! pq, pq =ᵢ p ⊕ᵢ q := existsUnique_of_exists_of_unique h_pq (uniqueness p q)
      obtain ⟨pq, h_pq⟩ := h_pq'
      by_cases h_pq_r: ∃ s, s =ᵢ pq ⊕ᵢ r
      · -- (5) lhs = some _    so contradiction
        obtain ⟨s, h_s⟩ := h_pq_r
        have : ∃ qr, qr =ᵢ q ⊕ᵢ r := by
          obtain ⟨qr, h_qr, _⟩ := independentProduct_assoc h_pq.1 h_s
          exact ⟨qr, h_qr⟩
        contradiction
      · -- (6) lhs = none
        have none_left : (binop <$> p ⋆ q <*> some r).join = none := by
          have some_pq : p ⋆ q = some pq := by
            dsimp [binop]
            rw [dif_pos ⟨pq, h_pq⟩, Option.some.injEq,
              ExistsUnique.choose_eq_iff ⟨pq, h_pq⟩]
            exact h_pq.1
          rw [some_pq]
          dsimp only [Option.map_eq_map, Option.map_some, Option.seq_some, Option.join_some]
          apply dite_cond_eq_false
          simp_all only [not_exists, existsUnique_false]
        have none_right : (binop <$> some p <*> q ⋆ r).join = none := by
          have none_qr : q ⋆ r = none := by
            apply dite_cond_eq_false
            simp_all only [not_exists, existsUnique_false]
          rw [none_qr]
          rfl
        rw [none_left, none_right]
  · -- lhs = none
    by_cases h_qr: ∃ qr, qr =ᵢ q ⊕ᵢ r
    · have h_qr' : ∃! qr, qr =ᵢ q ⊕ᵢ r := existsUnique_of_exists_of_unique h_qr (uniqueness q r)
      obtain ⟨qr, h_qr⟩ := h_qr'
      by_cases h_p_qr: ∃ s, s =ᵢ p ⊕ᵢ qr
      · -- (7) rhs = some _     so contradiction
        obtain ⟨s, h_s⟩ := h_p_qr
        have : ∃ pq, pq =ᵢ p ⊕ᵢ q := by
          obtain ⟨pq, h_pq, _⟩ := independentProduct_assoc_right h_qr.1 h_s
          exact ⟨pq, h_pq⟩
        contradiction
      · -- (8) rhs = none
        have none_left : (binop <$> p ⋆ q <*> some r).join = none := by
          have none_pq : p ⋆ q = none := by
            apply dite_cond_eq_false
            simp_all only [not_exists, existsUnique_false]
          rw [none_pq]
          rfl
        have none_right : (binop <$> some p <*> q ⋆ r).join = none := by
          have some_qr : q ⋆ r = some qr := by
            dsimp [binop]
            rw [dif_pos ⟨qr, h_qr⟩, Option.some.injEq,
              ExistsUnique.choose_eq_iff ⟨qr, h_qr⟩]
            exact h_qr.1
          rw [some_qr]
          dsimp only [Option.map_eq_map, Option.map_some, Option.seq_some, Option.join_some]
          apply dite_cond_eq_false
          simp_all only [not_exists, existsUnique_false]
        rw [none_left, none_right]
    · -- (9) rhs = none     copied verbatim none_left from (8) and none_right (6)
      have none_left : (binop <$> p ⋆ q <*> some r).join = none := by
        have none_pq : p ⋆ q = none := by
          apply dite_cond_eq_false
          simp_all only [not_exists, existsUnique_false]
        rw [none_pq]
        rfl
      have none_right : (binop <$> some p <*> q ⋆ r).join = none := by
        have none_qr : q ⋆ r = none := by
          apply dite_cond_eq_false
          simp_all only [not_exists, existsUnique_false]
        rw [none_qr]
        rfl
      rw [none_left, none_right]

noncomputable instance instPcm : Pcm (PSpace α) where
  one_mul := one_mul
  comm := comm
  assoc := assoc

end Pcm

namespace Krm

/- Extract independent product condition from a successful `binop` call.  -/
lemma isIndependentProduct_of_binop_eq_some {p q r : PSpace α}
    (h : p ⋆ q = some r) : r =ᵢ p ⊕ᵢ q := by
  contrapose! h;
  unfold instPcmBase;
  grind +splitImp

/- Construct a successful `binop` result from an independent product proof.  -/
lemma binop_eq_some_of_isIndependentProduct {p q r : PSpace α}
    (h : r =ᵢ p ⊕ᵢ q) : p ⋆ q = some r := by
  unfold instPcmBase;
  have h_unique : ∃! r, r =ᵢ p ⊕ᵢ q := by
    have := @PSpace.uniqueness α;
    exact ⟨r, h, fun r' hr' => this _ _ _ _ hr' h⟩;
  grind

/- If `p' =ᵢ x' ⊕ᵢ y'`, `x ≤ x'`, `y ≤ y'`, then the trim of `p'` to `x.ms.sum y.ms`
is an independent product of `x` and `y`.  -/
lemma trim_isIndependentProduct {x x' y y' p' : PSpace β}
    (x_le : x ≤ x') (y_le : y ≤ y') (h_p' : p' =ᵢ x' ⊕ᵢ y')
    (h_ms : x.1.ms.sum y.1.ms ≤ p'.1.ms) :
    (p'.trim h_ms) =ᵢ x ⊕ᵢ y := by
  constructor;
  · rfl;
  · intro E hE F hF;
    convert h_p'.2 E (x_le.1 _ hE) F (y_le.1 _ hF) using 1;
    · convert MeasureOnSpace.trim_eq h_ms _;
      exact mem_sum_inter x.ms y.ms hE hF;
    · rw [MeasureOnSpace.le_preserves_measure x_le hE, MeasureOnSpace.le_preserves_measure y_le hF]

def le_mul_mono (x x' y y' p' : PSpace α) (x_le : x ≤ x') (y_le : y ≤ y')
    (ex_ge_mul : x' ⋆ y' = some p') : ∃ p, (x ⋆ y = some p) ∧ p ≤ p' := by
  have h_p' : p' =ᵢ x' ⊕ᵢ y' := isIndependentProduct_of_binop_eq_some ex_ge_mul
  have h_ms : x.1.ms.sum y.1.ms ≤ p'.1.ms := by
    rw [h_p'.1]
    exact MeasurableSpace.sum_mono_both x_le.1 y_le.1
  let p := p'.trim h_ms
  have h_p : p =ᵢ x ⊕ᵢ y := trim_isIndependentProduct x_le y_le h_p' h_ms
  exact ⟨p, binop_eq_some_of_isIndependentProduct h_p, PSpace.trim_le h_ms⟩

noncomputable instance instKrm : Krm (PSpace α) where
  one_le _ := PSpace.le_of_isIndependentProduct_left PSpace.indepenendentProduct_identity
  le_mul_mono := le_mul_mono

end Krm
end PSpace

open HC

structure PSp extends (PSpace HC) where
  finite_footprint : ∃ n, FiniteFootprint n ms

/- This proof uses proof irrelevance in lean and shows that if in a subspace of a Krm,
the binary operation is closed, then the subspace is a KRM as well. -/
def closed_subtype_Krm (α : Type*) [Krm α] (β : Type*) [PcmBase β] (f : β → α)
    (f_inj : Function.Injective f)
    (f_one : f 1 = 1)
    (f_binop : ∀ x y : β, (x ⋆ y).map f = (f x) ⋆ (f y)) : Krm β where
  one_mul a := Option.map_injective f_inj <| by
    rw [Option.map_some, f_binop, f_one, Pcm.one_mul]
  comm a b := Option.map_injective f_inj <| by
    rw [f_binop, f_binop, Pcm.comm]
  assoc a b c := by
    apply Option.map_injective f_inj
    convert Pcm.assoc (f a) (f b) (f c) using 1
    · cases h : a ⋆ b <;> simp [← f_binop, h]; rfl
    · cases h : b ⋆ c <;> simp [← f_binop, h]; rfl
  le x y := f x ≤ f y
  le_refl x := le_refl (f x)
  le_trans _ _ _ h₁ h₂ := le_trans h₁ h₂
  one_le a := f_one ▸ Krm.one_le (f a)
  le_mul_mono x x' y y' p' hx hy hp := by
    obtain ⟨q, hq, hqle⟩ := Krm.le_mul_mono (f x) (f x') (f y) (f y') (f p') hx hy
      (by rw [← f_binop, hp]; rfl)
    rw [← f_binop, Option.map_eq_some_iff] at hq
    obtain ⟨p, hp_xy, rfl⟩ := hq
    exact ⟨p, hp_xy, hqle⟩


namespace PSp
open Classical

prefix:max "✓'" => Option.isSome

abbrev coerceOption {α : Type*} {x : Option α} (h : x.isSome) := x.get h

prefix:max "↓" => coerceOption

lemma ff_closed_under_sum (ms₁ ms₂ : MeasurableSpace HC) (hms₁ : ∃ n, FiniteFootprint n ms₁)
    (hms₂ : ∃ n, FiniteFootprint n ms₂) : ∃ n, FiniteFootprint n (ms₁.sum ms₂) := by
  obtain ⟨n₁, ff₁⟩ := hms₁
  obtain ⟨n₂, ff₂⟩ := hms₂
  use max n₁ n₂
  obtain ⟨ms₁', hms₁⟩ := finite_footprint_of_ge (le_max_left n₁ n₂) ff₁
  obtain ⟨ms₂', hms₂⟩ := finite_footprint_of_ge (le_max_right n₁ n₂) ff₂
  use ms₁'.sum ms₂'
  simp_all only [unSplitBi_eq_comap_fst, sum]
  rw [comap_generateFrom, Set.image_union]
  subst hms₁ hms₂
  rfl

/-
The unit PSpace has finite footprint (trivial σ-algebra depends on 0 coordinates).
-/
lemma ff_unit : ∃ n, FiniteFootprint n (PSpace.unit (Ω := HC)).ms := by
  use 0, ⊥
  simp [PSpace.unit, unSplitBi_eq_comap_fst]

noncomputable instance : One PSp where
  one := ⟨PSpace.unit, ff_unit⟩

/-- If `r =ᵢ p ⊕ᵢ q` and both `p` and `q` have finite footprint, then `r` does too. -/
lemma ff_of_isIndependentProduct {p q r : PSpace HC}
    (h : r =ᵢ p ⊕ᵢ q) (hp : ∃ n, FiniteFootprint n p.ms) (hq : ∃ n, FiniteFootprint n q.ms) :
    ∃ n, FiniteFootprint n r.ms := by
  rw [h.1]
  exact ff_closed_under_sum p.ms q.ms hp hq

noncomputable instance instPcmBase : PcmBase (PSp) where
  binop p q := if h: ∃! r, r =ᵢ p.1 ⊕ᵢ q.1 then
    some ⟨h.choose, ff_of_isIndependentProduct h.choose_spec.1 p.2 q.2⟩
  else none

/-- The map `PSp → PSpace HC` given by `Subtype.val` preserves the unit. -/
lemma psp_val_one : (1 : PSp).1 = (1 : PSpace HC) := rfl

/- The map `PSp → PSpace HC` given by `Subtype.val` preserves `binop` values.  -/
lemma psp_val_binop (x y : PSp) : (x ⋆ y).map (·.1) = (x.1 ⋆ y.1) := by
  rw [instPcmBase, PSpace.instPcmBase]
  grind

/-- PSp is a Krm using `closed_subtype_Krm`. -/
noncomputable instance instKrm : Krm PSp := closed_subtype_Krm (PSpace HC) PSp (·.1)
  (by rintro ⟨a, ha⟩ ⟨b, hb⟩ c; simpa only [mk.injEq])
  rfl
  psp_val_binop

@[simp]
lemma le_of_le {x y : PSp} (x_le_y : x ≤ y) : x.ms ≤ y.ms := x_le_y.1


/-- The PSpace-level binop is also defined when the PSp binop is. -/
lemma psp_isSome_val (x y : PSp) (h : ✓'(x ⋆ y)) : ✓'(x.1 ⋆ y.1) := by
  have hv := psp_val_binop x y
  cases hxy : x ⋆ y with
  | none => simp [hxy] at h
  | some v =>
    simp only [hxy, Option.map_some] at hv
    rw [← hv]
    rfl

/- If PSp binop is defined, the underlying PSpace value equals the PSpace-level binop value.  -/
lemma psp_val_get (x y : PSp) (h : ✓'(x ⋆ y)) :
    (↓h).1 = ↓(psp_isSome_val x y h) := by
      convert Option.some_inj.mp _;
      convert psp_val_binop x y using 1;
      · unfold instPcmBase;
        grind;
      · grind

@[simp]
lemma sum_ms_of_prod {x y : PSp} (xy : ✓'(x ⋆ y)) : (↓xy).ms = x.ms.sum y.ms := by
  have hval : x.1 ⋆ y.1 = some (↓xy).1 := by
    rw [psp_val_get x y xy, Option.some_get]
  exact (PSpace.Krm.isIndependentProduct_of_binop_eq_some hval).1


open MeasureTheory in
noncomputable def PSpace.mk''_psp {ms ms' : MeasurableSpace HC} (μ : @ProbabilityMeasure HC ms)
    (ms'_le_ms : ms' ≤ ms) (ff : ∃ n, FiniteFootprint n ms') : PSp :=
  ⟨⟨⟨ms', μ.1.trim ms'_le_ms⟩, Measure.trim_preserves_prob ms' ms ms'_le_ms μ.2⟩, ff⟩

end PSp

/- Helper functions which re-interpret the Krm laws in terms of the `Option.is_some` and
`Option.get` API, introduced using the notation `✓'` and `↓`. -/
namespace Krm

variable {α : Type*} [krm_α : Krm α]
open PcmBase

/-- If `a ⋆ b = b ⋆ a`, then `Option.get` yields equal values. -/
lemma get_comm (a b : α) (hab : ✓'(a ⋆ b)) (hba : ✓'(b ⋆ a)) :
    ↓hab = ↓hba := by
  have h : a ⋆ b = b ⋆ a := Pcm.comm a b
  set x := (a ⋆ b).get hab
  set y := (b ⋆ a).get hba
  have hax : a ⋆ b = some x := Option.some_get hab ▸ rfl
  have hay : b ⋆ a = some y := Option.some_get hba ▸ rfl
  exact Option.some_injective _ (hax.symm.trans (h.trans hay))

lemma right {a b c : α}
    {hab : ✓'(a ⋆ b)} (habc : ✓'(↓hab ⋆ c)) :
    ✓'(b ⋆ c) := by
      have assoc_lhs: ((krm_α.binop) <$> (a ⋆ b) <*> (some c)).join = (↓hab ⋆ c) := by
        conv_lhs => rw [← Option.some_get hab]
        rfl
      have assoc := assoc_lhs ▸ krm_α.assoc a b c
      rw [assoc] at habc
      by_cases h: ✓'(b ⋆ c)
      · exact h
      · exfalso
        have : b ⋆ c = none := by exact Option.not_isSome_iff_eq_none.mp h
        rw [this] at habc
        contradiction

lemma left {a b c : α}
    {hbc : ✓'(b ⋆ c)} (habc : ✓'(a ⋆ ↓hbc)) :
    ✓'(a ⋆ b) := by
      have assoc_rhs: (binop <$> (some a) <*> (b ⋆ c)).join = (a ⋆ ↓hbc) := by
        conv_lhs => rw [← Option.some_get hbc]
        rfl
      have assoc := assoc_rhs ▸ krm_α.assoc a b c
      rw [← assoc] at habc
      by_cases h: ✓'(a ⋆ b)
      · exact h
      · exfalso
        have : a ⋆ b = none := by exact Option.not_isSome_iff_eq_none.mp h
        rw [this] at habc
        contradiction

/- Given `↓hab ⋆ c` is defined, so is `a ⋆ ↓hbc`.  -/
lemma assoc_right {a b c : α} {hab : ✓'(a ⋆ b)} (habc : ✓'(↓hab ⋆ c)) :
  ✓'(a ⋆ ↓(right habc)) := by
    have hassoc := ‹Krm α›.assoc a b c
    obtain ⟨x, hx⟩ := Option.isSome_iff_exists.mp hab
    obtain ⟨y, hy⟩ := Option.isSome_iff_exists.mp habc
    obtain ⟨z, hz⟩ := Option.isSome_iff_exists.mp <| right habc
    simp_all

/- Given `a ⋆ ↓hbc` is defined, so is `↓hab ⋆ c`.  -/
lemma assoc_left {a b c : α} {hbc : ✓'(b ⋆ c)} (habc : ✓'(a ⋆ ↓hbc)) :
  ✓'(↓(left habc) ⋆ c) := by
    have hassoc := ‹Krm α›.assoc a b c
    obtain ⟨x, hx⟩ := Option.isSome_iff_exists.mp <| left habc
    obtain ⟨y, hy⟩ := Option.isSome_iff_exists.mp habc
    obtain ⟨z, hz⟩ := Option.isSome_iff_exists.mp hbc
    simp_all

/-- The result of reassociating `(a ⋆ b) ⋆ c` to `a ⋆ (b ⋆ c)` via `assoc_left`
yields the same combined element. -/
@[simp]
lemma assoc_left_eq {a b c : α} {hbc : ✓'(b ⋆ c)} (habc : ✓'(a ⋆ ↓hbc)) :
    ↓(assoc_left habc) = ↓habc := by
  apply Option.some_injective
  rw [Option.some_get, Option.some_get]
  have hassoc := krm_α.assoc a b c
  conv at hassoc =>
    lhs; rw [show a ⋆ b = some (↓(left habc)) from (Option.some_get _).symm]
  conv at hassoc =>
    rhs; rw [show b ⋆ c = some (↓hbc) from (Option.some_get _).symm]
  convert hassoc using 1

/-- The result of reassociating `a ⋆ (b ⋆ c)` to `(a ⋆ b) ⋆ c` via `assoc_right`
yields the same combined element. -/
@[simp]
lemma assoc_right_eq {a b c : α} {hab : ✓'(a ⋆ b)} (habc : ✓'(↓hab ⋆ c)) :
    ↓(assoc_right habc) = ↓habc := by
  apply Option.some_injective
  rw [Option.some_get, Option.some_get]
  have hassoc := krm_α.assoc a b c
  conv at hassoc =>
    lhs; rw [show a ⋆ b = some (↓hab) from (Option.some_get _).symm]
  conv at hassoc =>
    rhs; rw [show b ⋆ c = some (↓(right habc)) from (Option.some_get _).symm]
  convert hassoc.symm using 1

lemma le_mul_mono' (x₁ x₂ y₁ y₂ : α)
    (lex : x₁ ≤ x₂) (ley : y₁ ≤ y₂)
    (xy₂ : ✓'(x₂ ⋆ y₂)) : ∃ xy₁ : ✓'(x₁ ⋆ y₁), ↓xy₁ ≤ ↓xy₂ := by
  obtain ⟨p, hp, hple⟩ := krm_α.le_mul_mono x₁ x₂ y₁ y₂ (↓xy₂) lex ley (Option.some_get xy₂).symm
  exact ⟨hp ▸ rfl, by simp only [hp, Option.get_some]; exact hple⟩

lemma le_mul_mono_left' {x₁ x₂ y : α} (lex : x₁ ≤ x₂) (xy₂ : ✓'(x₂ ⋆ y)) :
  ∃ xy₁ : ✓'(x₁ ⋆ y), ↓xy₁ ≤ ↓xy₂ := le_mul_mono' x₁ x₂ y y lex (le_refl y) xy₂

lemma le_mul_mono_right' {x y₁ y₂ : α} (ley : y₁ ≤ y₂) (xy₂ : ✓'(x ⋆ y₂)) :
  ∃ xy₁ : ✓'(x ⋆ y₁), ↓xy₁ ≤ ↓xy₂ := le_mul_mono' x x y₁ y₂ (le_refl x) ley xy₂

end Krm
