class Fact (p : Prop) :=
(h : p)

class Ring (α : Type) :=
(one : α) -- dummy implementation

instance ringHasOne {α} [Ring α] : HasOne α :=
⟨Ring.one α⟩ -- dummy implementation

instance ringHasAdd {α} [Ring α] : HasAdd α :=
⟨fun a b => a⟩ -- dummy implementation

instance ringHasMul {α} [Ring α] : HasMul α :=
⟨fun a b => a⟩ -- dummy implementation

class Field (α : Type) extends Ring α :=
(otherStuff : Unit := ()) -- dummy implementation

instance fieldHasDiv {α} [Field α] : HasDiv α :=
⟨fun a b => a⟩ -- dummy implementation

def IsPrime (n : Nat) : Prop :=
True  -- dummy implementation

structure Zmod (n : Nat) :=
(dummy : Unit := ()) -- dummy implementation

instance zmodIsRing {n : Nat} : Ring (Zmod n) :=
{ one := {} }

/- After the instance above, Zmod already has `+`, `*` notations, but not `/`  -/

set_option pp.implicit true
set_option pp.notation false

#check fun (a b : Zmod 10) => a * b -- works

#check fun (a b : Zmod 10) => a / b -- fails

instance zmodIsField {n : Nat} [Fact (IsPrime n)] : Field (Zmod n) :=
{}

#check fun (a b : Zmod 3) => a / b -- fails because local instance [Fact (IsPrime 3)] is not available

#check fun (a b : Zmod 3) (h : Fact (IsPrime 3)) => a / b -- works

axiom foo {n : Nat} (h : Fact (IsPrime n)) (a : Zmod n) : a / a = 1 -- We need hypothesis `h` to be able to write `a/a`

#check fun {n : Nat} (h : IsPrime n) (a : Zmod n) => foo ⟨h⟩ a -- need to use ⟨...⟩ to convert `IsPrime n` into `Fact (IsPrime n)`

-- We can add a coercion from `p` to `Fact p` to minimize the amount of manual wrapping
instance toFact (p : Prop) : HasCoeT p (Fact p) :=
⟨fun h => ⟨h⟩⟩

#check fun {n : Nat} (h : IsPrime n) (a : Zmod n) => @foo n h a -- coercion helps. I needed to use `@` due to a Lean3 issue that is being fixed in Lean4

/- We support cycles in the new TC. So, we can also define -/
instance ofFact (p : Prop) : HasCoeT (Fact p) p :=
⟨fun h => h.1⟩

/- So, the coercions make it easy to wrap/unwrap facts -/
