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

public import Mathlib

/-!
# Path-level reflection identity for ±1 walks (André 1887, algebraic core)

For paths `ω : Fin n → Bool` (each entry: `+1` if `true`, `−1` if `false`),
**reflection after time `τ`** is the path that agrees with `ω` strictly
before time `τ` and *negates* every step from time `τ` onwards.

The algebraic core of André's reflection principle is the position identity

  **For any `τ ≤ k`,**
    `walkPos (reflectAfter τ ω) k = 2 · walkPos ω τ − walkPos ω k`.

Equivalently: the trajectory after time `τ` of the reflected path is the
mirror image of the original trajectory through the horizontal line at
height `walkPos ω τ`.

Combined with a hitting-time argument (the first `τ` with `walkPos ω τ = a`
when `ω` reaches level `a`), this identity yields the bijection between
*paths that touch level `a` and end at `b ≤ a`* and *paths that end at
`2a − b`*. That bijection is the counting backbone of barrier-option
pricing in the binomial tree.

This file formalises **both halves**: the algebraic core (the
path-position identity and involution) *and* the hitting-time bijection
itself (`reflectionPrincipleEquiv`, `reflectionPrincipleEquiv_below`), with
the discrete IVT (`HitsLevel_of_walkPos_endpoint_ge`) discharging the
redundant hitting condition on the reflected side. The counting form is
wired to the corpus as `mf-reflection-principle-counting`.

## Why this is more than algebra

The identity is non-trivial because reflection *changes* the path at every
position from `τ` onwards. The proof requires splitting each path sum into
a prefix (`< τ`) — preserved by reflection — and a suffix (`τ ≤ · < k`) —
negated step-by-step by reflection. The prefix contributes `walkPos ω τ`
to both sides, and the suffix-sign-flip is what produces the
`2 · walkPos ω τ − walkPos ω k` form.

## Results

* `walkStep`: step value (`+1`/`−1` from a Boolean).
* `walkPos`: position of the walk at time `k`.
* `reflectAfter`: flip every step at index `≥ τ`.
* `reflectAfter_involutive`: reflection at fixed `τ` is its own inverse.
* `walkPos_reflectAfter_le`: positions at `k ≤ τ` are unchanged.
* `walkPos_reflectAfter_ge`: positions at `k ≥ τ` reflect through
  `walkPos ω τ`.
* `walkPos_reflectAfter_endpoint`: endpoint statement: if the original
  hits `a` at `τ` and ends at `b`, the reflected ends at `2a − b`.
* `HitsLevel` / `firstHit` (+ `firstHit?`): the barrier-hit predicate and
  the first hitting time.
* `reflectAtFirstHit` (+ involutivity): reflection at the first hit.
* `HitsLevel_of_walkPos_endpoint_ge`: discrete IVT — a walk ending at or
  above a level `a ≥ 0` has hit `a`.
* `reflectionPrincipleEquiv`: paths hitting `a` ending at `b` ≃ paths
  hitting `a` ending at `2a − b` (André's bijection, an involution).
* `reflectionPrincipleEquiv_below`: for `0 < a`, `b ≤ a` — paths hitting
  `a` ending at `b` ≃ *all* paths ending at `2a − b` (hitting condition on
  the right discharged by the IVT): the counting form used in barrier
  pricing.
-/

@[expose] public section

namespace MathFin

variable {n : ℕ}

/-- Step value: `+1` if `true` (up), `−1` if `false` (down). -/
def walkStep (b : Bool) : ℤ := if b then 1 else -1

/-- **Step-value negation under boolean flip**: `walkStep !b = -walkStep b`. -/
lemma walkStep_not (b : Bool) : walkStep (!b) = -walkStep b := by
  unfold walkStep
  cases b <;> simp

/-- Position of the walk at time `k`: sum of the first `k` steps.

For `k = 0` the sum is empty and the position is `0`. For `k ≥ n` all
steps are included. -/
def walkPos (ω : Fin n → Bool) (k : ℕ) : ℤ :=
  ∑ i ∈ (Finset.univ : Finset (Fin n)).filter (fun (i : Fin n) => i.val < k),
    walkStep (ω i)

@[simp]
lemma walkPos_zero (ω : Fin n → Bool) : walkPos ω 0 = 0 := by
  unfold walkPos
  simp

/-- **Reflection after time `τ`**: flip every step at index `≥ τ`. -/
def reflectAfter (τ : ℕ) (ω : Fin n → Bool) : Fin n → Bool :=
  fun (i : Fin n) => if i.val < τ then ω i else !(ω i)

/-- **Reflection at fixed `τ` is involutive**: applying it twice recovers the
original path. -/
lemma reflectAfter_involutive (τ : ℕ) :
    Function.Involutive (reflectAfter τ : (Fin n → Bool) → Fin n → Bool) := by
  intro ω
  funext i
  unfold reflectAfter
  by_cases h : i.val < τ
  · simp [h]
  · simp [h]

/-- **Positions before `τ` are unchanged**: for `k ≤ τ`, the reflected walk's
position equals the original's. (The flip only takes effect from index `τ`
onwards.) -/
lemma walkPos_reflectAfter_le (τ : ℕ) (ω : Fin n → Bool) {k : ℕ} (hk : k ≤ τ) :
    walkPos (reflectAfter τ ω) k = walkPos ω k := by
  unfold walkPos
  apply Finset.sum_congr rfl
  intro i hi
  simp only [Finset.mem_filter] at hi
  have h_i_lt_τ : i.val < τ := lt_of_lt_of_le hi.2 hk
  unfold reflectAfter
  rw [if_pos h_i_lt_τ]

/-- **Position-reflection identity** (the algebraic core of André's principle).

For any `τ ≤ k`:

  `walkPos (reflectAfter τ ω) k = 2 · walkPos ω τ − walkPos ω k`.

Proof: decompose each path sum into the prefix `{i.val < τ}` (preserved by
reflection, contributing `walkPos ω τ`) and the suffix `{τ ≤ i.val < k}`
(negated step-by-step by reflection). Combining

  `walkPos (reflect) k = (prefix) − (suffix of ω)`
  `walkPos ω k         = (prefix) + (suffix of ω)`
  `walkPos ω τ         = (prefix)`

gives `walkPos (reflect) k = 2·walkPos ω τ − walkPos ω k`. -/
lemma walkPos_reflectAfter_ge (τ : ℕ) (ω : Fin n → Bool) {k : ℕ} (hτk : τ ≤ k) :
    walkPos (reflectAfter τ ω) k = 2 * walkPos ω τ - walkPos ω k := by
  let Pτ : Finset (Fin n) := Finset.univ.filter (fun (i : Fin n) => i.val < τ)
  let Pτk : Finset (Fin n) :=
    Finset.univ.filter (fun (i : Fin n) => τ ≤ i.val ∧ i.val < k)
  let Pk : Finset (Fin n) := Finset.univ.filter (fun (i : Fin n) => i.val < k)
  have h_disj : Disjoint Pτ Pτk := by
    rw [Finset.disjoint_filter]
    intros i _ hi1 hi2
    exact absurd hi1 (not_lt.mpr hi2.1)
  have h_union : Pk = Pτ ∪ Pτk := by
    ext i
    simp only [Pk, Pτ, Pτk, Finset.mem_filter, Finset.mem_union]
    constructor
    · rintro ⟨_, h_ik⟩
      by_cases h_iτ : i.val < τ
      · exact Or.inl ⟨Finset.mem_univ _, h_iτ⟩
      · exact Or.inr ⟨Finset.mem_univ _, not_lt.mp h_iτ, h_ik⟩
    · rintro (⟨_, h1⟩ | ⟨_, h2, h3⟩)
      · exact ⟨Finset.mem_univ _, lt_of_lt_of_le h1 hτk⟩
      · exact ⟨Finset.mem_univ _, h3⟩
  have h_split_σ : walkPos (reflectAfter τ ω) k =
      (∑ i ∈ Pτ, walkStep (reflectAfter τ ω i)) +
      (∑ i ∈ Pτk, walkStep (reflectAfter τ ω i)) := by
    unfold walkPos
    show ∑ i ∈ Pk, walkStep (reflectAfter τ ω i) = _
    rw [h_union, Finset.sum_union h_disj]
  have h_split_ω : walkPos ω k =
      (∑ i ∈ Pτ, walkStep (ω i)) + (∑ i ∈ Pτk, walkStep (ω i)) := by
    unfold walkPos
    show ∑ i ∈ Pk, walkStep (ω i) = _
    rw [h_union, Finset.sum_union h_disj]
  have h_τ_eq : walkPos ω τ = ∑ i ∈ Pτ, walkStep (ω i) := rfl
  have h_prefix :
      (∑ i ∈ Pτ, walkStep (reflectAfter τ ω i)) = ∑ i ∈ Pτ, walkStep (ω i) := by
    apply Finset.sum_congr rfl
    intro i hi
    simp only [Pτ, Finset.mem_filter] at hi
    unfold reflectAfter
    rw [if_pos hi.2]
  have h_suffix :
      (∑ i ∈ Pτk, walkStep (reflectAfter τ ω i)) = -∑ i ∈ Pτk, walkStep (ω i) := by
    rw [← Finset.sum_neg_distrib]
    apply Finset.sum_congr rfl
    intro i hi
    simp only [Pτk, Finset.mem_filter] at hi
    unfold reflectAfter
    rw [if_neg (not_lt.mpr hi.2.1), walkStep_not]
  rw [h_split_σ, h_prefix, h_suffix, h_split_ω, h_τ_eq]
  ring

/-- **Endpoint statement of the reflection identity**: if `ω` hits level `a` at
time `τ` (`walkPos ω τ = a`) and ends at `b` (`walkPos ω n = b`), then the
reflected path ends at `2a − b`.

This statement drives the counting argument in barrier-option pricing: a
path-to-`b`-touching-`a` and a path-to-`(2a−b)` are in bijection via
reflection at the first hitting time of `a`. -/
theorem walkPos_reflectAfter_endpoint (τ : ℕ) (ω : Fin n → Bool) (hτ : τ ≤ n)
    (a b : ℤ) (h_hit : walkPos ω τ = a) (h_end : walkPos ω n = b) :
    walkPos (reflectAfter τ ω) n = 2 * a - b := by
  rw [walkPos_reflectAfter_ge τ ω hτ, h_hit, h_end]

/-! ## Hitting time and the counting bijection

The reflection identity above is the algebraic spine. To turn it into a
counting statement we choose a canonical `τ` — the **first time** the walk
hits a given level `a` — and reflect there. The bijection is then between
paths that hit `a` and end at one level and paths that hit `a` and end at
its mirror image.

The construction:

1. `hittingSet ω a`: the (finite) set of times `k ∈ {0,…,n}` with
   `walkPos ω k = a`.
2. `HitsLevel`: the predicate that `hittingSet` is nonempty.
3. `firstHit ω a h`: the smallest hit time on a hit-path.
4. `reflectAtFirstHit a`: the partial map `ω ↦ reflectAfter (firstHit ω a h) ω`
   (defaulting to identity when `ω` doesn't hit `a`).
5. **Key invariance**: `firstHit (reflectAtFirstHit ω) a = firstHit ω a`,
   because reflection preserves positions on `[0, τ]`, hence both hits at τ
   (and doesn't hit earlier).
6. **Involution**: applying reflection twice at the same `τ` is identity,
   so `reflectAtFirstHit` is an involution on `{ω : HitsLevel ω a}`.
7. **Endpoint flip**: combining with `walkPos_reflectAfter_endpoint`,
   reflection maps `{ω : hits a, ends at b}` to
   `{ω : hits a, ends at 2a − b}` bijectively.
-/

variable {a : ℤ}

/-- The (finite) set of times `k ∈ {0, …, n}` at which the walk hits level `a`. -/
def hittingSet (ω : Fin n → Bool) (a : ℤ) : Finset ℕ :=
  (Finset.range (n + 1)).filter (fun k => walkPos ω k = a)

/-- A path **hits level `a`** if `hittingSet` is nonempty. -/
abbrev HitsLevel (a : ℤ) (ω : Fin n → Bool) : Prop := (hittingSet ω a).Nonempty

/-- First hitting time of level `a` on a hit-path. -/
noncomputable def firstHit (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω) : ℕ :=
  (hittingSet ω a).min' h

lemma firstHit_mem_hittingSet (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω) :
    firstHit ω a h ∈ hittingSet ω a :=
  (hittingSet ω a).min'_mem h

lemma firstHit_le (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω) :
    firstHit ω a h ≤ n := by
  have h_lt : firstHit ω a h < n + 1 :=
    Finset.mem_range.mp (Finset.mem_filter.mp (firstHit_mem_hittingSet ω a h)).1
  omega

lemma walkPos_firstHit (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω) :
    walkPos ω (firstHit ω a h) = a :=
  (Finset.mem_filter.mp (firstHit_mem_hittingSet ω a h)).2

/-- **Minimality of the first hit**: any `k ≤ n` with `walkPos ω k = a` is at
least `firstHit ω a`. -/
lemma firstHit_le_of_hit (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω)
    {k : ℕ} (hk : k ≤ n) (h_eq : walkPos ω k = a) : firstHit ω a h ≤ k := by
  apply Finset.min'_le
  rw [hittingSet, Finset.mem_filter, Finset.mem_range]
  exact ⟨by omega, h_eq⟩

/-- **Reflected path hits level `a`** when the original does. The reflected
hits at the same `firstHit` time (proven separately below). -/
lemma HitsLevel.reflectAfter_firstHit (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω) :
    HitsLevel a (reflectAfter (firstHit ω a h) ω) := by
  refine ⟨firstHit ω a h, ?_⟩
  unfold hittingSet
  rw [Finset.mem_filter, Finset.mem_range]
  refine ⟨by have := firstHit_le ω a h; omega, ?_⟩
  rw [walkPos_reflectAfter_le _ _ (le_refl _)]
  exact walkPos_firstHit ω a h

/-- **First hit is invariant under reflection at the first hit time.** Crucial
for the involution property of `reflectAtFirstHit`.

Proof: both bounds.
* `firstHit σ a ≤ τ` because `σ` hits `a` at `τ` (positions agree up to `τ`).
* `firstHit σ a ≥ τ` because positions on `[0, τ)` agree with `ω`, where `ω`
  doesn't hit `a` (minimality of `τ`). -/
theorem firstHit_reflectAfter_firstHit (ω : Fin n → Bool) (a : ℤ) (h : HitsLevel a ω) :
    firstHit (reflectAfter (firstHit ω a h) ω) a
        (HitsLevel.reflectAfter_firstHit ω a h) = firstHit ω a h := by
  let hσ := HitsLevel.reflectAfter_firstHit ω a h
  apply le_antisymm
  · -- τσ ≤ τ: σ hits a at τ (positions agree up to τ).
    apply firstHit_le_of_hit _ a hσ (firstHit_le ω a h)
    rw [walkPos_reflectAfter_le _ _ (le_refl _)]
    exact walkPos_firstHit ω a h
  · -- τ ≤ τσ: if τσ < τ, positions agree there with ω; ω doesn't hit a before τ.
    by_contra h_ge_not
    have h_lt : firstHit (reflectAfter (firstHit ω a h) ω) a hσ < firstHit ω a h :=
      not_le.mp h_ge_not
    have h_τσ_le_n : firstHit (reflectAfter (firstHit ω a h) ω) a hσ ≤ n :=
      firstHit_le _ _ _
    have h_walkPos_σ :
        walkPos (reflectAfter (firstHit ω a h) ω)
            (firstHit (reflectAfter (firstHit ω a h) ω) a hσ) = a :=
      walkPos_firstHit _ _ _
    have h_walkPos_ω :
        walkPos ω (firstHit (reflectAfter (firstHit ω a h) ω) a hσ) = a := by
      rw [← walkPos_reflectAfter_le (firstHit ω a h) ω (le_of_lt h_lt)]
      exact h_walkPos_σ
    have h_ge : firstHit ω a h ≤ firstHit (reflectAfter (firstHit ω a h) ω) a hσ :=
      firstHit_le_of_hit ω a h h_τσ_le_n h_walkPos_ω
    exact absurd h_ge (not_le.mpr h_lt)

/-- **Reflection at the first hitting time of `a`**: the path-level map that
reflects every step after `firstHit ω a`. Defaulted to identity off the
hit-set. -/
noncomputable def reflectAtFirstHit (a : ℤ) (ω : Fin n → Bool) : Fin n → Bool :=
  if h : HitsLevel a ω then reflectAfter (firstHit ω a h) ω else ω

/-- **Involution**: reflecting twice at the (preserved) first hit recovers
the original path.

Proof: on the hit-set, `firstHit` is invariant under reflection
(`firstHit_reflectAfter_firstHit`), and `reflectAfter τ` is involutive
(`reflectAfter_involutive`). Off the hit-set, the map is identity. -/
theorem reflectAtFirstHit_involutive (a : ℤ) :
    Function.Involutive (reflectAtFirstHit (n := n) a) := by
  intro ω
  by_cases h1 : HitsLevel a ω
  · -- ω hits a.
    have h2 : HitsLevel a (reflectAfter (firstHit ω a h1) ω) :=
      HitsLevel.reflectAfter_firstHit ω a h1
    have h_τ_eq : firstHit (reflectAfter (firstHit ω a h1) ω) a h2 = firstHit ω a h1 :=
      firstHit_reflectAfter_firstHit ω a h1
    show reflectAtFirstHit a (reflectAtFirstHit a ω) = ω
    unfold reflectAtFirstHit
    rw [dif_pos h1]
    rw [dif_pos h2]
    rw [h_τ_eq]
    exact reflectAfter_involutive (firstHit ω a h1) ω
  · -- ω doesn't hit a; reflectAtFirstHit acts as identity.
    show reflectAtFirstHit a (reflectAtFirstHit a ω) = ω
    unfold reflectAtFirstHit
    rw [dif_neg h1, dif_neg h1]

/-- **Endpoint flip under reflection at first hit**: if `ω` hits `a` and ends
at `b`, then `reflectAtFirstHit a ω` ends at `2a − b`. -/
theorem walkPos_reflectAtFirstHit_endpoint (ω : Fin n → Bool) (a b : ℤ)
    (h : HitsLevel a ω) (h_end : walkPos ω n = b) :
    walkPos (reflectAtFirstHit a ω) n = 2 * a - b := by
  unfold reflectAtFirstHit
  rw [dif_pos h]
  exact walkPos_reflectAfter_endpoint (firstHit ω a h) ω (firstHit_le ω a h)
    a b (walkPos_firstHit ω a h) h_end

/-! ## Discrete intermediate value theorem and the maximal distribution

The reflection principle's *counting* statement, in its cleanest form, drops
the `HitsLevel` hypothesis on one side: any path that *ends* above level `a`
automatically *hits* level `a`. This is the discrete intermediate value
theorem for ±1 walks. Combined with the bijection above, it gives the
classical maximal-distribution counting identity

  `|{ω : HitsLevel a ω}| = 2 · |{ω : a ≤ walkPos ω n}| − |{ω : walkPos ω n = a}|`

for `a ≥ 0`. This is the headline result of the reflection principle in
its application to barrier-option pricing: the cardinality of paths that
*ever* exceed level `a` is computable in terms of the *endpoint* distribution.
-/

/-- **Step relation**: the walk's position at time `k + 1` equals the position
at `k` plus the step value at index `k`. -/
lemma walkPos_succ (ω : Fin n → Bool) {k : ℕ} (hk : k < n) :
    walkPos ω (k + 1) = walkPos ω k + walkStep (ω ⟨k, hk⟩) := by
  unfold walkPos
  have h_split :
      (Finset.univ.filter (fun (i : Fin n) => i.val < k + 1)) =
      (Finset.univ.filter (fun (i : Fin n) => i.val < k)) ∪ {⟨k, hk⟩} := by
    ext i
    simp only [Finset.mem_filter, Finset.mem_univ, Finset.mem_union,
      Finset.mem_singleton, true_and]
    constructor
    · intro h_lt_succ
      by_cases h_eq : i.val = k
      · exact Or.inr (Fin.ext h_eq)
      · exact Or.inl (by omega)
    · rintro (h_lt | h_eq)
      · omega
      · simp [h_eq]
  have h_disj : Disjoint
      (Finset.univ.filter (fun (i : Fin n) => i.val < k))
      ({⟨k, hk⟩} : Finset (Fin n)) := by
    rw [Finset.disjoint_singleton_right, Finset.mem_filter]
    intro h
    exact Nat.lt_irrefl k h.2
  rw [h_split, Finset.sum_union h_disj, Finset.sum_singleton]

/-- **Discrete intermediate value theorem for ±1 walks**: if the walk
ends at or above level `a ≥ 0`, it hits level `a` at some time `k ≤ n`.

Proof: take `τ` = smallest `k ≤ n` with `walkPos ω k ≥ a`. Either `τ = 0`
(so `walkPos ω 0 = 0 ≥ a ≥ 0`, hence `a = 0` and we hit at 0), or `τ > 0`
with `walkPos ω (τ−1) < a` (minimality) and `walkPos ω τ ≥ a`. Since each
step is `±1`, `walkPos τ = walkPos (τ−1) ± 1`; the `-1` case would give
`walkPos τ < a`, contradicting `≥ a`; the `+1` case gives
`walkPos τ = walkPos (τ−1) + 1 ≤ a`, combined with `≥ a` gives `= a`. -/
theorem HitsLevel_of_walkPos_endpoint_ge (ω : Fin n → Bool) (a : ℤ)
    (ha : 0 ≤ a) (h_end : a ≤ walkPos ω n) : HitsLevel a ω := by
  classical
  -- Use Finset.min' on the set of k ≤ n with walkPos ω k ≥ a.
  let S : Finset ℕ :=
    (Finset.range (n + 1)).filter (fun k => a ≤ walkPos ω k)
  have h_n_mem : n ∈ S := by
    show n ∈ S
    rw [show S = _ from rfl, Finset.mem_filter, Finset.mem_range]
    exact ⟨Nat.lt_succ_self n, h_end⟩
  have hS_nonempty : S.Nonempty := ⟨n, h_n_mem⟩
  let τ := S.min' hS_nonempty
  have h_τ_mem_filter :
      τ ∈ (Finset.range (n + 1)).filter (fun k => a ≤ walkPos ω k) :=
    S.min'_mem hS_nonempty
  have h_τ_lt : τ < n + 1 :=
    Finset.mem_range.mp (Finset.mem_filter.mp h_τ_mem_filter).1
  have h_τ_le_n : τ ≤ n := by omega
  have h_τ_ge : a ≤ walkPos ω τ := (Finset.mem_filter.mp h_τ_mem_filter).2
  have h_min : ∀ k < τ, ¬ (a ≤ walkPos ω k) := by
    intro k hk h_ge
    have h_k_in : k ∈ S := by
      show k ∈ S
      rw [show S = _ from rfl, Finset.mem_filter, Finset.mem_range]
      exact ⟨by omega, h_ge⟩
    have : τ ≤ k := S.min'_le k h_k_in
    omega
  -- Show walkPos ω τ = a.
  have h_eq : walkPos ω τ = a := by
    rcases Nat.eq_zero_or_pos τ with hτ_zero | hτ_pos
    · have h_walkPos_zero : walkPos ω τ = 0 := by rw [hτ_zero]; exact walkPos_zero ω
      rw [h_walkPos_zero] at h_τ_ge
      rw [h_walkPos_zero]
      omega
    · obtain ⟨m, h_eq_m⟩ := Nat.exists_eq_succ_of_ne_zero hτ_pos.ne'
      have h_m_lt_n : m < n := by rw [h_eq_m] at h_τ_le_n; omega
      have h_walkPos_m_lt : walkPos ω m < a := by
        by_contra h_ge
        have h_ge' : a ≤ walkPos ω m := not_lt.mp h_ge
        have h_m_lt_τ : m < τ := by rw [h_eq_m]; omega
        exact h_min m h_m_lt_τ h_ge'
      have h_succ := walkPos_succ ω h_m_lt_n
      have h_walkPos_τ_eq : walkPos ω τ = walkPos ω (m + 1) := by rw [h_eq_m]
      rw [h_walkPos_τ_eq] at h_τ_ge
      rw [h_walkPos_τ_eq]
      have h_step_or : walkStep (ω ⟨m, h_m_lt_n⟩) = 1 ∨
                        walkStep (ω ⟨m, h_m_lt_n⟩) = -1 := by
        unfold walkStep
        by_cases hb : ω ⟨m, h_m_lt_n⟩
        · left; simp [hb]
        · right; simp [hb]
      rcases h_step_or with h_up | h_down
      · rw [h_up] at h_succ; omega
      · rw [h_down] at h_succ; omega
  refine ⟨τ, ?_⟩
  rw [hittingSet, Finset.mem_filter, Finset.mem_range]
  exact ⟨by omega, h_eq⟩

/-- **Endpoint-above-barrier ⟹ hits barrier** (alias / corollary).
Same as `HitsLevel_of_walkPos_endpoint_ge`, stated as a one-line use. -/
lemma HitsLevel_of_endpoint_gt {ω : Fin n → Bool} {a : ℤ} (ha : 0 ≤ a)
    (h : a < walkPos ω n) : HitsLevel a ω :=
  HitsLevel_of_walkPos_endpoint_ge ω a ha (le_of_lt h)

/-- **The reflection-principle bijection** (André 1887, counting form).

For any `a, b : ℤ`, the set of paths that hit level `a` and end at `b` is in
bijection with the set of paths that hit level `a` and end at `2a − b`,
via `reflectAtFirstHit a`.

The bijection is an involution (when applied twice, recovers the original
path). -/
noncomputable def reflectionPrincipleEquiv (a b : ℤ) :
    {ω : Fin n → Bool // HitsLevel a ω ∧ walkPos ω n = b} ≃
    {ω : Fin n → Bool // HitsLevel a ω ∧ walkPos ω n = 2 * a - b} where
  toFun ω :=
    ⟨reflectAtFirstHit a ω.val,
     by
       constructor
       · -- Reflected hits a.
         unfold reflectAtFirstHit
         rw [dif_pos ω.property.1]
         exact HitsLevel.reflectAfter_firstHit ω.val a ω.property.1
       · exact walkPos_reflectAtFirstHit_endpoint ω.val a b ω.property.1 ω.property.2⟩
  invFun ω :=
    ⟨reflectAtFirstHit a ω.val,
     by
       constructor
       · unfold reflectAtFirstHit
         rw [dif_pos ω.property.1]
         exact HitsLevel.reflectAfter_firstHit ω.val a ω.property.1
       · -- Reflected ends at 2a - (2a - b) = b.
         have h := walkPos_reflectAtFirstHit_endpoint ω.val a (2 * a - b)
           ω.property.1 ω.property.2
         linarith⟩
  left_inv ω := Subtype.ext (reflectAtFirstHit_involutive a ω.val)
  right_inv ω := Subtype.ext (reflectAtFirstHit_involutive a ω.val)

/-- **Endpoint-above-`a` implies `HitsLevel`** — packaged as an `Equiv`
between `{ω : hits a, ends at b}` and `{ω : ends at b}` for `b ≥ a > 0`.

By the IVT, `walkPos ω n = b ≥ a > 0` automatically implies `HitsLevel a ω`,
so the `HitsLevel` condition is redundant. -/
noncomputable def hitsLevel_inhabited_above (a b : ℤ) (ha : 0 < a) (hb : a ≤ b) :
    {ω : Fin n → Bool // HitsLevel a ω ∧ walkPos ω n = b} ≃
    {ω : Fin n → Bool // walkPos ω n = b} where
  toFun ω := ⟨ω.val, ω.property.2⟩
  invFun ω :=
    ⟨ω.val,
     HitsLevel_of_walkPos_endpoint_ge ω.val a (le_of_lt ha)
       (by rw [ω.property]; exact hb),
     ω.property⟩
  left_inv _ := rfl
  right_inv _ := rfl

/-- **Reflection bijection (below-the-barrier form)** — clean counting Equiv
between paths-touching-`a`-ending-at-`b` (with `b ≤ a`) and paths-ending-
at-`(2a − b)` (no hitting condition on the right, by IVT, since `2a − b ≥ a`).

This is the form actually used in barrier-option pricing: the LHS is "paths
that touch the barrier and end at `b`," the RHS is the simple endpoint
distribution at `2a − b`. -/
noncomputable def reflectionPrincipleEquiv_below
    (a b : ℤ) (ha : 0 < a) (hb : b ≤ a) :
    {ω : Fin n → Bool // HitsLevel a ω ∧ walkPos ω n = b} ≃
    {ω : Fin n → Bool // walkPos ω n = 2 * a - b} :=
  (reflectionPrincipleEquiv (n := n) a b).trans
    (hitsLevel_inhabited_above a (2 * a - b) ha (by linarith))

/-! ## `firstHit?` (Option-returning) for ergonomic downstream use

The `firstHit` function requires a `HitsLevel a ω` proof as input. For
downstream barrier-option code that doesn't have the hit hypothesis upfront
(e.g. computing over all paths and discriminating by hit/no-hit at runtime),
the `Option`-returning variant is cleaner. -/

/-- **First hitting time (Option-returning)**: `some τ` if the path hits
level `a` (with `τ` the first hit time), else `none`. -/
noncomputable def firstHit? (ω : Fin n → Bool) (a : ℤ) : Option ℕ :=
  if h : HitsLevel a ω then some (firstHit ω a h) else none

lemma firstHit?_eq_some_of_hitsLevel (ω : Fin n → Bool) (a : ℤ)
    (h : HitsLevel a ω) : firstHit? ω a = some (firstHit ω a h) := by
  unfold firstHit?; rw [dif_pos h]

lemma firstHit?_eq_none_iff_not_hitsLevel (ω : Fin n → Bool) (a : ℤ) :
    firstHit? ω a = none ↔ ¬ HitsLevel a ω := by
  refine ⟨fun h h_hit => ?_, fun h_not_hit => ?_⟩
  · unfold firstHit? at h
    rw [dif_pos h_hit] at h
    exact Option.some_ne_none _ h
  · unfold firstHit?; exact dif_neg h_not_hit

lemma firstHit?_isSome_iff (ω : Fin n → Bool) (a : ℤ) :
    (firstHit? ω a).isSome ↔ HitsLevel a ω := by
  refine ⟨fun h => ?_, fun h_hit => ?_⟩
  · by_contra h_not_hit
    unfold firstHit? at h
    rw [dif_neg h_not_hit] at h
    exact Bool.false_ne_true h
  · unfold firstHit?; rw [dif_pos h_hit]; rfl

end MathFin
