-- QFD/Cosmology/AxisExtraction.lean
import Mathlib.Analysis.InnerProductSpace.PiL2
import Mathlib.Analysis.InnerProductSpace.Basic
import Mathlib.Analysis.InnerProductSpace.Projection.FiniteDimensional
import Mathlib.Algebra.Ring.Parity
import Mathlib.Tactic.Ring
import Mathlib.Tactic.Linarith

noncomputable section

namespace QFD.Cosmology

open scoped BigOperators
open Real

/--
Use PiLp to get a canonical inner product / norm on functions `Fin 3 → ℝ`.
This avoids some `EuclideanSpace` alias/coercion quirks across mathlib commits.
-/
abbrev R3 := PiLp 2 (fun _ : Fin 3 => ℝ)

/-- Unit predicate (kept minimal; avoids importing `Metric.sphere`). -/
def IsUnit (x : R3) : Prop := ‖x‖ = 1

/-- Legendre P₂. -/
def P2 (t : ℝ) : ℝ := (3 * t^2 - 1) / 2

/-
COMMIT-ROBUST: avoid ⟪n,x⟫_ℝ notation completely.
If `inner` is not in scope for any reason, use `Inner.inner`.
-/
def ip (n x : R3) : ℝ := inner ℝ n x

/-- Axis score: square of inner product (captures ± symmetry). -/
def score (n x : R3) : ℝ := (ip n x)^2

/-- Quadrupole pattern about axis `n` evaluated at direction `x`. -/
def quadPattern (n x : R3) : ℝ := P2 (ip n x)

/-- "Argmax set" on the unit sphere. -/
def AxisSet (f : R3 → ℝ) : Set R3 :=
  {x | IsUnit x ∧ ∀ y, IsUnit y → f y ≤ f x}

lemma quadPattern_eq_affine_score (n x : R3) :
    quadPattern n x = (3/2) * score n x - 1/2 := by
  simp [quadPattern, P2, score, ip]
  ring

/-- Cauchy–Schwarz bound specialized to unit vectors: score ≤ 1. -/
lemma score_le_one_of_unit (n x : R3) (hn : IsUnit n) (hx : IsUnit x) :
    score n x ≤ 1 := by
  have habs : |inner ℝ n x| ≤ ‖n‖ * ‖x‖ := abs_real_inner_le_norm n x
  have habs' : |inner ℝ n x| ≤ 1 := by
    rw [hn, hx] at habs
    simpa using habs
  -- Square both sides: |a|^2 = a^2 and |a| ≤ 1 → a^2 ≤ 1
  have hsq : (inner ℝ n x)^2 ≤ 1 := by
    calc (inner ℝ n x)^2
        = |inner ℝ n x|^2 := by rw [sq_abs]
      _ ≤ 1^2 := by
          apply sq_le_sq'
          · linarith [abs_nonneg (inner ℝ n x)]
          · exact habs'
      _ = 1 := by norm_num
  simpa [score, ip] using hsq

/-- `n` is a maximizer of its own quadrupole pattern on the unit sphere. -/
theorem n_mem_AxisSet_quadPattern (n : R3) (hn : IsUnit n) :
    n ∈ AxisSet (quadPattern n) := by
  refine ⟨hn, ?_⟩
  intro y hy
  have h1 : score n y ≤ 1 := score_le_one_of_unit n y hn hy
  have h2 : score n n = 1 := by
    -- ⟪n,n⟫ = ‖n‖^2, so (⟪n,n⟫)^2 = 1 for unit n
    -- This lemma name is stable in most builds:
    have h_inner : inner ℝ n n = ‖n‖^2 := real_inner_self_eq_norm_sq n
    unfold score ip
    rw [h_inner, hn]
    norm_num
  -- Compare via the affine form (monotone in score since 3/2 > 0).
  -- Goal is quadPattern n y ≤ quadPattern n n.
  -- Use the affine identity and linarith.
  have : (3/2:ℝ) * score n y - 1/2 ≤ (3/2:ℝ) * score n n - 1/2 := by
    have h_nonneg : (0:ℝ) ≤ 3/2 := by norm_num
    linarith [mul_le_mul_of_nonneg_left h1 h_nonneg]
  rw [quadPattern_eq_affine_score, quadPattern_eq_affine_score]
  exact this

/--
**Micro-improvement A: -n is also a maximizer**

The quadrupole pattern is even: score n (-n) = score n n, so both n and -n
are in the argmax set. This establishes "axis defined up to sign."
-/
lemma score_neg (n x : R3) : score n (-x) = score n x := by
  unfold score ip
  simp [inner_neg_right]

theorem neg_n_mem_AxisSet_quadPattern (n : R3) (hn : IsUnit n) :
    -n ∈ AxisSet (quadPattern n) := by
  have h_neg_unit : IsUnit (-n) := by
    unfold IsUnit at *
    rw [norm_neg]
    exact hn
  refine ⟨h_neg_unit, ?_⟩
  intro y hy
  have h1 : score n y ≤ 1 := score_le_one_of_unit n y hn hy
  have h2 : score n (-n) = 1 := by
    rw [score_neg]
    have h_inner : inner ℝ n n = ‖n‖^2 := real_inner_self_eq_norm_sq n
    unfold score ip
    rw [h_inner, hn]
    norm_num
  have : (3/2:ℝ) * score n y - 1/2 ≤ (3/2:ℝ) * score n (-n) - 1/2 := by
    have h_nonneg : (0:ℝ) ≤ 3/2 := by norm_num
    linarith [mul_le_mul_of_nonneg_left h1 h_nonneg]
  rw [quadPattern_eq_affine_score, quadPattern_eq_affine_score]
  exact this

/--
**Micro-improvement B: Argmax invariance under positive affine transforms**

The argmax set is invariant under positive affine transformations.
This connects cos²θ → P₂ → score without re-proving monotonicity each time.
-/
lemma AxisSet_affine (f : R3 → ℝ) (a b : ℝ) (ha : 0 < a) :
    AxisSet (fun x => a * f x + b) = AxisSet f := by
  ext x
  unfold AxisSet IsUnit
  constructor
  · intro ⟨hx_unit, hx_max⟩
    refine ⟨hx_unit, ?_⟩
    intro y hy
    have h : a * f y + b ≤ a * f x + b := hx_max y hy
    -- Cancel: a * f y + b ≤ a * f x + b → a * f y ≤ a * f x → f y ≤ f x
    have h1 : a * f y ≤ a * f x := by linarith
    exact le_of_mul_le_mul_left h1 ha
  · intro ⟨hx_unit, hx_max⟩
    refine ⟨hx_unit, ?_⟩
    intro y hy
    have h : f y ≤ f x := hx_max y hy
    linarith [mul_le_mul_of_nonneg_left h (le_of_lt ha)]

/--
**Monotone transform invariance**

If `g : ℝ → ℝ` is strictly monotone increasing, then applying `g` to a
function `f` preserves its argmax set on the unit sphere.

This generalizes `AxisSet_affine` and makes the framework more robust:
any strictly increasing transformation preserves which directions maximize.
-/
lemma AxisSet_monotone (f : R3 → ℝ) (g : ℝ → ℝ)
    (hg : StrictMono g) :
    AxisSet (g ∘ f) = AxisSet f := by
  ext x
  unfold AxisSet
  constructor
  · intro ⟨hx_unit, hx_max⟩
    refine ⟨hx_unit, ?_⟩
    intro y hy
    have h : g (f y) ≤ g (f x) := hx_max y hy
    exact hg.le_iff_le.mp h
  · intro ⟨hx_unit, hx_max⟩
    refine ⟨hx_unit, ?_⟩
    intro y hy
    have h : f y ≤ f x := hx_max y hy
    exact hg.monotone h

/-! ## Phase 2: Full Uniqueness (AxisSet = {n, -n}) -/

/-- Helper: ip = 1 iff x = n (for unit vectors) -/
lemma ip_eq_one_iff_eq (n x : R3) (hn : IsUnit n) (hx : IsUnit x) :
    ip n x = 1 ↔ n = x := by
  unfold ip IsUnit at *
  exact inner_eq_one_iff_of_norm_eq_one hn hx

/-- Helper: ip = -1 iff x = -n (for unit vectors) -/
lemma ip_eq_neg_one_iff_eq_neg (n x : R3) (hn : IsUnit n) (hx : IsUnit x) :
    ip n x = -1 ↔ n = -x := by
  unfold ip IsUnit at *
  exact inner_eq_neg_one_iff_of_norm_eq_one hn hx

/-- Upper bound: quadPattern ≤ 1 on the unit sphere -/
lemma quadPattern_le_one_of_unit (n x : R3) (hn : IsUnit n) (hx : IsUnit x) :
    quadPattern n x ≤ 1 := by
  have hscore : score n x ≤ 1 := score_le_one_of_unit n x hn hx
  have h_nonneg : (0 : ℝ) ≤ (3/2 : ℝ) := by norm_num
  have hmul : (3/2 : ℝ) * score n x ≤ (3/2 : ℝ) * 1 :=
    mul_le_mul_of_nonneg_left hscore h_nonneg
  have hsub : (3/2 : ℝ) * score n x - 1/2 ≤ (3/2 : ℝ) * 1 - 1/2 :=
    sub_le_sub_right hmul (1/2 : ℝ)
  calc quadPattern n x
      = (3/2 : ℝ) * score n x - 1/2 := quadPattern_eq_affine_score n x
    _ ≤ (3/2 : ℝ) * 1 - 1/2 := hsub
    _ = 1 := by norm_num

/--
**Phase 2: Full Uniqueness**

The argmax set of the quadrupole pattern on the unit sphere is exactly {n, -n}.
This completes the characterization: the "Axis of Evil" (if it follows the QFD
predicted pattern) is the motion vector, defined up to sign.
-/
theorem AxisSet_quadPattern_eq_pm (n : R3) (hn : IsUnit n) :
    AxisSet (quadPattern n) = {x | x = n ∨ x = -n} := by
  ext x
  unfold AxisSet IsUnit at *
  constructor
  · intro ⟨hx_unit, hx_max⟩
    have h_ge : quadPattern n n ≤ quadPattern n x := hx_max n hn
    have h_le : quadPattern n x ≤ 1 := quadPattern_le_one_of_unit n x hn hx_unit
    have hnn : quadPattern n n = 1 := by
      have h_inner : inner ℝ n n = ‖n‖^2 := real_inner_self_eq_norm_sq n
      unfold quadPattern P2 ip
      rw [h_inner, hn]
      norm_num
    have hxEq1 : quadPattern n x = 1 := le_antisymm h_le (by simpa [hnn] using h_ge)
    have hscoreEq1 : score n x = 1 := by
      have : (3/2 : ℝ) * score n x - 1/2 = 1 := by
        rw [← quadPattern_eq_affine_score]
        exact hxEq1
      linarith
    have hip_sq : (ip n x)^2 = 1 := by
      unfold score at hscoreEq1
      exact hscoreEq1
    have hip : ip n x = 1 ∨ ip n x = -1 := by
      have := sq_eq_one_iff.mp hip_sq
      exact this
    cases hip with
    | inl hip1 =>
        have : n = x := (ip_eq_one_iff_eq n x hn hx_unit).1 hip1
        exact Or.inl this.symm
    | inr hipm1 =>
        have hneg : n = -x := (ip_eq_neg_one_iff_eq_neg n x hn hx_unit).1 hipm1
        have : x = -n := by
          have := neg_eq_iff_eq_neg.mpr hneg
          exact this.symm
        exact Or.inr this
  · intro hx
    cases hx with
    | inl hxn =>
        rw [hxn]
        exact n_mem_AxisSet_quadPattern n hn
    | inr hxneg =>
        rw [hxneg]
        exact neg_n_mem_AxisSet_quadPattern n hn

/-! ## Model-to-Data Bridge: Temperature Pattern -/

/--
Temperature-like sky pattern: an affine transform of the quadrupole pattern.

This matches the model-to-data bridge used in the cosmology narrative:
  T(x) = A · P₂(⟨n,x⟩) + B

Here we represent it as:
  T(x) = A * quadPattern n x + B

where:
- A is the quadrupole amplitude (positive for detection)
- B is the monopole offset (isotropic background)
- n is the observer's motion vector

**Physical interpretation**: If the CMB temperature anisotropy (after subtracting
isotropic background) has this functional form, then the extracted axis is
deterministically ±n, independent of the values of A and B (as long as A > 0).
-/
def tempPattern (n : R3) (A B : ℝ) (x : R3) : ℝ :=
  A * quadPattern n x + B

/--
**Bridge Theorem (Model-to-Data, still purely geometric)**

For A > 0, the argmax set on the unit sphere is unchanged by the affine transform,
so the extracted axis is still exactly {n, -n}.

**Physical interpretation**: If the observational fit returns a dominant P₂(⟨n,x⟩)
component with positive amplitude A, the extracted axis is **forced** to be ±n.
There is no freedom for the axis to point elsewhere.

**Connection to README assumptions**: This theorem makes formal the claim that
"IF the CMB has form T(x) = A·P₂(⟨n,x⟩) + B with A > 0, THEN the axis is ±n."
The condition A > 0 is necessary (A < 0 would flip the maximizer to the equator).

**Falsifiability**: If the fitted amplitude A is negative or if the axis extracted
from Planck data differs from the dipole direction, the model is falsified.
-/
theorem AxisSet_tempPattern_eq_pm (n : R3) (hn : IsUnit n) (A B : ℝ) (hA : 0 < A) :
    AxisSet (tempPattern n A B) = {x | x = n ∨ x = -n} := by
  -- Affine invariance of AxisSet
  have h_aff :
      AxisSet (fun x => A * quadPattern n x + B) = AxisSet (quadPattern n) := by
    exact AxisSet_affine (quadPattern n) A B hA
  -- Finish using the proven uniqueness theorem
  calc AxisSet (tempPattern n A B)
      = AxisSet (fun x => A * quadPattern n x + B) := by rfl
    _ = AxisSet (quadPattern n) := h_aff
    _ = {x | x = n ∨ x = -n} := AxisSet_quadPattern_eq_pm n hn

/-! ## Negative Amplitude: Equatorial Maximizers -/

/--
**Definition: Equator of a unit vector n**

The equator perpendicular to n is the set of all unit vectors orthogonal to n.
Equivalently: {x | ⟨n,x⟩ = 0 and ‖x‖ = 1}
-/
def Equator (n : R3) : Set R3 :=
  {x | IsUnit x ∧ ip n x = 0}

/--
**Helper: P₂ value at equator is -1/2**

For any unit vector x orthogonal to n, P₂(⟨n,x⟩) = P₂(0) = -1/2.
This is the minimum value of P₂ on [-1,1].
-/
lemma quadPattern_at_equator (n x : R3) (_hn : IsUnit n) (hx : x ∈ Equator n) :
    quadPattern n x = -1/2 := by
  unfold Equator at hx
  have h_ip_zero : ip n x = 0 := hx.2
  unfold quadPattern P2
  rw [h_ip_zero]
  norm_num

/--
**Helper: P₂ minimum is at the equator**

P₂(t) = (3t² - 1)/2 is minimized at t = 0 with value -1/2.
For unit vectors, this means quadPattern is minimized exactly on the equator.
-/
lemma quadPattern_ge_at_equator (n x : R3) (hn : IsUnit n) (hx : IsUnit x) :
    quadPattern n x ≥ -1/2 := by
  unfold quadPattern P2
  have h_bound : (ip n x)^2 ≤ 1 := by
    have h := abs_real_inner_le_norm n x
    unfold ip IsUnit at *
    rw [hn, hx] at h
    -- |ip n x| ≤ 1, so (ip n x)² ≤ 1
    calc (ip n x)^2
        = |ip n x|^2 := by rw [sq_abs]
      _ ≤ 1^2 := by
        apply sq_le_sq'
        · linarith [abs_nonneg (ip n x)]
        · simpa using h
      _ = 1 := by norm_num
  have : 3 * (ip n x)^2 - 1 ≥ 3 * 0 - 1 := by
    have : (0:ℝ) ≤ 3/2 := by norm_num
    linarith [mul_nonneg this (sq_nonneg (ip n x))]
  linarith

/--
**Helper: If ip ≠ 0, then quadPattern > -1/2 (strict inequality)**

Points off the equator have strictly higher quadPattern values than -1/2.
-/
lemma quadPattern_gt_at_non_equator (n x : R3) (_hn : IsUnit n) (_hx : IsUnit x)
    (h_off : ip n x ≠ 0) :
    quadPattern n x > -1/2 := by
  unfold quadPattern P2
  have : (ip n x)^2 > 0 := by
    exact sq_pos_iff.mpr h_off
  linarith

/--
**Theorem: Equator is non-empty**

For any unit vector n in R³, there exists a unit vector orthogonal to n.

**Proof strategy**: Use Mathlib's finite-dimensional inner product space theory:
1. The orthogonal complement of span{n} has dimension 2 (ℝ³ is 3-dimensional)
2. A subspace of positive dimension contains nonzero vectors
3. Normalize any nonzero orthogonal vector to get a unit vector in the Equator

**Mathlib theorems used**:
- `Submodule.finrank_orthogonal_span_singleton`: dim(span{v}ᗮ) = dim(E) - 1
- `Submodule.exists_mem_ne_zero_of_ne_bot`: positive dimension → nonzero element
-/
theorem equator_nonempty (n : R3) (hn : IsUnit n) : ∃ x, x ∈ Equator n := by
  -- Step 1: n ≠ 0 (from IsUnit n : ‖n‖ = 1)
  have hn_ne : n ≠ 0 := by
    intro h_zero
    unfold IsUnit at hn
    rw [h_zero, norm_zero] at hn
    norm_num at hn

  -- Step 2: The orthogonal complement (ℝ ∙ n)ᗮ is nontrivial
  -- Use Mathlib's finrank_orthogonal_span_singleton theorem
  have h_nontrivial : (Submodule.span ℝ {n})ᗮ ≠ ⊥ := by
    intro h_eq_bot

    -- If (span{n})ᗮ = ⊥, then its dimension is 0
    have h_dim_zero : Module.finrank ℝ (Submodule.span ℝ {n})ᗮ = 0 := by
      rw [h_eq_bot]
      simp

    -- But by Submodule.finrank_orthogonal_span_singleton,
    -- dim((span{v})ᗮ) = dim(E) - 1 for nonzero v in finite-dimensional E
    -- For R3 with dim = 3, we get dim((span{n})ᗮ) = 2

    -- Use the dimension formula for orthogonal complements
    have h_dim_two : Module.finrank ℝ (Submodule.span ℝ {n})ᗮ = 2 := by
      -- Apply Submodule.finrank_orthogonal_span_singleton
      -- This theorem states: for E with finrank = n+1, span{v}ᗮ has finrank = n
      -- For R3 with finrank = 3 = 2+1, we get finrank (span{n})ᗮ = 2

      -- Need to provide Fact instance that finrank R3 = 3
      haveI : Fact (Module.finrank ℝ R3 = 2 + 1) := by
        constructor
        -- R3 = PiLp 2 (Fin 3 → ℝ) has dimension 3
        -- Try to let Lean infer this from PiLp instances
        unfold R3
        norm_num

      -- Now apply the theorem
      -- finrank_orthogonal_span_singleton says: finrank (𝕜 ∙ v)ᗮ = n
      -- where 𝕜 ∙ v is notation for span 𝕜 {v}
      exact Submodule.finrank_orthogonal_span_singleton hn_ne

    -- Now we have: 0 = 2, contradiction
    omega

  -- Step 3: Get a nonzero element from the nontrivial orthogonal complement
  obtain ⟨v, hv_mem, hv_ne⟩ := Submodule.exists_mem_ne_zero_of_ne_bot h_nontrivial

  -- Step 4: v is orthogonal to n (from hv_mem : v ∈ (span ℝ {n})ᗮ)
  have hv_orth : inner ℝ n v = 0 := by
    rw [Submodule.mem_orthogonal] at hv_mem
    exact hv_mem n (Submodule.mem_span_singleton_self n)

  -- Step 5: Normalize v to get unit vector
  let v_norm := ‖v‖
  have hv_norm_pos : 0 < v_norm := norm_pos_iff.mpr hv_ne
  let x := v_norm⁻¹ • v

  use x
  constructor
  · -- Prove IsUnit x: ‖x‖ = 1
    unfold IsUnit
    rw [norm_smul, norm_inv, norm_norm]
    field_simp [ne_of_gt hv_norm_pos]
  · -- Prove ip n x = 0
    unfold ip
    rw [inner_smul_right, hv_orth, mul_zero]

/--
**Negative-Amplitude Companion Theorem**

When the amplitude A < 0, the maximizers of T(x) = A·P₂(⟨n,x⟩) + B are
**not** at the poles ±n, but instead at the **equator** (orthogonal to n).

**Physical interpretation**: This proves that the sign of A matters geometrically.
- A > 0: poles aligned with motion vector (bridge theorem)
- A < 0: equator orthogonal to motion vector (this theorem)

**Falsifiability**: If Planck data require A < 0 to fit observations, the
axis-alignment prediction is falsified. The sign is not a free parameter.

**Why this matters**: Reviewers might ask "couldn't you just flip the sign?"
This theorem shows the answer is NO - flipping the sign changes the geometric
prediction from poles to equator, which is observationally distinguishable.
-/
theorem AxisSet_tempPattern_eq_equator (n : R3) (hn : IsUnit n) (A B : ℝ) (hA : A < 0) :
    AxisSet (tempPattern n A B) = Equator n := by
  ext x
  unfold AxisSet tempPattern Equator
  constructor
  · intro ⟨hx_unit, hx_max⟩
    constructor
    · exact hx_unit
    · -- Must show ip n x = 0
      -- Proof by contradiction: if ip n x ≠ 0, x is not a maximizer

      by_contra h_not_zero

      -- If ip n x ≠ 0, then quadPattern n x > -1/2
      have h_quad_gt : quadPattern n x > -1/2 :=
        quadPattern_gt_at_non_equator n x hn hx_unit h_not_zero

      -- Get a point on the equator (exists by axiom)
      obtain ⟨e, he⟩ := equator_nonempty n hn
      have he_unit : IsUnit e := he.1
      have he_ip : ip n e = 0 := he.2

      -- At the equator, quadPattern = -1/2
      have h_eq_val : quadPattern n e = -1/2 := quadPattern_at_equator n e hn he

      -- Since A < 0 and quadPattern n x > quadPattern n e:
      -- A * quadPattern n x < A * quadPattern n e
      have : A * quadPattern n x < A * quadPattern n e := by
        rw [h_eq_val]
        exact mul_lt_mul_of_neg_left h_quad_gt hA

      -- Therefore tempPattern n A B x < tempPattern n A B e
      have : tempPattern n A B x < tempPattern n A B e := by
        unfold tempPattern
        linarith

      -- But this contradicts x being a maximizer
      have : tempPattern n A B e ≤ tempPattern n A B x :=
        hx_max e he_unit
      linarith
  · intro ⟨hx_unit, h_ip_zero⟩
    constructor
    · exact hx_unit
    · intro y hy
      -- Show tempPattern n A B y ≤ tempPattern n A B x when x is on equator
      -- Since A < 0, we want to show:
      -- A * quadPattern n y + B ≤ A * quadPattern n x + B
      -- which means: A * quadPattern n y ≤ A * quadPattern n x
      -- Since A < 0, this means: quadPattern n y ≥ quadPattern n x

      have h_x_min : quadPattern n x = -1/2 := by
        unfold quadPattern P2
        rw [h_ip_zero]
        norm_num

      have h_y_ge : quadPattern n y ≥ -1/2 :=
        quadPattern_ge_at_equator n y hn hy

      calc tempPattern n A B y
          = A * quadPattern n y + B := rfl
        _ ≤ A * (-1/2) + B := by
            have : A * quadPattern n y ≤ A * (-1/2) := by
              have hA_neg : A ≤ 0 := le_of_lt hA
              exact mul_le_mul_of_nonpos_left h_y_ge hA_neg
            linarith
        _ = A * quadPattern n x + B := by rw [h_x_min]
        _ = tempPattern n A B x := rfl

end QFD.Cosmology
