/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
module

prelude

import all Init.Data.Option.Instances
public import Init.Data.Option.Attach

public section

namespace Option

@[simp, grind =] theorem bindM_none [Pure m] (f : α → m (Option β)) : none.bindM f = pure none := rfl
@[simp, grind =] theorem bindM_some [Pure m] (a) (f : α → m (Option β)) : (some a).bindM f = f a := by
  simp [Option.bindM]

-- We simplify `Option.forM` to `forM`.
@[simp] theorem forM_eq_forM [Monad m] : @Option.forM m α _ = forM := rfl

@[simp, grind =] theorem forM_none [Monad m] (f : α → m PUnit) :
    forM none f = pure .unit := rfl

@[simp, grind =] theorem forM_some [Monad m] (f : α → m PUnit) (a : α) :
    forM (some a) f = f a := rfl

@[simp, grind =] theorem forM_map [Monad m] [LawfulMonad m] (o : Option α) (g : α → β) (f : β → m PUnit) :
    forM (o.map g) f = forM o (fun a => f (g a)) := by
  cases o <;> simp

theorem forM_join [Monad m] [LawfulMonad m] (o : Option (Option α)) (f : α → m PUnit) :
    forM o.join f = forM o (forM · f) := by
  cases o <;> simp

@[simp, grind =] theorem forIn'_none [Monad m] (b : β) (f : (a : α) → a ∈ none → β → m (ForInStep β)) :
    forIn' none b f = pure b := by
  rfl

@[simp, grind =] theorem forIn'_some [Monad m] [LawfulMonad m] (a : α) (b : β) (f : (a' : α) → a' ∈ some a → β → m (ForInStep β)) :
    forIn' (some a) b f = bind (f a rfl b) (fun r => pure (ForInStep.value r)) := by
  simp only [forIn', bind_pure_comp]
  rw [map_eq_pure_bind]
  congr
  funext x
  split <;> simp

@[simp, grind =] theorem forIn_none [Monad m] (b : β) (f : α → β → m (ForInStep β)) :
    forIn none b f = pure b := by
  rfl

@[simp, grind =] theorem forIn_some [Monad m] [LawfulMonad m] (a : α) (b : β) (f : α → β → m (ForInStep β)) :
    forIn (some a) b f = bind (f a b) (fun r => pure (ForInStep.value r)) := by
  simp only [forIn, forIn', bind_pure_comp]
  rw [map_eq_pure_bind]
  congr
  funext x
  split <;> simp

@[congr] theorem forIn'_congr [Monad m] [LawfulMonad m] {as bs : Option α} (w : as = bs)
    {b b' : β} (hb : b = b')
    {f : (a' : α) → a' ∈ as → β → m (ForInStep β)}
    {g : (a' : α) → a' ∈ bs → β → m (ForInStep β)}
    (h : ∀ a m b, f a (by simpa [w] using m) b = g a m b) :
    forIn' as b f = forIn' bs b' g := by
  cases as <;> cases bs
  · simp [hb]
  · simp at w
  · simp at w
  · simp only [some.injEq] at w
    subst w
    simp [hb, h]

theorem forIn'_eq_pelim [Monad m] [LawfulMonad m]
    (o : Option α) (f : (a : α) → a ∈ o → β → m (ForInStep β)) (b : β) :
    forIn' o b f =
      o.pelim (pure b) (fun a h => ForInStep.value <$> f a h b) := by
  cases o <;> simp

@[simp] theorem forIn'_yield_eq_pelim [Monad m] [LawfulMonad m] (o : Option α)
    (f : (a : α) → a ∈ o → β → m γ) (g : (a : α) → a ∈ o → β → γ → β) (b : β) :
    forIn' o b (fun a m b => (fun c => .yield (g a m b c)) <$> f a m b) =
      o.pelim (pure b) (fun a h => g a h b <$> f a h b) := by
  cases o <;> simp

@[simp] theorem forIn'_pure_yield_eq_pelim [Monad m] [LawfulMonad m]
    (o : Option α) (f : (a : α) → a ∈ o → β → β) (b : β) :
    forIn' o b (fun a m b => pure (.yield (f a m b))) =
      pure (f := m) (o.pelim b (fun a h => f a h b)) := by
  cases o <;> simp

@[simp] theorem idRun_forIn'_yield_eq_pelim
    (o : Option α) (f : (a : α) → a ∈ o → β → Id β) (b : β) :
    (forIn' o b (fun a m b => .yield <$> f a m b)).run =
      o.pelim b (fun a h => f a h b |>.run) :=
  forIn'_pure_yield_eq_pelim _ _ _

@[deprecated idRun_forIn'_yield_eq_pelim (since := "2025-05-21")]
theorem forIn'_id_yield_eq_pelim
    (o : Option α) (f : (a : α) → a ∈ o → β → β) (b : β) :
    forIn' (m := Id) o b (fun a m b => .yield (f a m b)) =
      o.pelim b (fun a h => f a h b) :=
  forIn'_pure_yield_eq_pelim _ _ _

@[simp, grind =] theorem forIn'_map [Monad m] [LawfulMonad m]
    (o : Option α) (g : α → β) (f : (b : β) → b ∈ o.map g → γ → m (ForInStep γ)) :
    forIn' (o.map g) init f = forIn' o init fun a h y => f (g a) (mem_map_of_mem g h) y := by
  cases o <;> simp

theorem forIn'_join [Monad m] [LawfulMonad m] (b : β) (o : Option (Option α))
    (f : (a : α) → a ∈ o.join → β → m (ForInStep β)) :
    forIn' o.join b f = forIn' o b (fun o' ho' b => ForInStep.yield <$> forIn' o' b (fun a ha b' => f a (by simp_all) b')) := by
  cases o with
  | none => simp
  | some a => simpa using forIn'_congr rfl rfl (by simp)

theorem forIn_eq_elim [Monad m] [LawfulMonad m]
    (o : Option α) (f : (a : α) → β → m (ForInStep β)) (b : β) :
    forIn o b f =
      o.elim (pure b) (fun a => ForInStep.value <$> f a b) := by
  cases o <;> simp

@[simp] theorem forIn_yield_eq_elim [Monad m] [LawfulMonad m] (o : Option α)
    (f : (a : α) → β → m γ) (g : (a : α) → β → γ → β) (b : β) :
    forIn o b (fun a b => (fun c => .yield (g a b c)) <$> f a b) =
      o.elim (pure b) (fun a => g a b <$> f a b) := by
  cases o <;> simp

@[simp] theorem forIn_pure_yield_eq_elim [Monad m] [LawfulMonad m]
    (o : Option α) (f : (a : α) → β → β) (b : β) :
    forIn o b (fun a b => pure (.yield (f a b))) =
      pure (f := m) (o.elim b (fun a => f a b)) := by
  cases o <;> simp

@[simp] theorem idRun_forIn_yield_eq_elim
    (o : Option α) (f : (a : α) → β → Id β) (b : β) :
    (forIn o b (fun a b => .yield <$> f a b)).run =
      o.elim b (fun a => f a b |>.run) :=
  forIn_pure_yield_eq_elim _ _ _

@[deprecated idRun_forIn_yield_eq_elim (since := "2025-05-21")]
theorem forIn_id_yield_eq_elim
    (o : Option α) (f : (a : α) → β → β) (b : β) :
    forIn (m := Id) o b (fun a b => .yield (f a b)) =
      o.elim b (fun a => f a b) :=
  forIn_pure_yield_eq_elim _ _ _

@[simp, grind =] theorem forIn_map [Monad m] [LawfulMonad m]
    (o : Option α) (g : α → β) (f : β → γ → m (ForInStep γ)) :
    forIn (o.map g) init f = forIn o init fun a y => f (g a) y := by
  cases o <;> simp

theorem forIn_join [Monad m] [LawfulMonad m]
    (o : Option (Option α)) (f : α → β → m (ForInStep β)) :
    forIn o.join init f = forIn o init (fun o' b => ForInStep.yield <$> forIn o' b f) := by
  cases o <;> simp

@[simp, grind =] theorem elimM_pure [Monad m] [LawfulMonad m] (x : Option α) (y : m β) (z : α → m β) :
    Option.elimM (pure x : m (Option α)) y z = x.elim y z := by
  simp [Option.elimM]

@[simp, grind =] theorem elimM_bind [Monad m] [LawfulMonad m] (x : m α) (f : α → m (Option β))
    (y : m γ) (z : β → m γ) : Option.elimM (x >>= f) y z = (do Option.elimM (f (← x)) y z) := by
  simp [Option.elimM]

@[simp, grind =] theorem elimM_map [Monad m] [LawfulMonad m] (x : m α) (f : α → Option β)
    (y : m γ) (z : β → m γ) : Option.elimM (f <$> x) y z = (do Option.elim (f (← x)) y z) := by
  simp [Option.elimM]

@[simp, grind =] theorem tryCatch_eq_or (o : Option α) (alternative : Unit → Option α) :
    tryCatch o alternative = o.or (alternative ()) := by cases o <;> rfl

@[simp, grind =] theorem throw_eq_none : throw () = (none : Option α) := rfl

@[simp, grind =] theorem filterM_none [Applicative m] (p : α → m Bool) :
    none.filterM p = pure none := rfl
@[grind =] theorem filterM_some [Applicative m] (p : α → m Bool) (a : α) :
    (some a).filterM p = (fun b => if b then some a else none) <$> p a := rfl

theorem sequence_join [Applicative m] [LawfulApplicative m] {o : Option (Option (m α))} :
    o.join.sequence = join <$> sequence (o.map sequence) := by
  cases o <;> simp

theorem bindM_join [Pure m] {f : α → m (Option β)} {o : Option (Option α)} :
    o.join.bindM f = o.bindM (·.bindM f) := by
  cases o <;> simp

theorem mapM_join [Applicative m] [LawfulApplicative m] {f : α → m β} {o : Option (Option α)} :
    o.join.mapM f = join <$> o.mapM (Option.mapM f) := by
  cases o <;> simp

theorem mapM_guard [Applicative m] {x : α} {p : α → Bool} {f : α → m β} :
    (guard p x).mapM f = if p x then some <$> f x else pure none := by
  simp only [guard_eq_ite]
  split <;> simp

end Option
