theorem all_eq_not_any_not (l : List α) (p : α → Bool) :
    l.all p = !l.any fun x => binderNameHint x p (!p x)
  := List.all_eq_not_any_not

/--
error: Failed: `fail` tactic was invoked
names : List String
⊢ (!names.any fun name => !"Waldo".isPrefixOf name) = true
-/
#guard_msgs in
example (names : List String) : names.all (fun name => "Waldo".isPrefixOf name) = true := by
  rw [all_eq_not_any_not]
  fail


/--
error: Failed: `fail` tactic was invoked
names : List String
⊢ (names.any fun name => !"Waldo".isPrefixOf name) = false
-/
#guard_msgs in
example (names : List String) : names.all (fun name => "Waldo".isPrefixOf name) = true := by
  simp [all_eq_not_any_not, -List.any_eq_false]
  fail


def List.myAll (p : α → Bool) (xs : List α) : Bool := !(xs.any fun x => !p x)

theorem myAll_eq_not_any_not (l : List α) (p : α → Bool) :
    l.myAll p = !l.any fun x => binderNameHint x p (!p x)
  := rfl

/--
error: Failed: `fail` tactic was invoked
names : List String
⊢ (!names.any fun name => !"Waldo".isPrefixOf name) = true
-/
#guard_msgs in
example (names : List String) : names.myAll (fun name => "Waldo".isPrefixOf name) = true := by
  dsimp [myAll_eq_not_any_not]
  fail

-- Check if we can pick the second binder by providing arguments to `f` in `binderNameHint`
-- (It will beta-reduce it)


-- Why this not in standard lib (maybe with the more complex form of recognizing lambdas that
-- ignore arguments?)
@[simp]
theorem List.mapIdx_eq_map (l : List α) (f : α → β) : (l.mapIdx fun _ x => f x) = l.map f := by
  induction l <;> simp_all

set_option linter.unusedVariables false in
theorem zipWith_eq_map_idx_zipWith (l1 : List α) (l2 : List β) (f : α → β → γ) :
    List.zipWith f l1 l2 = (List.zip l1 l2).mapIdx
      (fun i ⟨a, b⟩ => binderNameHint a f <| binderNameHint b (f a) <| f a b)
  := by simp [List.zip_eq_zipWith, List.map_zipWith]

/--
error: Failed: `fail` tactic was invoked
l1 l2 : List Nat
⊢ (List.mapIdx
        (fun i x =>
          match x with
          | (x, y) => x + y)
        (l1.zip l2)).isEmpty =
    true
-/
#guard_msgs in
example (l1 l2 : List Nat) :
  (List.zipWith  (fun x y => x + y) l1 l2).isEmpty := by
  rw [zipWith_eq_map_idx_zipWith]
  fail

-- For now, binder name hints do not work in other tactics, like `apply`
-- (but at least `simp` or `dsimp` removes them)

theorem myAll_eq_not_any_not_iff {l : List α} {p : α → Bool} :
    l.myAll p ↔ !l.any fun x => binderNameHint x p (!p x)
  := by simp [myAll_eq_not_any_not]

/--
error: unsolved goals
names : List String
⊢ (!names.any fun x => !"Waldo".isPrefixOf x) = true
---
trace: names : List String
⊢ (!names.any fun x => binderNameHint x (fun name => "Waldo".isPrefixOf name) !"Waldo".isPrefixOf x) = true
-/
#guard_msgs in
example (names : List String) : names.myAll (fun name => "Waldo".isPrefixOf name) = true := by
  apply myAll_eq_not_any_not_iff.mpr
  trace_state
  dsimp
  done
