import Mathlib

/-! # Fel's Conjecture for Numerical Semigroups -/
structure NumericalSemigroup where
  carrier : Set ℕ
  zero_mem : 0 ∈ carrier
  add_mem : ∀ a b, a ∈ carrier → b ∈ carrier → a + b ∈ carrier
  finite_complement : (carrier)ᶜ.Finite

namespace NumericalSemigroup

-- The gap set (complement of the semigroup in ℕ)
noncomputable def gaps (S : NumericalSemigroup) : Finset ℕ := S.finite_complement.toFinset

-- Gap power sum: G_r(S) = sum_{g in Delta} g^r (Definition 3)
noncomputable def gapPowerSum (S : NumericalSemigroup) (r : ℕ) : ℚ :=
  ∑ g ∈ S.gaps, (g : ℚ) ^ r

-- Gap polynomial: Phi_S(z) = sum_{g in Delta} z^g (Definition 4)
noncomputable def gapPolynomial (S : NumericalSemigroup) : Polynomial ℚ :=
  ∑ g ∈ S.gaps, Polynomial.X ^ g

-- Hilbert series: H_S(z) = sum_{s in S} z^s (Definition 5)
-- Defined as a formal power series
-- We use Classical decidability since membership in S.carrier is not decidable in general
noncomputable def hilbertSeries (S : NumericalSemigroup) : PowerSeries ℚ :=
  PowerSeries.mk fun n => if Classical.propDecidable (n ∈ S.carrier) |>.decide then 1 else 0

/-- The coefficient of the Hilbert series at position n is 1 if n ∈ S.carrier, else 0. -/
lemma hilbertSeries_coeff (S : NumericalSemigroup) (n : ℕ) :
    (PowerSeries.coeff n) S.hilbertSeries =
    if Classical.propDecidable (n ∈ S.carrier) |>.decide then 1 else 0 := by
  simp only [hilbertSeries, PowerSeries.coeff_mk]

/-- The coefficient of the gap polynomial (viewed as a power series) at position n is 1 if n ∈ S.gaps, else 0. -/
lemma gapPolynomial_coeff (S : NumericalSemigroup) (n : ℕ) :
    (PowerSeries.coeff n) (S.gapPolynomial : PowerSeries ℚ) =
    if n ∈ S.gaps then 1 else 0 := by
  unfold gapPolynomial
  rw [Polynomial.coeff_coe]
  rw [Polynomial.finset_sum_coeff]
  simp_rw [Polynomial.coeff_X_pow]
  exact Finset.sum_ite_eq S.gaps n (fun _ => 1)

/-- For any n, we have n ∈ S.gaps ↔ n ∉ S.carrier. -/
lemma mem_gaps_iff_not_mem_carrier (S : NumericalSemigroup) (n : ℕ) :
    n ∈ S.gaps ↔ n ∉ S.carrier := by
  simp only [gaps, Set.Finite.mem_toFinset, Set.mem_compl_iff]

lemma semigroupGapDecomposition_aux (S : NumericalSemigroup) (n : ℕ) :
    (PowerSeries.coeff n) (S.hilbertSeries + (S.gapPolynomial : PowerSeries ℚ)) =
    (PowerSeries.coeff n) (PowerSeries.mk (1 : ℕ → ℚ)) := by
  rw [map_add]
  rw [hilbertSeries_coeff, gapPolynomial_coeff]
  rw [PowerSeries.coeff_mk]
  simp only [Pi.one_apply]
  by_cases h : n ∈ S.carrier
  · simp only [decide_eq_true_eq, h, ↓reduceIte]
    rw [if_neg]
    · ring
    · rw [mem_gaps_iff_not_mem_carrier]; exact not_not.mpr h
  · simp only [decide_eq_true_eq, h, ↓reduceIte]
    rw [if_pos]
    · ring
    · rw [mem_gaps_iff_not_mem_carrier]; exact h

theorem semigroupGapDecomposition (S : NumericalSemigroup) :
    S.hilbertSeries + (S.gapPolynomial : PowerSeries ℚ) = PowerSeries.mk (1 : ℕ → ℚ) := by
  apply PowerSeries.ext
  exact semigroupGapDecomposition_aux S

end NumericalSemigroup

structure NumericalSemigroupGenerators (S : NumericalSemigroup) where
  m : ℕ                           -- number of generators (embedding dimension)
  hm_pos : 0 < m                  -- at least one generator
  d : Fin m → ℕ                   -- the generators d_1, ..., d_m
  hd_pos : ∀ i, 0 < d i           -- each generator is positive
  hgcd : (Finset.univ.image d).gcd id = 1  -- gcd of generators is 1
  generates : S.carrier = {n : ℕ | ∃ (c : Fin m → ℕ), n = ∑ i, c i * d i}

namespace NumericalSemigroupGenerators

def pi_m {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) : ℕ :=
  ∏ i : Fin G.m, G.d i

noncomputable def productPolynomial {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    Polynomial ℤ :=
  ∏ i : Fin G.m, (1 - Polynomial.X ^ (G.d i))

noncomputable def hilbertNumeratorDegBound {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    ℕ :=
  G.productPolynomial.natDegree + S.gaps.sup id + 1

noncomputable def hilbertNumerator {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    Polynomial ℤ :=
  let P := G.productPolynomial
  let bound := G.hilbertNumeratorDegBound
  ∑ n ∈ Finset.range bound,
    Polynomial.monomial n
      ((∑ k ∈ Finset.range (n + 1), P.coeff k) -
       (∑ g ∈ S.gaps.filter (· ≤ n), P.coeff (n - g)))

lemma coeff_polynomial_map_coe {P : Polynomial ℤ} (n : ℕ) :
    (PowerSeries.coeff n) (P.map (Int.castRingHom ℚ) : PowerSeries ℚ) =
    (Int.castRingHom ℚ) (P.coeff n) := by simp

lemma coeff_mul_hilbert_product {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ)) =
    ∑ k ∈ Finset.range (n + 1),
      (PowerSeries.coeff k) S.hilbertSeries * (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
  have h1 : (PowerSeries.coeff n) (S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ)) =
      ∑ k ∈ Finset.range (n + 1), (PowerSeries.coeff k) S.hilbertSeries *
        (PowerSeries.coeff (n - k)) (G.productPolynomial.map (Int.castRingHom ℚ)) := by
    rw [PowerSeries.coeff_mul]
    simp_all [Finset.Nat.antidiagonal_eq_image, Finset.sum_image]
  have h2 : ∀ k ∈ Finset.range (n + 1), (PowerSeries.coeff (n - k)) (G.productPolynomial.map (Int.castRingHom ℚ)) =
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
    intro k _
    simp [PowerSeries.coeff_map]
  calc
    (PowerSeries.coeff n) (S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ)) =
        ∑ k ∈ Finset.range (n + 1), (PowerSeries.coeff k) S.hilbertSeries *
          (PowerSeries.coeff (n - k)) (G.productPolynomial.map (Int.castRingHom ℚ)) := by
      rw [h1]
    _ = ∑ k ∈ Finset.range (n + 1), (PowerSeries.coeff k) S.hilbertSeries *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
      apply Finset.sum_congr rfl
      intro k hk
      rw [h2 k hk]
    _ = ∑ k ∈ Finset.range (n + 1), (PowerSeries.coeff k) S.hilbertSeries * (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
      rfl

lemma hilbertNumerator_coeff_lt {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n < G.hilbertNumeratorDegBound) :
    G.hilbertNumerator.coeff n =
      (∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k) -
       (∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g)) := by
  unfold hilbertNumerator
  simp only
  rw [Polynomial.finset_sum_coeff]
  simp only [Polynomial.coeff_monomial]
  have hn_mem : n ∈ Finset.range G.hilbertNumeratorDegBound := Finset.mem_range.mpr hn
  rw [Finset.sum_eq_single_of_mem n hn_mem]
  · simp only [ite_true]
  · intro b _ hbn
    simp only [hbn, ite_false]

lemma hilbertNumerator_coeff_ge {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n ≥ G.hilbertNumeratorDegBound) :
    G.hilbertNumerator.coeff n = 0 := by
  unfold hilbertNumerator
  simp only [Polynomial.finset_sum_coeff]
  apply Finset.sum_eq_zero
  intro i hi
  rw [Polynomial.coeff_monomial]
  simp only [ite_eq_right_iff]
  intro heq
  exfalso
  rw [Finset.mem_range] at hi
  omega

lemma coeff_hilbertNumerator_formula {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (G.hilbertNumerator.map (Int.castRingHom ℚ) : PowerSeries ℚ) =
    if n < G.hilbertNumeratorDegBound then
      (Int.castRingHom ℚ) ((∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k) -
       (∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g)))
    else 0 := by
  rw [Polynomial.coeff_coe, Polynomial.coeff_map]
  split_ifs with h
  · rw [hilbertNumerator_coeff_lt G n h]
  · simp only [hilbertNumerator_coeff_ge G n (le_of_not_gt h), map_zero]

lemma lhs_eq_sum_over_carrier {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (if (Classical.propDecidable (k ∈ S.carrier)).decide then 1 else 0) *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    ∑ k ∈ (Finset.range (n + 1)).filter (fun k => (Classical.propDecidable (k ∈ S.carrier)).decide),
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
  have h₁ : ∑ k ∈ Finset.range (n + 1), (if (Classical.propDecidable (k ∈ S.carrier)).decide then 1 else 0) * (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    ∑ k ∈ Finset.range (n + 1), (if (Classical.propDecidable (k ∈ S.carrier)).decide then (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) else 0) := by
    apply Finset.sum_congr rfl
    intro k _
    split_ifs <;> simp [*]
  have h₂ : ∑ k ∈ Finset.range (n + 1), (if (Classical.propDecidable (k ∈ S.carrier)).decide then (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) else 0) =
    ∑ k ∈ (Finset.range (n + 1)).filter (fun k => (Classical.propDecidable (k ∈ S.carrier)).decide), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
    rw [← Finset.sum_filter]
  rw [h₁, h₂]

lemma partition_sum {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    ∑ k ∈ (Finset.range (n + 1)).filter (fun k => (Classical.propDecidable (k ∈ S.carrier)).decide),
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) +
    ∑ k ∈ (Finset.range (n + 1)).filter (fun k => !(Classical.propDecidable (k ∈ S.carrier)).decide),
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
  have h := @Finset.sum_filter_add_sum_filter_not _ _ _ (Finset.range (n + 1))
    (fun k => k ∈ S.carrier) (Classical.decPred _) (fun x => Classical.dec _)
    (fun k => (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)))
  rw [← h]
  congr 1
  · congr 1
    ext k
    simp only [Finset.mem_filter, decide_eq_true_eq]
  · congr 1
    ext k
    simp only [Finset.mem_filter]
    constructor
    · intro ⟨hk, hnot⟩
      refine ⟨hk, ?_⟩
      simp only [Bool.not_eq_true', decide_eq_false_iff_not]
      exact hnot
    · intro ⟨hk, hbool⟩
      simp only [Bool.not_eq_true', decide_eq_false_iff_not] at hbool
      exact ⟨hk, hbool⟩

lemma filter_not_carrier_eq_gaps {S : NumericalSemigroup} (n : ℕ) :
    (Finset.range (n + 1)).filter (fun k => !(Classical.propDecidable (k ∈ S.carrier)).decide) =
    S.gaps.filter (· ≤ n) := by
  have h_main : (Finset.range (n + 1)).filter (fun k => !(Classical.propDecidable (k ∈ S.carrier)).decide) = S.gaps.filter (· ≤ n) := by
    apply Finset.ext
    intro k
    simp only [Finset.mem_filter, Finset.mem_range, NumericalSemigroup.gaps, Set.Finite.mem_toFinset,
      Set.mem_compl_iff]
    constructor
    · intro h
      have h₂ : ¬(k ∈ S.carrier) := by
        simpa [Classical.propDecidable] using h.2
      have h₃ : k ≤ n := by
        omega
      have h₄ : ¬(k ∈ S.carrier) := h₂
      exact ⟨h₄, h₃⟩
    · intro h
      have h₁ : ¬(k ∈ S.carrier) := h.1
      have h₃ : k < n + 1 := by
        omega
      have h₄ : ¬(k ∈ S.carrier) := h₁
      exact ⟨h₃, by simpa [Classical.propDecidable] using h₄⟩
  apply h_main

lemma carrier_sum_eq_full_minus_gaps {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ (Finset.range (n + 1)).filter (fun k => (Classical.propDecidable (k ∈ S.carrier)).decide),
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    ∑ k ∈ Finset.range (n + 1), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) -
    ∑ g ∈ S.gaps.filter (· ≤ n), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - g)) := by
  have hpart := partition_sum G n
  have hfilter := filter_not_carrier_eq_gaps (S := S) n
  rw [hfilter] at hpart
  linarith

lemma indicator_sum_eq_full_minus_gaps {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (if Classical.propDecidable (k ∈ S.carrier) |>.decide then 1 else 0) *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    ∑ k ∈ Finset.range (n + 1), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) -
    ∑ g ∈ S.gaps.filter (· ≤ n), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - g)) := by
  rw [lhs_eq_sum_over_carrier, carrier_sum_eq_full_minus_gaps]

lemma sum_coeff_reindex {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    ∑ j ∈ Finset.range (n + 1), (Int.castRingHom ℚ) (G.productPolynomial.coeff j) := by
  apply Finset.sum_bij' (fun j _ => n - j) (fun j _ => n - j)
  <;> simp_all [Finset.mem_range, Nat.lt_succ_iff]
  <;> omega

lemma lhs_eq_hilbertNumerator_coeff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n < G.hilbertNumeratorDegBound) :
    ∑ k ∈ Finset.range (n + 1), (if Classical.propDecidable (k ∈ S.carrier) |>.decide then 1 else 0) *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    (Int.castRingHom ℚ) (G.hilbertNumerator.coeff n) := by
  rw [indicator_sum_eq_full_minus_gaps]
  rw [sum_coeff_reindex]
  rw [hilbertNumerator_coeff_lt G n hn]
  simp only [map_sub, map_sum]

lemma sum_range_reindex (f : ℕ → ℚ) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), f (n - k) = ∑ j ∈ Finset.range (n + 1), f j := by
  apply Finset.sum_bij' (fun k _ => n - k) (fun k _ => n - k)
  <;> simp_all [Finset.mem_range]
  <;> (try omega)

lemma large_n_both_zero {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (_hn : n ≥ G.hilbertNumeratorDegBound) :
    (∑ k ∈ Finset.range (n + 1), (if Classical.propDecidable (k ∈ S.carrier) |>.decide then 1 else 0) *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    (Int.castRingHom ℚ) ((∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k) -
     (∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g)))) := by
  rw [lhs_eq_sum_over_carrier]
  rw [carrier_sum_eq_full_minus_gaps]
  rw [sum_range_reindex (fun k => (Int.castRingHom ℚ) (G.productPolynomial.coeff k)) n]
  rw [map_sub, map_sum, map_sum]

lemma sum_over_semigroup_eq_diff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (if Classical.propDecidable (k ∈ S.carrier) |>.decide then 1 else 0) *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) =
    (Int.castRingHom ℚ) ((∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k) -
     (∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g))) := by
  by_cases hn : n < G.hilbertNumeratorDegBound
  · rw [lhs_eq_hilbertNumerator_coeff G n hn]
    congr 1
    exact hilbertNumerator_coeff_lt G n hn
  · push_neg at hn
    exact large_n_both_zero G n hn

lemma coeff_mul_eq_indicator_sum {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ)) =
    ∑ k ∈ Finset.range (n + 1), (if Classical.propDecidable (k ∈ S.carrier) |>.decide then 1 else 0) *
      (Int.castRingHom ℚ) (G.productPolynomial.coeff (n - k)) := by
  rw [coeff_mul_hilbert_product]
  apply Finset.sum_congr rfl
  intro k _
  rw [S.hilbertSeries_coeff]

lemma productPolynomial_eval_one {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    Polynomial.eval 1 G.productPolynomial = 0 := by
  unfold productPolynomial
  rw [Polynomial.eval_prod]
  apply Finset.prod_eq_zero (i := ⟨0, G.hm_pos⟩) (Finset.mem_univ _)
  simp

lemma sum_coeff_eq_eval_one {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), G.productPolynomial.coeff k =
    Polynomial.eval 1 G.productPolynomial := by
  have h₁ : ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), G.productPolynomial.coeff k = G.productPolynomial.eval 1 := by
    rw [Polynomial.eval_eq_sum_range]
    simp [add_comm]
  rw [h₁]

lemma coeff_Ico_eq_zero {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (n : ℕ) (_hn : G.productPolynomial.natDegree + 1 ≤ n)
    (k : ℕ) (hk : k ∈ Finset.Ico (G.productPolynomial.natDegree + 1) (n + 1)) :
    G.productPolynomial.coeff k = 0 := by
  apply Polynomial.coeff_eq_zero_of_natDegree_lt
  simp only [Finset.mem_Ico] at hk
  omega

lemma sum_coeff_large_eq_sum_coeff_deg {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (n : ℕ) (hn : G.productPolynomial.natDegree + 1 ≤ n) :
    ∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k =
    ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), G.productPolynomial.coeff k := by
  have hsplit := Finset.sum_range_add_sum_Ico (f := fun k => G.productPolynomial.coeff k)
    (m := G.productPolynomial.natDegree + 1) (n := n + 1) (by omega)
  have htail : ∑ k ∈ Finset.Ico (G.productPolynomial.natDegree + 1) (n + 1), G.productPolynomial.coeff k = 0 := by
    apply Finset.sum_eq_zero
    intro k hk
    exact coeff_Ico_eq_zero G n hn k hk
  linarith [hsplit, htail]

lemma first_sum_zero {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n ≥ G.hilbertNumeratorDegBound) :
    ∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k = 0 := by
  have h1 : G.productPolynomial.natDegree + 1 ≤ n := by
    unfold hilbertNumeratorDegBound at hn
    omega
  rw [sum_coeff_large_eq_sum_coeff_deg G n h1]
  rw [sum_coeff_eq_eval_one G]
  exact productPolynomial_eval_one G

lemma coeff_n_minus_gap_zero {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (n g : ℕ) (hn : n ≥ G.hilbertNumeratorDegBound) (hg : g ∈ S.gaps) (hgn : g ≤ n) :
    G.productPolynomial.coeff (n - g) = 0 := by
  apply Polynomial.coeff_eq_zero_of_natDegree_lt
  have hge : g ≤ S.gaps.sup id := Finset.le_sup (f := id) hg
  have hn' : n ≥ G.productPolynomial.natDegree + S.gaps.sup id + 1 := hn
  omega

lemma second_sum_zero {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n ≥ G.hilbertNumeratorDegBound) :
    ∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g) = 0 := by
  apply Finset.sum_eq_zero
  intro g hg
  rw [Finset.mem_filter] at hg
  exact coeff_n_minus_gap_zero G n g hn hg.1 hg.2

lemma rhs_zero_of_large {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n ≥ G.hilbertNumeratorDegBound) :
    (∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k) -
     (∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g)) = 0 := by
  rw [first_sum_zero G n hn, second_sum_zero G n hn]
  ring

lemma coeff_eq_zero_of_large {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ)
    (hn : n ≥ G.hilbertNumeratorDegBound) :
    (PowerSeries.coeff n) (S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ)) = 0 := by
  rw [coeff_mul_eq_indicator_sum]
  rw [large_n_both_zero G n hn]
  rw [rhs_zero_of_large G n hn]
  simp

lemma numeratorIdentity_coeff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ)) =
    (PowerSeries.coeff n) (G.hilbertNumerator.map (Int.castRingHom ℚ) : PowerSeries ℚ) := by
  by_cases h : n < G.hilbertNumeratorDegBound
  · -- Case n < bound: use the formula
    rw [coeff_mul_hilbert_product, coeff_hilbertNumerator_formula, if_pos h]
    simp only [NumericalSemigroup.hilbertSeries_coeff]
    exact sum_over_semigroup_eq_diff G n
  · -- Case n >= bound: both sides are 0
    push_neg at h
    rw [coeff_eq_zero_of_large G n h]
    rw [coeff_hilbertNumerator_formula, if_neg (by omega)]

theorem numeratorIdentity {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    S.hilbertSeries * (G.productPolynomial.map (Int.castRingHom ℚ) : PowerSeries ℚ) =
    (G.hilbertNumerator.map (Int.castRingHom ℚ) : PowerSeries ℚ) := by
  apply PowerSeries.ext
  exact numeratorIdentity_coeff G

noncomputable def numeratorCoeff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (j : ℕ) : ℤ :=
  if j = 0 then 0 else -G.hilbertNumerator.coeff j

noncomputable def alternatingPowerSum {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (n : ℕ) : ℚ :=
  ∑ j ∈ Finset.range G.hilbertNumeratorDegBound,
    (G.numeratorCoeff j : ℚ) * (j : ℚ) ^ n

noncomputable def K_invariant {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (p : ℕ) : ℚ :=
  ((-1 : ℚ) ^ G.m * (p.factorial : ℚ)) / (((G.m + p).factorial : ℚ) * (G.pi_m : ℚ)) *
    G.alternatingPowerSum (G.m + p)

noncomputable def scaledExpFactor {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (i : Fin G.m) : PowerSeries ℚ :=
  PowerSeries.mk fun k => (G.d i : ℚ) ^ k / ((k + 1).factorial : ℚ)

noncomputable def A_series {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    PowerSeries ℚ :=
  ∏ i : Fin G.m, G.scaledExpFactor i

noncomputable def T_sigma {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) : ℚ :=
  (n.factorial : ℚ) * (PowerSeries.coeff n) (G.A_series)

noncomputable def B_series {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    PowerSeries ℚ :=
  bernoulliPowerSeries ℚ * G.A_series

noncomputable def T_delta {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) : ℚ :=
  (n.factorial : ℚ) / (2 ^ n : ℚ) * (PowerSeries.coeff n) (G.B_series)

end NumericalSemigroupGenerators

namespace FelsConjectureProof

lemma pi_m_pos {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    0 < G.pi_m := by
  apply Finset.prod_pos
  intro i _
  exact G.hd_pos i

lemma choose_mul_sub_factorial_eq_div (n r : ℕ) (hr : r ≤ n) :
    ((n.choose r : ℚ) * ((n - r).factorial : ℚ)) = (n.factorial : ℚ) / (r.factorial : ℚ) := by
  have h₁ : (n.choose r : ℚ) * (r.factorial : ℚ) * ((n - r).factorial : ℚ) = (n.factorial : ℚ) := by
    norm_cast
    rw [← Nat.choose_mul_factorial_mul_factorial hr]
  have h₂ : (r.factorial : ℚ) ≠ 0 := by positivity
  field_simp [h₂]
  linarith

lemma bernoulli_term_simplify (m p : ℕ) (pi_m : ℕ) (hpi : 0 < pi_m)
    (coeff : ℚ) :
    ((-1 : ℚ)^m * (p.factorial : ℚ)) / (((m + p).factorial : ℚ) * (pi_m : ℚ)) *
      ((-1 : ℚ)^m * (pi_m : ℚ) * ((m + p).factorial : ℚ) * coeff) =
    (p.factorial : ℚ) * coeff := by
  have hpi' : (pi_m : ℚ) ≠ 0 := Nat.cast_ne_zero.mpr (Nat.pos_iff_ne_zero.mp hpi)
  have hfac : ((m + p).factorial : ℚ) ≠ 0 := Nat.cast_ne_zero.mpr (Nat.factorial_ne_zero _)
  have h_neg_sq : ((-1 : ℚ)^(m * 2)) = 1 := by
    rw [mul_comm]
    exact Even.neg_one_pow (even_two_mul m)
  field_simp [hpi', hfac]
  ring_nf
  rw [h_neg_sq]
  ring

lemma outer_factor_cancel {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ)
    (sum_term : ℚ) :
    ((-1 : ℚ)^G.m * (p.factorial : ℚ)) / (((G.m + p).factorial : ℚ) * (G.pi_m : ℚ)) *
      ((-1 : ℚ)^G.m * (G.pi_m : ℚ) * sum_term) =
    (p.factorial : ℚ) / ((G.m + p).factorial : ℚ) * sum_term := by
  have hpi : (G.pi_m : ℚ) ≠ 0 := Nat.cast_ne_zero.mpr (Nat.pos_iff_ne_zero.mp (pi_m_pos G))
  have hfac : ((G.m + p).factorial : ℚ) ≠ 0 := Nat.cast_ne_zero.mpr (Nat.factorial_ne_zero _)
  have h1 : ((-1 : ℚ)^G.m)^2 = 1 := by
    rw [← pow_mul, mul_comm]
    exact Even.neg_one_pow (even_two_mul G.m)
  field_simp
  rw [h1]
  ring

lemma term_simplify (m p r : ℕ) (hr : r ≤ p) (A_coeff G_r : ℚ) :
    (p.factorial : ℚ) / ((m + p).factorial : ℚ) *
      (((m + p).choose r : ℚ) * ((m + p - r).factorial : ℚ) * A_coeff * G_r) =
    (p.factorial : ℚ) / (r.factorial : ℚ) * A_coeff * G_r := by
  have hr_mp : r ≤ m + p := Nat.le_add_left r m |>.trans (Nat.add_le_add_left hr m)
  have h := choose_mul_sub_factorial_eq_div (m + p) r hr_mp
  have h₁ : ((m + p).choose r : ℚ) * ((m + p - r).factorial : ℚ) = ( (m + p).factorial : ℚ ) / ( r.factorial : ℚ ) := by
    exact h
  calc
    (p.factorial : ℚ) / ((m + p).factorial : ℚ) * (((m + p).choose r : ℚ) * ((m + p - r).factorial : ℚ) * A_coeff * G_r) =
        (p.factorial : ℚ) / ((m + p).factorial : ℚ) * ((( (m + p).factorial : ℚ ) / ( r.factorial : ℚ )) * A_coeff * G_r) := by
      rw [h₁]
    _ = (p.factorial : ℚ) / ((m + p).factorial : ℚ) * (((m + p).factorial : ℚ) / (r.factorial : ℚ)) * A_coeff * G_r := by
      ring_nf
    _ = (p.factorial : ℚ) / (r.factorial : ℚ) * A_coeff * G_r := by
      by_cases h₂ : (m + p).factorial = 0 <;>
        by_cases h₃ : (r.factorial : ℚ) = 0 <;>
          by_cases h₄ : (p.factorial : ℚ) = 0 <;>
            field_simp [h₂, h₃, h₄]

lemma gap_sum_simplify {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    ((-1 : ℚ)^G.m * (p.factorial : ℚ)) / (((G.m + p).factorial : ℚ) * (G.pi_m : ℚ)) *
      ((-1 : ℚ)^G.m * (G.pi_m : ℚ) *
        ∑ r ∈ Finset.range (p + 1),
          ((G.m + p).choose r : ℚ) * ((G.m + p - r).factorial : ℚ) *
            (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r) =
    ∑ r ∈ Finset.range (p + 1),
      ((p.factorial : ℚ) / (r.factorial : ℚ)) *
        (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r := by
  rw [outer_factor_cancel]
  rw [Finset.mul_sum]
  apply Finset.sum_congr rfl
  intro r hr
  rw [Finset.mem_range] at hr
  exact term_simplify G.m p r (Nat.lt_succ_iff.mp hr)
    ((PowerSeries.coeff (p - r)) G.A_series) (S.gapPowerSum r)

noncomputable def Q_exp_series {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    PowerSeries ℚ :=
  ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) •
    (PowerSeries.X ^ (G.m - 1) * G.B_series +
     PowerSeries.X ^ G.m * G.A_series *
       PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ))

noncomputable def hilbertNumerator_exp_sub {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) : PowerSeries ℚ :=
  PowerSeries.mk fun n =>
    (∑ j ∈ Finset.range G.hilbertNumeratorDegBound, (G.hilbertNumerator.coeff j : ℚ) * (j : ℚ)^n) /
      (n.factorial : ℚ)

lemma hilbertNumerator_exp_sub_coeff {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (hilbertNumerator_exp_sub G) =
      (∑ j ∈ Finset.range G.hilbertNumeratorDegBound, (G.hilbertNumerator.coeff j : ℚ) * (j : ℚ)^n) /
        (n.factorial : ℚ) := by
  simp only [hilbertNumerator_exp_sub, PowerSeries.coeff_mk]

lemma alternatingPowerSum_eq_neg_sum {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) (hn : 1 ≤ n) :
    G.alternatingPowerSum n =
      -(∑ j ∈ Finset.range G.hilbertNumeratorDegBound, (G.hilbertNumerator.coeff j : ℚ) * (j : ℚ)^n) := by
  unfold NumericalSemigroupGenerators.alternatingPowerSum NumericalSemigroupGenerators.numeratorCoeff
  have hn_ne : n ≠ 0 := Nat.one_le_iff_ne_zero.mp hn
  have h0_pow : (0 : ℚ) ^ n = 0 := zero_pow hn_ne
  rw [← Finset.sum_neg_distrib]
  apply Finset.sum_congr rfl
  intro j _
  by_cases hj0 : j = 0
  · subst hj0
    simp only [↓reduceIte, Int.cast_zero, Nat.cast_zero, h0_pow, mul_zero, neg_zero]
  · simp only [hj0, ↓reduceIte, Int.cast_neg, neg_mul]

lemma alternatingPowerSum_eq_coeff_hilbert_exp {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) (hn : 1 ≤ n) :
    G.alternatingPowerSum n =
      -((n.factorial : ℚ) * (PowerSeries.coeff n) (hilbertNumerator_exp_sub G)) := by
  rw [hilbertNumerator_exp_sub_coeff, alternatingPowerSum_eq_neg_sum G n hn]
  congr 1
  have hfact : (n.factorial : ℚ) ≠ 0 := by positivity
  field_simp

lemma B_term_coeff_shift {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    (PowerSeries.coeff (G.m + p)) (PowerSeries.X ^ (G.m - 1) * G.B_series) =
    (PowerSeries.coeff (p + 1)) G.B_series := by
  have hm_pos : 1 ≤ G.m := G.hm_pos
  have h₁ : (G.m : ℕ) - 1 ≤ G.m + p := by omega
  have h₂ : (G.m + p : ℕ) - (G.m - 1 : ℕ) = p + 1 := by omega
  have h₃ : (PowerSeries.coeff (G.m + p)) (PowerSeries.X ^ (G.m - 1) * G.B_series) = (PowerSeries.coeff ((G.m + p) - (G.m - 1))) G.B_series := by
    rw [PowerSeries.coeff_X_pow_mul']
    simp [h₁]
  rw [h₃, h₂]

lemma coeff_A_mul_E {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    (PowerSeries.coeff p) (G.A_series * PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ)) =
    ∑ r ∈ Finset.range (p + 1), (PowerSeries.coeff (p - r)) G.A_series * (∑ g ∈ S.gaps, (g : ℚ)^r / (r.factorial : ℚ)) := by
  have h₁ : (PowerSeries.coeff p) (G.A_series * PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ)) =
      ∑ ij ∈ Finset.antidiagonal p, (PowerSeries.coeff ij.1) G.A_series * (PowerSeries.coeff ij.2 (PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ))) := by
    rw [PowerSeries.coeff_mul]
  have h₂ : ∑ ij ∈ Finset.antidiagonal p, (PowerSeries.coeff ij.1) G.A_series * (PowerSeries.coeff ij.2 (PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ))) =
      ∑ r ∈ Finset.range (p + 1), (PowerSeries.coeff (p - r)) G.A_series * (∑ g ∈ S.gaps, (g : ℚ)^r / (r.factorial : ℚ)) := by
    have h₃ : ∑ ij ∈ Finset.antidiagonal p, (PowerSeries.coeff ij.1) G.A_series * (PowerSeries.coeff ij.2 (PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ))) =
        ∑ ij ∈ Finset.antidiagonal p, (PowerSeries.coeff ij.1) G.A_series * (∑ g ∈ S.gaps, (g : ℚ)^ij.2 / (ij.2.factorial : ℚ)) := by
      apply Finset.sum_congr rfl
      intro ij _
      rw [PowerSeries.coeff_mk]
    rw [h₃]
    apply Eq.symm
    apply Finset.sum_bij (fun (r : ℕ) _ => ⟨p - r, r⟩)
    <;> simp_all [Finset.mem_antidiagonal, Finset.mem_range, Nat.lt_succ_iff]
    <;> (try ring_nf at *)
    <;> (try omega)
  calc
    (PowerSeries.coeff p) (G.A_series * PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ)) = ∑ ij ∈ Finset.antidiagonal p, (PowerSeries.coeff ij.1) G.A_series * (PowerSeries.coeff ij.2 (PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ))) := by rw [h₁]
    _ = ∑ r ∈ Finset.range (p + 1), (PowerSeries.coeff (p - r)) G.A_series * (∑ g ∈ S.gaps, (g : ℚ)^r / (r.factorial : ℚ)) := by rw [h₂]

lemma sum_swap_gaps_range {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    ∑ r ∈ Finset.range (p + 1), (PowerSeries.coeff (p - r)) G.A_series * (∑ g ∈ S.gaps, (g : ℚ)^r / (r.factorial : ℚ)) =
    ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1), (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r / (r.factorial : ℚ) := by
  have h₁ : ∑ r ∈ Finset.range (p + 1), (PowerSeries.coeff (p - r)) G.A_series * (∑ g ∈ S.gaps, (g : ℚ)^r / (r.factorial : ℚ)) = ∑ r ∈ Finset.range (p + 1), ∑ g ∈ S.gaps, (PowerSeries.coeff (p - r)) G.A_series * ((g : ℚ)^r / (r.factorial : ℚ)) := by
    apply Finset.sum_congr rfl
    intro r _
    rw [Finset.mul_sum]
  rw [h₁]
  rw [Finset.sum_comm]
  apply Finset.sum_congr rfl
  intro g _
  apply Finset.sum_congr rfl
  intro r _
  ring

lemma gap_term_coeff_shift {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    (PowerSeries.coeff (G.m + p)) (PowerSeries.X ^ G.m * G.A_series *
       PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ)) =
    ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
      (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r / (r.factorial : ℚ) := by
  have h1 : PowerSeries.X ^ G.m * G.A_series *
            (PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ)) =
            PowerSeries.X ^ G.m * (G.A_series *
            (PowerSeries.mk fun n => ∑ g ∈ S.gaps, (g : ℚ)^n / (n.factorial : ℚ))) := by ring
  rw [h1]
  rw [add_comm]
  rw [PowerSeries.coeff_X_pow_mul]
  rw [coeff_A_mul_E]
  exact sum_swap_gaps_range G p

lemma Q_exp_coeff_at_m_plus_p {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    (PowerSeries.coeff (G.m + p)) (Q_exp_series G) =
    ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) *
      ((PowerSeries.coeff (p + 1)) G.B_series +
       ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
         (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r / (r.factorial : ℚ)) := by
  unfold Q_exp_series
  rw [PowerSeries.coeff_smul]
  rw [map_add]
  rw [B_term_coeff_shift, gap_term_coeff_shift]
  simp only [smul_eq_mul]

lemma gap_coeff_to_leibniz {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    ((G.m + p).factorial : ℚ) *
      ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
        (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r / (r.factorial : ℚ) =
    ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
        ((G.m + p).factorial : ℚ) / (r.factorial : ℚ) *
          (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r := by
  rw [Finset.mul_sum]
  apply Finset.sum_congr rfl
  intro g _
  rw [Finset.mul_sum]
  apply Finset.sum_congr rfl
  intro r _
  have hr_fac : (r.factorial : ℚ) ≠ 0 := by positivity
  field_simp [hr_fac]

lemma gap_sum_swap {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
        ((G.m + p).factorial : ℚ) / (r.factorial : ℚ) *
          (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r =
    ∑ r ∈ Finset.range (p + 1),
      ((G.m + p).choose r : ℚ) * ((G.m + p - r).factorial : ℚ) *
        (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r := by
  rw [Finset.sum_comm]
  apply Finset.sum_congr rfl
  intro r hr
  rw [Finset.mem_range] at hr
  have hrp : r ≤ p := Nat.lt_succ_iff.mp hr
  have hr_mp : r ≤ G.m + p := Nat.le_add_left r G.m |>.trans (Nat.add_le_add_left hrp G.m)
  have h_choose := choose_mul_sub_factorial_eq_div (G.m + p) r hr_mp
  unfold NumericalSemigroup.gapPowerSum
  rw [Finset.mul_sum]
  apply Finset.sum_congr rfl
  intro g _
  calc ((G.m + p).factorial : ℚ) / (r.factorial : ℚ) *
        (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r
      = (((G.m + p).choose r : ℚ) * ((G.m + p - r).factorial : ℚ)) *
        (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r := by rw [h_choose]
    _ = ((G.m + p).choose r : ℚ) * ((G.m + p - r).factorial : ℚ) *
        (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r := by ring

lemma gap_term_final_form {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    ((G.m + p).factorial : ℚ) *
      ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
        (PowerSeries.coeff (p - r)) G.A_series * (g : ℚ)^r / (r.factorial : ℚ) =
    ∑ r ∈ Finset.range (p + 1),
      ((G.m + p).choose r : ℚ) * ((G.m + p - r).factorial : ℚ) *
        (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r := by
  rw [gap_coeff_to_leibniz, gap_sum_swap]

noncomputable def exp_poly_sub (P : Polynomial ℤ) : PowerSeries ℚ :=
  PowerSeries.mk fun n =>
    (∑ j ∈ Finset.range (P.natDegree + 1), (P.coeff j : ℚ) * (j : ℚ)^n) / (n.factorial : ℚ)

lemma exp_poly_sub_coeff (P : Polynomial ℤ) (n : ℕ) :
    (PowerSeries.coeff n) (exp_poly_sub P) =
    (∑ j ∈ Finset.range (P.natDegree + 1), (P.coeff j : ℚ) * (j : ℚ)^n) / (n.factorial : ℚ) := by
  simp only [exp_poly_sub, PowerSeries.coeff_mk]

lemma exp_poly_sub_sum_extend (P : Polynomial ℤ) (k : ℕ) (n : ℕ) (hk : P.natDegree + 1 ≤ k) :
    ∑ j ∈ Finset.range (P.natDegree + 1), (P.coeff j : ℚ) * (j : ℚ)^n =
    ∑ j ∈ Finset.range k, (P.coeff j : ℚ) * (j : ℚ)^n := by
  have h_subset : Finset.range (P.natDegree + 1) ⊆ Finset.range k := by
    intro x hx
    simp only [Finset.mem_range] at hx ⊢
    omega
  have h_extra_zero : ∀ (j : ℕ), j ∈ Finset.range k → j ∉ Finset.range (P.natDegree + 1) → (P.coeff j : ℚ) * (j : ℚ)^n = 0 := by
    intro j _ hj'
    have h₂ : ¬(j < P.natDegree + 1) := by
      intro h
      apply hj'
      simp_all [Finset.mem_range]
    have h₄ : (P.coeff j : ℤ) = 0 := by
      have h₅ : P.natDegree < j := by omega
      have h₆ : P.coeff j = 0 := Polynomial.coeff_eq_zero_of_natDegree_lt h₅
      simp_all
    have h₅ : (P.coeff j : ℚ) = 0 := by norm_cast at h₄ ⊢
    simp [h₅]
  apply Finset.sum_subset h_subset
  intro j hj hj'
  exact h_extra_zero j hj hj'

lemma hilbertNumerator_natDegree_le_pred_bound {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    G.hilbertNumerator.natDegree ≤ G.hilbertNumeratorDegBound - 1 := by
  unfold NumericalSemigroupGenerators.hilbertNumerator
  apply Polynomial.natDegree_sum_le_of_forall_le
  intro n hn
  calc ((Polynomial.monomial n) _).natDegree
      ≤ n := Polynomial.natDegree_monomial_le _
    _ ≤ G.hilbertNumeratorDegBound - 1 := by
        rw [Finset.mem_range] at hn
        omega

lemma hilbertNumeratorDegBound_pos {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    0 < G.hilbertNumeratorDegBound := by
  unfold NumericalSemigroupGenerators.hilbertNumeratorDegBound
  omega

lemma hilbertNumerator_natDegree_lt_bound {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    G.hilbertNumerator.natDegree + 1 ≤ G.hilbertNumeratorDegBound := by
  have h := hilbertNumerator_natDegree_le_pred_bound G
  have hpos := hilbertNumeratorDegBound_pos G
  omega

lemma hilbertNumerator_exp_sub_eq_exp_poly_sub {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    hilbertNumerator_exp_sub G = exp_poly_sub G.hilbertNumerator := by
  apply PowerSeries.ext
  intro n
  simp only [hilbertNumerator_exp_sub, exp_poly_sub, PowerSeries.coeff_mk]
  congr 1
  exact (exp_poly_sub_sum_extend G.hilbertNumerator G.hilbertNumeratorDegBound n
    (hilbertNumerator_natDegree_lt_bound G)).symm

lemma Q_exp_series_coeff {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (Q_exp_series G) =
    ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) *
      ((PowerSeries.coeff n) (PowerSeries.X ^ (G.m - 1) * G.B_series) +
       (PowerSeries.coeff n) (PowerSeries.X ^ G.m * G.A_series *
         PowerSeries.mk fun k => ∑ g ∈ S.gaps, (g : ℚ)^k / (k.factorial : ℚ))) := by
  simp only [Q_exp_series, PowerSeries.coeff_smul, smul_eq_mul, map_add]

lemma j_lt_bound_of_mem_range_natDegree {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (j : ℕ)
    (hj : j ∈ Finset.range (G.hilbertNumerator.natDegree + 1)) :
    j < G.hilbertNumeratorDegBound := by
  have hj_lt := Finset.mem_range.mp hj
  exact Nat.lt_of_lt_of_le hj_lt (hilbertNumerator_natDegree_lt_bound G)

lemma hilbert_sum_decomposition_natDegree {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (G.hilbertNumerator.coeff j : ℚ) * (j : ℚ)^n =
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) * (j : ℚ)^n) -
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ g ∈ S.gaps.filter (· ≤ j), (G.productPolynomial.coeff (j - g) : ℚ)) * (j : ℚ)^n) := by
  rw [← Finset.sum_sub_distrib]
  apply Finset.sum_congr rfl
  intro j hj
  have hj_lt : j < G.hilbertNumeratorDegBound := j_lt_bound_of_mem_range_natDegree G j hj
  have coeff_eq := NumericalSemigroupGenerators.hilbertNumerator_coeff_lt G j hj_lt
  simp only [coeff_eq, Int.cast_sub, Int.cast_sum]
  ring

lemma partial_sum_eq_zero_when_large {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (D : ℕ) (hD : G.productPolynomial.natDegree ≤ D) :
    (∑ k ∈ Finset.range (D + 1), (G.productPolynomial.coeff k : ℚ)) = 0 := by
  have h1 : ∑ k ∈ Finset.range (D + 1), G.productPolynomial.coeff k =
            ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), G.productPolynomial.coeff k := by
    rcases Nat.eq_or_lt_of_le hD with heq | hlt
    · rw [heq]
    · exact NumericalSemigroupGenerators.sum_coeff_large_eq_sum_coeff_deg G D (by omega)
  have h2 : ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), G.productPolynomial.coeff k =
            Polynomial.eval 1 G.productPolynomial := NumericalSemigroupGenerators.sum_coeff_eq_eval_one G
  have h3 : Polynomial.eval 1 G.productPolynomial = 0 := NumericalSemigroupGenerators.productPolynomial_eval_one G
  simp only [← Int.cast_sum]
  rw [h1, h2, h3]
  simp

lemma leadingCoeff_one_sub_X_pow (k : ℕ) (hk : 0 < k) :
    (1 - Polynomial.X ^ k : Polynomial ℤ).leadingCoeff = -1 := by
  have h₁ : (1 - Polynomial.X ^ k : Polynomial ℤ) = -(Polynomial.X ^ k - 1 : Polynomial ℤ) := by ring
  have h₂ : (Polynomial.X ^ k - 1 : Polynomial ℤ).Monic := by
    apply Polynomial.monic_X_pow_sub_C
    omega
  simp only [h₁, Polynomial.leadingCoeff_neg, h₂.leadingCoeff]

lemma productPolynomial_leadingCoeff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    G.productPolynomial.leadingCoeff = (-1 : ℤ) ^ G.m := by
  unfold NumericalSemigroupGenerators.productPolynomial
  rw [Polynomial.leadingCoeff_prod]
  have h : ∀ i : Fin G.m, (1 - Polynomial.X ^ G.d i : Polynomial ℤ).leadingCoeff = -1 := by
    intro i
    exact leadingCoeff_one_sub_X_pow (G.d i) (G.hd_pos i)
  simp only [h]
  exact Fin.prod_const G.m (-1 : ℤ)

lemma natDegree_ge_of_gaps_nonempty {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (h : S.gaps.Nonempty) :
    G.productPolynomial.natDegree ≤ G.hilbertNumerator.natDegree := by
  have hge : G.productPolynomial.natDegree + S.gaps.sup id ≤ G.hilbertNumerator.natDegree := by
    apply Polynomial.le_natDegree_of_ne_zero
    unfold NumericalSemigroupGenerators.hilbertNumerator
    simp only [Polynomial.finset_sum_coeff, Polynomial.coeff_monomial]
    rw [Finset.sum_eq_single (G.productPolynomial.natDegree + S.gaps.sup id)]
    · simp only [↓reduceIte, ne_eq, sub_eq_zero]
      intro hcontra
      obtain ⟨gmax, hgmax_mem, hgmax_eq⟩ := Finset.exists_mem_eq_sup S.gaps h id
      simp only [id_eq] at hgmax_eq
      have hfilter : S.gaps.filter (· ≤ G.productPolynomial.natDegree + S.gaps.sup id) = S.gaps := by
        apply Finset.filter_true_of_mem
        intro g hg
        calc g ≤ S.gaps.sup id := Finset.le_sup (f := id) hg
             _ ≤ G.productPolynomial.natDegree + S.gaps.sup id := le_add_self
      have hsum_partial : ∑ k ∈ Finset.range (G.productPolynomial.natDegree + S.gaps.sup id + 1),
          G.productPolynomial.coeff k = 0 := by
        have hge : 1 ≤ S.gaps.sup id := by
          rw [hgmax_eq]
          have hgap_pos : 0 < gmax := by
            by_contra h0
            push_neg at h0
            interval_cases gmax
            · have : (0 : ℕ) ∈ S.gaps := hgmax_mem
              have hmem : 0 ∈ S.carrier := S.zero_mem
              have hnotmem : 0 ∉ S.carrier := by
                have : S.gaps = S.finite_complement.toFinset := rfl
                rw [this] at hgmax_mem
                rw [Set.Finite.mem_toFinset] at hgmax_mem
                exact hgmax_mem
              contradiction
          omega
        have h1 := NumericalSemigroupGenerators.sum_coeff_large_eq_sum_coeff_deg G (G.productPolynomial.natDegree + S.gaps.sup id) (by omega)
        rw [h1, NumericalSemigroupGenerators.sum_coeff_eq_eval_one G, NumericalSemigroupGenerators.productPolynomial_eval_one G]
      rw [hfilter] at hcontra
      rw [Finset.sum_eq_single_of_mem gmax hgmax_mem] at hcontra
      · have hsub : G.productPolynomial.natDegree + S.gaps.sup id - gmax = G.productPolynomial.natDegree := by
          rw [hgmax_eq, Nat.add_sub_cancel]
        rw [hsub, Polynomial.coeff_natDegree, productPolynomial_leadingCoeff G] at hcontra
        simp only [hsum_partial] at hcontra
        exact Int.neg_one_pow_ne_zero hcontra.symm
      · intro g hg hne
        apply Polynomial.coeff_eq_zero_of_natDegree_lt
        have hle : g ≤ gmax := by
          have := Finset.le_sup (f := id) hg
          simp only [id_eq, hgmax_eq] at this
          exact this
        have hlt : g < gmax := lt_of_le_of_ne hle hne
        simp only [hgmax_eq]
        omega
    · intro j _ hj
      simp [hj]
    · intro hn
      simp at hn
      unfold NumericalSemigroupGenerators.hilbertNumeratorDegBound at hn
      omega
  omega

lemma telescoping_general (D n : ℕ) (P : Polynomial ℤ) :
    (∑ j ∈ Finset.range (D + 1),
      (∑ k ∈ Finset.range (j + 1), (P.coeff k : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) =
    (∑ k ∈ Finset.range (D + 1), (P.coeff k : ℚ)) * ((D : ℚ) + 1)^n -
    (∑ j ∈ Finset.range (D + 1), (P.coeff j : ℚ) * (j : ℚ)^n) := by
  have h₀ : ∀ (m : ℕ), (∑ j ∈ Finset.range (m + 1),
      (∑ k ∈ Finset.range (j + 1), (P.coeff k : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) =
    (∑ k ∈ Finset.range (m + 1), (P.coeff k : ℚ)) * ((m : ℚ) + 1)^n -
    (∑ j ∈ Finset.range (m + 1), (P.coeff j : ℚ) * (j : ℚ)^n) := by
    intro m
    induction m with
    | zero =>
      simp [Finset.sum_range_succ]
      ring_nf
    | succ m ih =>
      rw [Finset.sum_range_succ, ih]
      simp [Finset.sum_range_succ, add_assoc] at *
      ring_nf at *
  exact h₀ D

lemma telescoping_sum_gaps_nonempty {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (n : ℕ) (hgaps : S.gaps.Nonempty) :
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) =
    - (∑ j ∈ Finset.range (G.productPolynomial.natDegree + 1),
        (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n) := by
  set D := G.hilbertNumerator.natDegree with hD_def
  set d := G.productPolynomial.natDegree with hd_def
  have htele := telescoping_general D n G.productPolynomial
  have hDge : d ≤ D := natDegree_ge_of_gaps_nonempty G hgaps
  have hSD : ∑ k ∈ Finset.range (D + 1), (G.productPolynomial.coeff k : ℚ) = 0 :=
    partial_sum_eq_zero_when_large G D hDge
  rw [htele, hSD, zero_mul, zero_sub]
  have hext : ∑ j ∈ Finset.range (d + 1), (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n =
              ∑ j ∈ Finset.range (D + 1), (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n :=
    exp_poly_sub_sum_extend G.productPolynomial (D + 1) n (Nat.add_le_add_right hDge 1)
  rw [← hext]

lemma hilbertNumerator_coeff_eq_partial_sum_gaps_empty {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (hgaps : ¬S.gaps.Nonempty) (j : ℕ)
    (hj : j < G.hilbertNumeratorDegBound) :
    G.hilbertNumerator.coeff j = ∑ k ∈ Finset.range (j + 1), G.productPolynomial.coeff k := by
  have h₁ : S.gaps = ∅ := by
    by_contra h
    have h₂ : S.gaps.Nonempty := Finset.nonempty_iff_ne_empty.mpr h
    contradiction
  have h₂ : ∀ (n : ℕ), ∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g) = 0 := by
    intro n
    rw [h₁]
    simp [Finset.filter_empty]
  have h₃ : ∀ (n : ℕ), n < G.hilbertNumeratorDegBound →
      G.hilbertNumerator.coeff n = ∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k := by
    intro n hn
    have h₄ : G.hilbertNumerator.coeff n =
        (∑ k ∈ Finset.range (n + 1), G.productPolynomial.coeff k) -
        ∑ g ∈ S.gaps.filter (· ≤ n), G.productPolynomial.coeff (n - g) := by
      simp only [NumericalSemigroupGenerators.hilbertNumerator]
      simp [Polynomial.coeff_monomial, Finset.mem_range, hn]
    rw [h₄, h₂ n, sub_zero]
  exact h₃ j hj

lemma partial_sum_zero_after_natDegree_gaps_empty {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (hgaps : ¬S.gaps.Nonempty) (j : ℕ)
    (hj1 : G.hilbertNumerator.natDegree < j) (hj2 : j < G.hilbertNumeratorDegBound) :
    (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) = 0 := by
  have h_coeff_eq := hilbertNumerator_coeff_eq_partial_sum_gaps_empty G hgaps j hj2
  have h_coeff_zero : G.hilbertNumerator.coeff j = 0 := Polynomial.coeff_eq_zero_of_natDegree_lt hj1
  have h_sum_zero : ∑ k ∈ Finset.range (j + 1), G.productPolynomial.coeff k = 0 := by
    rw [← h_coeff_eq, h_coeff_zero]
  rw [← Int.cast_sum]
  simp only [h_sum_zero, Int.cast_zero]

lemma coeff_eq_partial_sum_diff (P : Polynomial ℤ) (n : ℕ) :
    (P.coeff n : ℚ) = (∑ k ∈ Finset.range (n + 1), (P.coeff k : ℚ)) -
                      (∑ k ∈ Finset.range n, (P.coeff k : ℚ)) := by
  have h_sum_split : (∑ k ∈ Finset.range (n + 1), (P.coeff k : ℚ)) =
      (∑ k ∈ Finset.range n, (P.coeff k : ℚ)) + (P.coeff n : ℚ) := by
    rw [Finset.sum_range_succ]
  rw [h_sum_split]
  ring

lemma coeff_at_D_plus_one_gaps_empty {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (hgaps : ¬S.gaps.Nonempty)
    (hbound : G.hilbertNumerator.natDegree + 1 < G.hilbertNumeratorDegBound) :
    (G.productPolynomial.coeff (G.hilbertNumerator.natDegree + 1) : ℚ) =
    -(∑ k ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (G.productPolynomial.coeff k : ℚ)) := by
  set D := G.hilbertNumerator.natDegree with hD_def
  have h1 : (G.productPolynomial.coeff (D + 1) : ℚ) =
      (∑ k ∈ Finset.range (D + 1 + 1), (G.productPolynomial.coeff k : ℚ)) -
      (∑ k ∈ Finset.range (D + 1), (G.productPolynomial.coeff k : ℚ)) :=
    coeff_eq_partial_sum_diff G.productPolynomial (D + 1)
  have h2 : (∑ k ∈ Finset.range (D + 1 + 1), (G.productPolynomial.coeff k : ℚ)) = 0 := by
    apply partial_sum_zero_after_natDegree_gaps_empty G hgaps (D + 1)
    · exact Nat.lt_add_one D
    · exact hbound
  rw [h1, h2, zero_sub]

lemma coeff_zero_after_D_plus_one_gaps_empty {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (hgaps : ¬S.gaps.Nonempty)
    (_hbound : G.hilbertNumerator.natDegree + 1 < G.hilbertNumeratorDegBound)
    (j : ℕ) (hj_lo : G.hilbertNumerator.natDegree + 2 ≤ j)
    (hj_hi : j ≤ G.productPolynomial.natDegree) :
    (G.productPolynomial.coeff j : ℚ) = 0 := by
  set D := G.hilbertNumerator.natDegree with hD_def
  have hcoeff_rel : (G.productPolynomial.coeff j : ℚ) =
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) -
      (∑ k ∈ Finset.range j, (G.productPolynomial.coeff k : ℚ)) :=
    coeff_eq_partial_sum_diff G.productPolynomial j
  rw [hcoeff_rel]
  have hj_bound : j < G.hilbertNumeratorDegBound := by
    unfold NumericalSemigroupGenerators.hilbertNumeratorDegBound at _hbound ⊢
    omega
  have hjm1_bound : j - 1 < G.hilbertNumeratorDegBound := by omega
  have hj_gt_D : D < j := by omega
  have hjm1_gt_D : D < j - 1 := by omega
  have hS_j_zero : (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) = 0 :=
    partial_sum_zero_after_natDegree_gaps_empty G hgaps j hj_gt_D hj_bound
  have hS_jm1_zero : (∑ k ∈ Finset.range j, (G.productPolynomial.coeff k : ℚ)) = 0 := by
    have heq : j = (j - 1) + 1 := by omega
    rw [heq]
    exact partial_sum_zero_after_natDegree_gaps_empty G hgaps (j - 1) hjm1_gt_D hjm1_bound
  rw [hS_j_zero, hS_jm1_zero]
  ring

lemma sum_tail_eq_zero {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) (hgaps : ¬S.gaps.Nonempty)
    (hbound : G.hilbertNumerator.natDegree + 1 < G.hilbertNumeratorDegBound) :
    (∑ j ∈ Finset.Ico (G.hilbertNumerator.natDegree + 2) (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n) = 0 := by
  apply Finset.sum_eq_zero
  intro j hj
  simp only [Finset.mem_Ico] at hj
  have hj_lo : G.hilbertNumerator.natDegree + 2 ≤ j := hj.1
  have hj_hi : j ≤ G.productPolynomial.natDegree := by omega
  have hcoeff_zero : (G.productPolynomial.coeff j : ℚ) = 0 :=
    coeff_zero_after_D_plus_one_gaps_empty G hgaps hbound j hj_lo hj_hi
  rw [hcoeff_zero]
  ring

lemma poly_sum_tail_eq_neg_partial_gaps_empty {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) (hgaps : ¬S.gaps.Nonempty)
    (hbound : G.hilbertNumerator.natDegree + 1 < G.hilbertNumeratorDegBound) :
    (∑ j ∈ Finset.Ico (G.hilbertNumerator.natDegree + 1) (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n) =
    -(∑ k ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (G.productPolynomial.coeff k : ℚ)) *
     ((G.hilbertNumerator.natDegree : ℚ) + 1)^n := by
  set D := G.hilbertNumerator.natDegree with hD_def
  set d := G.productPolynomial.natDegree with hd_def
  by_cases hcase : D + 1 > d
  · have hempty : Finset.Ico (D + 1) (d + 1) = ∅ := Finset.Ico_eq_empty_of_le (by omega)
    simp only [hempty, Finset.sum_empty]
    have hDd : d ≤ D := by omega
    have hS_D_zero : (∑ k ∈ Finset.range (D + 1), (G.productPolynomial.coeff k : ℚ)) = 0 :=
      partial_sum_eq_zero_when_large G D hDd
    rw [hS_D_zero]
    ring
  · push_neg at hcase
    have hsplit : Finset.Ico (D + 1) (d + 1) = {D + 1} ∪ Finset.Ico (D + 2) (d + 1) := by
      ext x
      simp only [Finset.mem_Ico, Finset.mem_union, Finset.mem_singleton]
      constructor
      · intro ⟨hlo, hhi⟩
        by_cases hx : x = D + 1
        · left; exact hx
        · right; constructor <;> omega
      · intro h
        cases h with
        | inl h => rw [h]; constructor <;> omega
        | inr h => obtain ⟨hlo, hhi⟩ := h; constructor <;> omega
    have hdisj : Disjoint ({D + 1} : Finset ℕ) (Finset.Ico (D + 2) (d + 1)) := by
      rw [Finset.disjoint_left]
      intro x hx hx'
      simp only [Finset.mem_singleton] at hx
      simp only [Finset.mem_Ico] at hx'
      omega
    rw [hsplit, Finset.sum_union hdisj]
    simp only [Finset.sum_singleton]
    rw [sum_tail_eq_zero G n hgaps hbound]
    rw [add_zero]
    rw [coeff_at_D_plus_one_gaps_empty G hgaps hbound]
    simp only [← hD_def, Nat.cast_add, Nat.cast_one, neg_mul]

lemma telescoping_sum_gaps_empty {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (n : ℕ) (hgaps : ¬S.gaps.Nonempty) :
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) =
    - (∑ j ∈ Finset.range (G.productPolynomial.natDegree + 1),
        (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n) := by
  set D := G.hilbertNumerator.natDegree with hD_def
  set d := G.productPolynomial.natDegree with hd_def
  rw [telescoping_general D n G.productPolynomial]
  simp only [Finset.not_nonempty_iff_eq_empty] at hgaps
  have hgaps_sup : S.gaps.sup id = 0 := by rw [hgaps]; simp
  by_cases hbound : D + 1 < G.hilbertNumeratorDegBound
  · have hle : D + 1 ≤ d + 1 := by
      unfold NumericalSemigroupGenerators.hilbertNumeratorDegBound at hbound
      rw [hgaps_sup] at hbound
      omega
    rw [← Finset.sum_range_add_sum_Ico _ hle]
    have hgaps' : ¬S.gaps.Nonempty := by simp [hgaps]
    have htail := poly_sum_tail_eq_neg_partial_gaps_empty G n hgaps' hbound
    rw [htail]
    ring
  · push_neg at hbound
    unfold NumericalSemigroupGenerators.hilbertNumeratorDegBound at hbound
    rw [hgaps_sup] at hbound
    have hDge : d ≤ D := Nat.lt_succ_iff.mp (Nat.succ_le_iff.mpr hbound)
    have hS_D_zero : (∑ k ∈ Finset.range (D + 1), (G.productPolynomial.coeff k : ℚ)) = 0 :=
      partial_sum_eq_zero_when_large G D hDge
    have hext := exp_poly_sub_sum_extend G.productPolynomial (D + 1) n (by omega)
    rw [← hext, hS_D_zero]
    ring

lemma telescoping_sum {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) =
    - (∑ j ∈ Finset.range (G.productPolynomial.natDegree + 1),
        (G.productPolynomial.coeff j : ℚ) * (j : ℚ)^n) := by
  by_cases hgaps : S.gaps.Nonempty
  · exact telescoping_sum_gaps_nonempty G n hgaps
  · exact telescoping_sum_gaps_empty G n hgaps


noncomputable def partialSumGenFunc {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    PowerSeries ℚ :=
  PowerSeries.mk fun n =>
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) * (j : ℚ)^n) / (n.factorial : ℚ)

lemma partialSumGenFunc_coeff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (partialSumGenFunc G) =
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) * (j : ℚ)^n) / (n.factorial : ℚ) := by
  simp only [partialSumGenFunc, PowerSeries.coeff_mk]

lemma exp_poly_sub_one : exp_poly_sub 1 = 1 := by
  ext n
  simp only [exp_poly_sub, PowerSeries.coeff_mk, PowerSeries.coeff_one]
  simp only [Polynomial.natDegree_one, Polynomial.coeff_one]
  rw [Finset.range_one, Finset.sum_singleton]
  simp only [↓reduceIte, CharP.cast_eq_zero]
  cases n with
  | zero => simp
  | succ n => simp [zero_pow (Nat.succ_ne_zero n)]

lemma coeff_mul_expand (P Q : Polynomial ℤ) (n : ℕ) :
    (PowerSeries.coeff n) (exp_poly_sub P * exp_poly_sub Q) =
    ∑ k ∈ Finset.range (n + 1),
      ((∑ a ∈ Finset.range (P.natDegree + 1), (P.coeff a : ℚ) * (a : ℚ)^k) / k.factorial) *
      ((∑ b ∈ Finset.range (Q.natDegree + 1), (Q.coeff b : ℚ) * (b : ℚ)^(n - k)) / (n - k).factorial) := by
  rw [PowerSeries.coeff_mul]
  rw [Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk]
  congr 1
  ext k
  simp only [exp_poly_sub, PowerSeries.coeff_mk]

lemma lhs_to_triple_sum (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      ((∑ a ∈ Finset.range (P.natDegree + 1), (P.coeff a : ℚ) * (a : ℚ)^k) / k.factorial) *
      ((∑ b ∈ Finset.range (Q.natDegree + 1), (Q.coeff b : ℚ) * (b : ℚ)^(n - k)) / (n - k).factorial) =
    ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) / (k.factorial * (n - k).factorial) := by
  apply Finset.sum_congr rfl
  intro k _
  have h₂ : ((∑ a ∈ Finset.range (P.natDegree + 1), (P.coeff a : ℚ) * (a : ℚ)^k) / k.factorial) *
      ((∑ b ∈ Finset.range (Q.natDegree + 1), (Q.coeff b : ℚ) * (b : ℚ)^(n - k)) / (n - k).factorial) =
    ((∑ a ∈ Finset.range (P.natDegree + 1), (P.coeff a : ℚ) * (a : ℚ)^k) * (∑ b ∈ Finset.range (Q.natDegree + 1), (Q.coeff b : ℚ) * (b : ℚ)^(n - k))) / (k.factorial * (n - k).factorial) := by
    field_simp [mul_comm]
  rw [h₂]
  have h₃ : (∑ a ∈ Finset.range (P.natDegree + 1), (P.coeff a : ℚ) * (a : ℚ)^k) * (∑ b ∈ Finset.range (Q.natDegree + 1), (Q.coeff b : ℚ) * (b : ℚ)^(n - k)) =
      ∑ a ∈ Finset.range (P.natDegree + 1), ∑ b ∈ Finset.range (Q.natDegree + 1), (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
    calc
      (∑ a ∈ Finset.range (P.natDegree + 1), (P.coeff a : ℚ) * (a : ℚ)^k) * (∑ b ∈ Finset.range (Q.natDegree + 1), (Q.coeff b : ℚ) * (b : ℚ)^(n - k)) =
          ∑ a ∈ Finset.range (P.natDegree + 1), ∑ b ∈ Finset.range (Q.natDegree + 1), ((P.coeff a : ℚ) * (a : ℚ)^k) * ((Q.coeff b : ℚ) * (b : ℚ)^(n - k)) := by
        rw [Finset.sum_mul_sum]
      _ = ∑ a ∈ Finset.range (P.natDegree + 1), ∑ b ∈ Finset.range (Q.natDegree + 1), (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
        apply Finset.sum_congr rfl
        intro a _
        apply Finset.sum_congr rfl
        intro b _
        ring_nf
  rw [h₃]
  simp only [Finset.sum_div]

lemma term_factorial_to_choose (Pa Qb : ℚ) (a b k n : ℕ) (hk : k ≤ n) :
    Pa * Qb * (a : ℚ)^k * (b : ℚ)^(n - k) / (k.factorial * (n - k).factorial) =
    Pa * Qb * (a : ℚ)^k * (b : ℚ)^(n - k) * ((n.choose k : ℚ) / (n.factorial : ℚ)) := by
  have h₁ : (1 : ℚ) / (k.factorial * (n - k).factorial) = (n.choose k : ℚ) / (n.factorial : ℚ) := by
    have h₂ : (n.choose k : ℕ) * k.factorial * (n - k).factorial = n.factorial := by
      rw [Nat.choose_mul_factorial_mul_factorial hk]
    have h₃ : (n.choose k : ℚ) * (k.factorial : ℚ) * ((n - k).factorial : ℚ) = (n.factorial : ℚ) := by
      norm_cast at h₂ ⊢
    have h₄ : (k.factorial : ℚ) ≠ 0 := by positivity
    have h₅ : ((n - k).factorial : ℚ) ≠ 0 := by positivity
    have h₆ : (n.factorial : ℚ) ≠ 0 := by positivity
    field_simp [h₄, h₅, h₆] at h₃ ⊢
    nlinarith
  have h₂ : Pa * Qb * (a : ℚ)^k * (b : ℚ)^(n - k) / (k.factorial * (n - k).factorial : ℚ) =
      Pa * Qb * (a : ℚ)^k * (b : ℚ)^(n - k) * ((n.choose k : ℚ) / (n.factorial : ℚ)) := by
    have h₃ : (Pa * Qb * (a : ℚ)^k * (b : ℚ)^(n - k) : ℚ) / (k.factorial * (n - k).factorial : ℚ) =
        (Pa * Qb * (a : ℚ)^k * (b : ℚ)^(n - k) : ℚ) * ((1 : ℚ) / (k.factorial * (n - k).factorial : ℚ)) := by
      ring_nf
    rw [h₃]
    rw [h₁]
  exact h₂

lemma natDegree_one_sub_X_pow_le (k : ℕ) :
    (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ k := by
  have h₁ : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ max (Polynomial.natDegree (1 : Polynomial ℤ)) (Polynomial.natDegree (Polynomial.X ^ k : Polynomial ℤ)) := by
    apply Polynomial.natDegree_sub_le
  have h₂ : Polynomial.natDegree (1 : Polynomial ℤ) = 0 := by
    simp [Polynomial.natDegree_one]
  have h₃ : Polynomial.natDegree (Polynomial.X ^ k : Polynomial ℤ) = k := by
    simp [Polynomial.natDegree_pow, Polynomial.natDegree_X]
  have h₄ : max (Polynomial.natDegree (1 : Polynomial ℤ)) (Polynomial.natDegree (Polynomial.X ^ k : Polynomial ℤ)) ≤ k := by
    rw [h₂, h₃]
    exact max_le_iff.mpr ⟨by simp, by simp⟩
  calc
    (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ max (Polynomial.natDegree (1 : Polynomial ℤ)) (Polynomial.natDegree (Polynomial.X ^ k : Polynomial ℤ)) := h₁
    _ ≤ k := h₄

lemma sum_coeff_one_sub_X_pow (k : ℕ) (hk : 0 < k) :
    ∑ j ∈ Finset.range (k + 1), ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff j : ℚ) = 0 := by
  aesop

lemma exp_poly_sub_one_sub_X_pow_coeff_zero (k : ℕ) (hk : 0 < k) :
    (PowerSeries.coeff 0) (exp_poly_sub (1 - Polynomial.X ^ k)) = 0 := by
  unfold exp_poly_sub
  rw [PowerSeries.coeff_mk]
  simp only [pow_zero, mul_one, Nat.factorial_zero, Nat.cast_one, div_one]
  have h_deg : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ k := natDegree_one_sub_X_pow_le k
  have h_sum := sum_coeff_one_sub_X_pow k hk
  convert h_sum using 1
  apply Finset.sum_subset
  · intro j hj
    simp only [Finset.mem_range] at hj ⊢
    omega
  · intro j hj hj_not
    simp only [Finset.mem_range] at hj hj_not
    have : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree < j := by omega
    simp only [Int.cast_eq_zero]
    exact Polynomial.coeff_eq_zero_of_natDegree_lt this

lemma natDegree_one_sub_X_pow_eq (k : ℕ) (hk : 0 < k) :
    (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree = k := by
  have h₁ : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ k := by
    have h₂ : (1 : Polynomial ℤ).natDegree = 0 := by
      simp [Polynomial.natDegree_one]
    have h₃ : (Polynomial.X ^ k : Polynomial ℤ).natDegree = k := by
      simp [Polynomial.natDegree_pow, Polynomial.natDegree_X]
    have h₄ : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ max (1 : Polynomial ℤ).natDegree (Polynomial.X ^ k : Polynomial ℤ).natDegree := by
      apply Polynomial.natDegree_sub_le
    have h₅ : max (1 : Polynomial ℤ).natDegree (Polynomial.X ^ k : Polynomial ℤ).natDegree ≤ k := by
      simp [h₂, h₃]
    linarith
  have h₂ : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≥ k := by
    have h₃ : (1 - Polynomial.X ^ k : Polynomial ℤ).coeff k = -1 := by
      simp [Polynomial.coeff_sub, Polynomial.coeff_one, Polynomial.coeff_X_pow]
      cases k <;> simp_all
    have h₅ : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree ≥ k := by
      by_contra h₆
      have h₇ : (1 - Polynomial.X ^ k : Polynomial ℤ).natDegree < k := by
        linarith
      have h₈ : (1 - Polynomial.X ^ k : Polynomial ℤ).coeff k = 0 := by
        apply Polynomial.coeff_eq_zero_of_natDegree_lt
        exact_mod_cast h₇
      rw [h₃] at h₈
      norm_num at h₈
    exact h₅
  linarith

lemma one_sub_X_pow_term_eq_zero (k n j : ℕ) (_hk : 0 < k) (hn : 1 ≤ n)
    (_hj_mem : j ∈ Finset.range (k + 1)) (hj_ne : j ≠ k) :
    ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff j : ℚ) * (j : ℚ)^n = 0 := by
  have h₂ : ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff j : ℚ) = 0 ∨ (j : ℚ) = 0 := by
    by_cases h : j = 0
    · right
      norm_cast
    · left
      have h₈ : (1 - Polynomial.X ^ k : Polynomial ℤ).coeff j = 0 := by
        have h₉ : (1 - Polynomial.X ^ k : Polynomial ℤ).coeff j = (1 : Polynomial ℤ).coeff j - (Polynomial.X ^ k : Polynomial ℤ).coeff j := by
          simp [Polynomial.coeff_sub]
        rw [h₉]
        have h₁₀ : (1 : Polynomial ℤ).coeff j = if j = 0 then 1 else 0 := by
          simp [Polynomial.coeff_one]
        have h₁₁ : (Polynomial.X ^ k : Polynomial ℤ).coeff j = if j = k then 1 else 0 := by
          simp [Polynomial.coeff_X_pow]
        rw [h₁₀, h₁₁]
        split_ifs at *
        simp_all (config := {decide := true})
      norm_cast at h₈ ⊢
  cases h₂ with
  | inl h₂ =>
    rw [h₂]
    norm_num
  | inr h₂ =>
    have h₃ : (j : ℕ) = 0 := by norm_cast at h₂ ⊢
    have h₅ : (j : ℚ) ^ n = 0 := by
      have h₈ : (j : ℚ) = 0 := by simp_all
      rw [h₈]
      have h₁₁ : n ≠ 0 := by linarith
      simp [h₁₁]
    calc
      ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff j : ℚ) * (j : ℚ) ^ n = ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff j : ℚ) * 0 := by rw [h₅]
      _ = 0 := by ring

lemma one_sub_X_pow_term_at_k (k n : ℕ) (hk : 0 < k) :
    ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff k : ℚ) * (k : ℚ)^n = -((k : ℚ)^n) := by
  have h₁ : (1 - Polynomial.X ^ k : Polynomial ℤ).coeff k = (-1 : ℤ) := by
    simp [Polynomial.coeff_sub, Polynomial.coeff_one, Polynomial.coeff_X_pow]
    cases k <;> simp_all
  rw [h₁]
  norm_cast
  simp

lemma one_sub_X_pow_sum_eq (k n : ℕ) (hk : 0 < k) (hn : 1 ≤ n) :
    ∑ j ∈ Finset.range ((1 - Polynomial.X ^ k : Polynomial ℤ).natDegree + 1),
      ((1 - Polynomial.X ^ k : Polynomial ℤ).coeff j : ℚ) * (j : ℚ)^n = -((k : ℚ)^n) := by
  rw [natDegree_one_sub_X_pow_eq k hk]
  have hk_mem : k ∈ Finset.range (k + 1) := Finset.mem_range.mpr (Nat.lt_succ_self k)
  rw [Finset.sum_eq_single_of_mem k hk_mem]
  · exact one_sub_X_pow_term_at_k k n hk
  · intro j hj_mem hj_ne
    exact one_sub_X_pow_term_eq_zero k n j hk hn hj_mem hj_ne

lemma exp_poly_sub_one_sub_X_pow_coeff_pos (k n : ℕ) (hk : 0 < k) (hn : 1 ≤ n) :
    (PowerSeries.coeff n) (exp_poly_sub (1 - Polynomial.X ^ k)) = -((k : ℚ)^n) / (n.factorial : ℚ) := by
  rw [exp_poly_sub_coeff]
  rw [one_sub_X_pow_sum_eq k n hk hn]

lemma rhs_coeff_zero {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (i : Fin G.m) :
    (PowerSeries.coeff 0) (-(G.d i : ℚ) • (PowerSeries.X * G.scaledExpFactor i)) = 0 := by
  have h₁ : (PowerSeries.coeff 0) (PowerSeries.X * G.scaledExpFactor i) = 0 := by
    simpa using PowerSeries.coeff_X_mul_zero (G.scaledExpFactor i)
  have h₂ : (PowerSeries.coeff 0) (-(G.d i : ℚ) • (PowerSeries.X * G.scaledExpFactor i)) = (-(G.d i : ℚ)) * (PowerSeries.coeff 0) (PowerSeries.X * G.scaledExpFactor i) := by
    simp
  rw [h₂, h₁]
  ring_nf

lemma rhs_coeff_pos {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (i : Fin G.m)
    (n : ℕ) (hn : 1 ≤ n) :
    (PowerSeries.coeff n) (-(G.d i : ℚ) • (PowerSeries.X * G.scaledExpFactor i)) =
    -((G.d i : ℚ)^n) / (n.factorial : ℚ) := by
  rw [PowerSeries.coeff_smul]
  obtain ⟨k, hk⟩ := Nat.exists_eq_succ_of_ne_zero (Nat.one_le_iff_ne_zero.mp hn)
  subst hk
  rw [PowerSeries.coeff_succ_X_mul]
  simp only [NumericalSemigroupGenerators.scaledExpFactor, PowerSeries.coeff_mk]
  simp only [smul_eq_mul, Nat.succ_eq_add_one]
  ring

lemma single_factor_exp_transform {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (i : Fin G.m) :
    exp_poly_sub (1 - Polynomial.X ^ (G.d i)) =
    -(G.d i : ℚ) • (PowerSeries.X * G.scaledExpFactor i) := by
  apply PowerSeries.ext
  intro n
  rcases n.eq_zero_or_pos with rfl | hn
  · rw [exp_poly_sub_one_sub_X_pow_coeff_zero (G.d i) (G.hd_pos i)]
    rw [rhs_coeff_zero G i]
  · rw [exp_poly_sub_one_sub_X_pow_coeff_pos (G.d i) n (G.hd_pos i) hn]
    rw [rhs_coeff_pos G i n hn]

lemma prod_smul_eq_smul_prod (m : ℕ) (c : Fin m → ℚ) (f : Fin m → PowerSeries ℚ) :
    ∏ i : Fin m, (c i • f i) = (∏ i : Fin m, c i) • (∏ i : Fin m, f i) := by
  simp only [MvPowerSeries.smul_eq_C_mul]
  rw [Finset.prod_mul_distrib]
  rw [← map_prod]

lemma prod_X_mul_eq_X_pow_mul (m : ℕ) (f : Fin m → PowerSeries ℚ) :
    ∏ i : Fin m, (PowerSeries.X * f i) = PowerSeries.X ^ m * ∏ i : Fin m, f i := by
  simp_rw [Finset.prod_mul_distrib, Fin.prod_const]

lemma prod_neg_eq_neg_one_pow_mul (m : ℕ) (c : Fin m → ℚ) :
    ∏ i : Fin m, (-c i) = (-1 : ℚ) ^ m * ∏ i : Fin m, c i := by
  calc
    _ = ∏ i : Fin m, (-1 : ℚ) * c i := by
      apply Finset.prod_congr rfl
      intro i _
      ring
    _ = (∏ i : Fin m, (-1 : ℚ)) * ∏ i : Fin m, c i := by
      rw [Finset.prod_mul_distrib]
    _ = ((-1 : ℚ) ^ m) * ∏ i : Fin m, c i := by
      have h2 : ∏ i : Fin m, (-1 : ℚ) = (-1 : ℚ) ^ m := by
        simp [Finset.prod_const]
      rw [h2]

lemma prod_single_factor_eq_final_form {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    ∏ i : Fin G.m, (-(G.d i : ℚ) • (PowerSeries.X * G.scaledExpFactor i)) =
    ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series) := by
  rw [prod_smul_eq_smul_prod]
  rw [prod_X_mul_eq_X_pow_mul]
  have h1 : (∏ i : Fin G.m, -(G.d i : ℚ)) = (-1 : ℚ) ^ G.m * (G.pi_m : ℚ) := by
    rw [prod_neg_eq_neg_one_pow_mul]
    congr 1
    simp only [NumericalSemigroupGenerators.pi_m]
    rw [Nat.cast_prod]
  rw [h1]
  rfl

-- productPolynomial_exp_transform: defined later at productPolynomial_exp_transform'

lemma triple_sum_factorial_to_choose (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) / (k.factorial * (n - k).factorial) =
    ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) * ((n.choose k : ℚ) / (n.factorial : ℚ)) := by
  apply Finset.sum_congr rfl
  intro k hk
  apply Finset.sum_congr rfl
  intro a _
  apply Finset.sum_congr rfl
  intro b _
  have hkn : k ≤ n := Nat.lt_succ_iff.mp (Finset.mem_range.mp hk)
  exact term_factorial_to_choose (P.coeff a : ℚ) (Q.coeff b : ℚ) a b k n hkn

lemma factor_out_factorial (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) * ((n.choose k : ℚ) / (n.factorial : ℚ)) =
    (1 / n.factorial) * ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
  calc
    ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) * ((n.choose k : ℚ) / (n.factorial : ℚ)) = ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          ((1 : ℚ) / (n.factorial : ℚ)) * ((P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k)) := by
      apply Finset.sum_congr rfl
      intro k _
      apply Finset.sum_congr rfl
      intro a _
      apply Finset.sum_congr rfl
      intro b _
      ring_nf
    _ = (1 / (n.factorial : ℚ)) * ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
      simp [Finset.mul_sum, mul_assoc]
    _ = (1 / n.factorial) * ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
      simp_all [div_eq_mul_inv]

lemma sum_rearrange (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      ∑ a ∈ Finset.range (P.natDegree + 1),
        ∑ b ∈ Finset.range (Q.natDegree + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) =
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        ∑ k ∈ Finset.range (n + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
  rw [Finset.sum_comm]
  apply Finset.sum_congr rfl
  intro a _
  rw [Finset.sum_comm]

lemma factor_coeffs_from_inner_sum (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        ∑ k ∈ Finset.range (n + 1),
          (P.coeff a : ℚ) * (Q.coeff b : ℚ) * (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) =
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        (P.coeff a : ℚ) * (Q.coeff b : ℚ) *
        ∑ k ∈ Finset.range (n + 1), (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) := by
  apply Finset.sum_congr rfl
  intro a _
  apply Finset.sum_congr rfl
  intro b _
  rw [Finset.mul_sum]
  apply Finset.sum_congr rfl
  intro k _
  ring

lemma binomial_sum_eq_power (a b n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) = ((a : ℚ) + b)^n := by
  rw [add_pow]
  apply Finset.sum_congr rfl
  intro k hk
  have hkn : k ≤ n := Nat.lt_succ_iff.mp (Finset.mem_range.mp hk)
  simp only [mul_comm ((n.choose k : ℚ)) _]
  ring

lemma apply_binomial_to_inner_sum (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        (P.coeff a : ℚ) * (Q.coeff b : ℚ) *
        ∑ k ∈ Finset.range (n + 1), (n.choose k : ℚ) * (a : ℚ)^k * (b : ℚ)^(n - k) =
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        (P.coeff a : ℚ) * (Q.coeff b : ℚ) * ((a : ℚ) + b)^n := by
  apply Finset.sum_congr rfl
  intro a _
  apply Finset.sum_congr rfl
  intro b _
  rw [binomial_sum_eq_power]

lemma exp_poly_sub_mul_coeff_eq (P Q : Polynomial ℤ) (n : ℕ) :
    (PowerSeries.coeff n) (exp_poly_sub P * exp_poly_sub Q) =
    (∑ a ∈ Finset.range (P.natDegree + 1),
     ∑ b ∈ Finset.range (Q.natDegree + 1),
       (P.coeff a : ℚ) * (Q.coeff b : ℚ) * ((a : ℚ) + b)^n) / (n.factorial : ℚ) := by
  rw [coeff_mul_expand]
  rw [lhs_to_triple_sum]
  rw [triple_sum_factorial_to_choose]
  rw [factor_out_factorial]
  rw [sum_rearrange]
  rw [factor_coeffs_from_inner_sum]
  rw [apply_binomial_to_inner_sum]
  ring

lemma sum_extend_to_deg_sum (PQ : Polynomial ℤ) (n : ℕ) :
    ∑ j ∈ Finset.range (PQ.natDegree + 1), (PQ.coeff j : ℚ) * (j : ℚ)^n =
    ∑ j ∈ Finset.range (PQ.natDegree + 1), (PQ.coeff j : ℚ) * (j : ℚ)^n := rfl

lemma convolution_sum_eq (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ j ∈ Finset.range ((P * Q).natDegree + 1), ((P * Q).coeff j : ℚ) * (j : ℚ)^n =
    ∑ j ∈ Finset.range (P.natDegree + Q.natDegree + 1),
      ∑ ab ∈ Finset.antidiagonal j, (P.coeff ab.1 : ℚ) * (Q.coeff ab.2 : ℚ) * ((ab.1 : ℚ) + ab.2)^n := by
  have h_deg : (P * Q).natDegree ≤ P.natDegree + Q.natDegree := Polynomial.natDegree_mul_le
  rw [exp_poly_sub_sum_extend (P * Q) (P.natDegree + Q.natDegree + 1) n (by omega)]
  apply Finset.sum_congr rfl
  intro j hj
  have hj' : j < P.natDegree + Q.natDegree + 1 := Finset.mem_range.mp hj
  have h1 : ((P * Q).coeff j : ℚ) = ∑ k ∈ Finset.range (j + 1), (P.coeff k : ℚ) * (Q.coeff (j - k) : ℚ) := by
    rw [Polynomial.coeff_mul]
    simp only [Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk]
    norm_cast
  rw [h1]
  have h2 : (∑ k ∈ Finset.range (j + 1), (P.coeff k : ℚ) * (Q.coeff (j - k) : ℚ)) * (j : ℚ) ^ n =
      ∑ k ∈ Finset.range (j + 1), (P.coeff k : ℚ) * (Q.coeff (j - k) : ℚ) * (j : ℚ) ^ n := by
    rw [Finset.sum_mul]
  rw [h2]
  rw [Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk]
  apply Finset.sum_congr rfl
  intro k hk
  have hk' : k ≤ j := Nat.lt_succ_iff.mp (Finset.mem_range.mp hk)
  have h_eq : ((k : ℚ) + (j - k : ℕ)) = j := by
    simp only [Nat.cast_sub hk']
    ring
  simp only [h_eq]

lemma rectangle_subset_triangle (P Q : Polynomial ℤ) :
    Finset.range (P.natDegree + 1) ×ˢ Finset.range (Q.natDegree + 1) ⊆
    (Finset.range (P.natDegree + Q.natDegree + 1)).biUnion Finset.antidiagonal := by
  intro ab hab
  simp only [Finset.mem_product, Finset.mem_range] at hab
  simp only [Finset.mem_biUnion, Finset.mem_range, Finset.mem_antidiagonal]
  refine' ⟨ab.1 + ab.2, _, rfl⟩
  omega

lemma term_vanishes_outside_rectangle (P Q : Polynomial ℤ) (n : ℕ) (ab : ℕ × ℕ)
    (_h_in_tri : ab ∈ (Finset.range (P.natDegree + Q.natDegree + 1)).biUnion Finset.antidiagonal)
    (h_not_rect : ab ∉ Finset.range (P.natDegree + 1) ×ˢ Finset.range (Q.natDegree + 1)) :
    (P.coeff ab.1 : ℚ) * (Q.coeff ab.2 : ℚ) * ((ab.1 : ℚ) + ab.2)^n = 0 := by
  have h₁ : ab.1 > P.natDegree ∨ ab.2 > Q.natDegree := by
    by_contra! h
    have h₄ : ab.1 < P.natDegree + 1 := by linarith
    have h₅ : ab.2 < Q.natDegree + 1 := by linarith
    have h₆ : ab ∈ Finset.range (P.natDegree + 1) ×ˢ Finset.range (Q.natDegree + 1) := by
      simp [Finset.mem_product, Finset.mem_range, h₄, h₅]
    contradiction
  cases h₁ with
  | inl h₁ =>
    have h₂ : (P.coeff ab.1 : ℚ) = 0 := by
      have h₃ : (P.natDegree : ℕ) < ab.1 := h₁
      have h₄ : P.coeff ab.1 = 0 := Polynomial.coeff_eq_zero_of_natDegree_lt h₃
      norm_cast
    rw [h₂]
    simp
  | inr h₁ =>
    have h₂ : (Q.coeff ab.2 : ℚ) = 0 := by
      have h₃ : (Q.natDegree : ℕ) < ab.2 := h₁
      have h₄ : Q.coeff ab.2 = 0 := Polynomial.coeff_eq_zero_of_natDegree_lt h₃
      norm_cast
    rw [h₂]
    simp

lemma pairwiseDisjoint_antidiagonal (s : Finset ℕ) :
    (s : Set ℕ).PairwiseDisjoint Finset.antidiagonal := by
  intro j₁ _ j₂ _ hne
  rw [Function.onFun, Finset.disjoint_left]
  intro x hx₁ hx₂
  rw [Finset.mem_antidiagonal] at hx₁ hx₂
  exact hne (hx₁.symm.trans hx₂)

lemma nested_sum_eq_biUnion_sum (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ j ∈ Finset.range (P.natDegree + Q.natDegree + 1),
      ∑ ab ∈ Finset.antidiagonal j, (P.coeff ab.1 : ℚ) * (Q.coeff ab.2 : ℚ) * ((ab.1 : ℚ) + ab.2)^n =
    ∑ ab ∈ (Finset.range (P.natDegree + Q.natDegree + 1)).biUnion Finset.antidiagonal,
      (P.coeff ab.1 : ℚ) * (Q.coeff ab.2 : ℚ) * ((ab.1 : ℚ) + ab.2)^n := by
  exact (Finset.sum_biUnion (pairwiseDisjoint_antidiagonal _)).symm

lemma product_sum_eq_nested (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ ab ∈ Finset.range (P.natDegree + 1) ×ˢ Finset.range (Q.natDegree + 1),
      (P.coeff ab.1 : ℚ) * (Q.coeff ab.2 : ℚ) * ((ab.1 : ℚ) + ab.2)^n =
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        (P.coeff a : ℚ) * (Q.coeff b : ℚ) * ((a : ℚ) + b)^n := by
  rw [Finset.sum_product]

lemma triangular_to_rectangular_sum (P Q : Polynomial ℤ) (n : ℕ) :
    ∑ j ∈ Finset.range (P.natDegree + Q.natDegree + 1),
      ∑ ab ∈ Finset.antidiagonal j, (P.coeff ab.1 : ℚ) * (Q.coeff ab.2 : ℚ) * ((ab.1 : ℚ) + ab.2)^n =
    ∑ a ∈ Finset.range (P.natDegree + 1),
      ∑ b ∈ Finset.range (Q.natDegree + 1),
        (P.coeff a : ℚ) * (Q.coeff b : ℚ) * ((a : ℚ) + b)^n := by
  rw [nested_sum_eq_biUnion_sum]
  rw [← product_sum_eq_nested]
  symm
  apply Finset.sum_subset (rectangle_subset_triangle P Q)
  intro ab h_in_tri h_not_rect
  exact term_vanishes_outside_rectangle P Q n ab h_in_tri h_not_rect

lemma exp_poly_sub_prod_coeff_eq (P Q : Polynomial ℤ) (n : ℕ) :
    (PowerSeries.coeff n) (exp_poly_sub (P * Q)) =
    (∑ a ∈ Finset.range (P.natDegree + 1),
     ∑ b ∈ Finset.range (Q.natDegree + 1),
       (P.coeff a : ℚ) * (Q.coeff b : ℚ) * ((a : ℚ) + b)^n) / (n.factorial : ℚ) := by
  rw [exp_poly_sub_coeff]
  rw [convolution_sum_eq]
  rw [triangular_to_rectangular_sum]

lemma exp_poly_sub_mul (P Q : Polynomial ℤ) :
    exp_poly_sub (P * Q) = exp_poly_sub P * exp_poly_sub Q := by
  apply PowerSeries.ext
  intro n
  rw [exp_poly_sub_prod_coeff_eq, exp_poly_sub_mul_coeff_eq]

lemma exp_poly_sub_prod' {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    exp_poly_sub G.productPolynomial =
    ∏ i : Fin G.m, exp_poly_sub (1 - Polynomial.X ^ (G.d i)) := by
  unfold NumericalSemigroupGenerators.productPolynomial
  have h := Finset.prod_hom_rel (s := Finset.univ) (r := fun P F => exp_poly_sub P = F)
    (f := fun i : Fin G.m => (1 - Polynomial.X ^ (G.d i)))
    (g := fun i : Fin G.m => exp_poly_sub (1 - Polynomial.X ^ (G.d i)))
  rw [h]
  · exact exp_poly_sub_one
  · intro a b c hbc
    rw [exp_poly_sub_mul, hbc]

lemma productPolynomial_exp_transform {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    exp_poly_sub G.productPolynomial =
    ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series) := by
  rw [exp_poly_sub_prod']
  conv_lhs => arg 2; ext i; rw [single_factor_exp_transform]
  exact prod_single_factor_eq_final_form G

lemma coeff_one_sub_exp (n : ℕ) :
    (PowerSeries.coeff n) (1 - PowerSeries.exp ℚ) =
    if n = 0 then 0 else -1 / (n.factorial : ℚ) := by
  rw [map_sub, PowerSeries.coeff_one, PowerSeries.coeff_exp]
  simp only [Algebra.algebraMap_self, RingHom.id_apply]
  split_ifs with h
  · simp [h]
  · ring

lemma binomial_sum_minus_j_pow (j n : ℕ) :
    (∑ k ∈ Finset.range (n + 1), if k = 0 then 0 else (n.choose k : ℚ) * (j : ℚ)^(n - k)) =
    ((j : ℚ) + 1)^n - (j : ℚ)^n := by
  have h := add_pow (1 : ℚ) (j : ℚ) n
  simp only [one_pow, add_comm] at h
  rw [Finset.sum_eq_add_sum_diff_singleton (Finset.mem_range.mpr (Nat.zero_lt_succ n))]
  simp only [↓reduceIte, zero_add]
  have hne : ∀ x ∈ (Finset.range n.succ \ {0}), x ≠ 0 := by
    simp [Finset.mem_sdiff, Finset.mem_singleton]
  rw [Finset.sum_congr rfl (fun x hx => if_neg (hne x hx))]
  rw [Finset.sum_eq_add_sum_diff_singleton (Finset.mem_range.mpr (Nat.zero_lt_succ n))] at h
  simp only [Nat.choose_zero_right, Nat.cast_one, one_mul, Nat.sub_zero] at h
  calc
    ∑ k ∈ Finset.range n.succ \ {0}, (n.choose k : ℚ) * (j : ℚ)^(n - k)
      = ∑ k ∈ Finset.range n.succ \ {0}, (j : ℚ)^(n - k) * (n.choose k : ℚ) := by
        apply Finset.sum_congr rfl; intro k _; ring
    _ = ((j : ℚ) + 1)^n - (j : ℚ)^n := by linarith

lemma coeff_mul_as_range_sum {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) ((1 - PowerSeries.exp ℚ) * partialSumGenFunc G) =
    ∑ k ∈ Finset.range (n + 1),
      (PowerSeries.coeff k) (1 - PowerSeries.exp ℚ) *
      (PowerSeries.coeff (n - k)) (partialSumGenFunc G) := by
  rw [PowerSeries.coeff_mul]
  exact Finset.Nat.sum_antidiagonal_eq_sum_range_succ
    (fun k m => (PowerSeries.coeff k) (1 - PowerSeries.exp ℚ) * (PowerSeries.coeff m) (partialSumGenFunc G)) n

lemma coeff_sum_substitute {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      (PowerSeries.coeff k) (1 - PowerSeries.exp ℚ) *
      (PowerSeries.coeff (n - k)) (partialSumGenFunc G) =
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else -1 / (k.factorial : ℚ)) *
      ((∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
        (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) /
       ((n - k).factorial : ℚ)) := by
  apply Finset.sum_congr rfl
  intro k _
  rw [coeff_one_sub_exp, partialSumGenFunc_coeff]

lemma drop_k_eq_zero_term {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else -1 / (k.factorial : ℚ)) *
      ((∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
        (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) /
       ((n - k).factorial : ℚ)) =
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (-1 / (k.factorial : ℚ)) *
        ((∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
          (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) /
         ((n - k).factorial : ℚ))) := by
  apply Finset.sum_congr rfl
  intro k _
  split_ifs with hk
  · simp
  · ring

lemma summand_eq_for_pos {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n k : ℕ)
    (_hk_pos : k ≠ 0) (hk_le : k ≤ n) :
    (-1 / (k.factorial : ℚ)) *
      ((∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
        (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) /
       ((n - k).factorial : ℚ)) =
    (-(n.choose k : ℚ) / (n.factorial : ℚ)) *
      (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
        (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) := by
  have h₈ : (n.choose k : ℚ) / (n.factorial : ℚ) = 1 / (k.factorial : ℚ) / ((n - k).factorial : ℚ) := by
    have h₉ : (n.choose k : ℕ) * k.factorial * (n - k).factorial = n.factorial := by
      rw [← Nat.choose_mul_factorial_mul_factorial hk_le]
    have h₁₂ : (n.choose k : ℚ) * (k.factorial : ℚ) * ((n - k).factorial : ℚ) = (n.factorial : ℚ) := by
      norm_cast at h₉ ⊢
    have h₁₄ : (n.factorial : ℚ) ≠ 0 := by positivity
    have h₁₅ : (k.factorial : ℚ) ≠ 0 := by positivity
    have h₁₆ : ((n - k).factorial : ℚ) ≠ 0 := by positivity
    field_simp [h₁₄, h₁₅, h₁₆] at h₁₂ ⊢
    nlinarith
  calc
    (-1 / (k.factorial : ℚ)) * ((∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) / ((n - k).factorial : ℚ))
      = (-(∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) : ℚ) / ((k.factorial : ℚ) * ((n - k).factorial : ℚ)) := by ring_nf
      _ = (-(∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) : ℚ) * (1 / ((k.factorial : ℚ) * ((n - k).factorial : ℚ))) := by field_simp
      _ = (-(∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) : ℚ) * (1 / (k.factorial : ℚ) / ((n - k).factorial : ℚ)) := by field_simp
      _ = (-(∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) : ℚ) * ((n.choose k : ℚ) / (n.factorial : ℚ)) := by rw [h₈]
      _ = (-(n.choose k : ℚ) / (n.factorial : ℚ)) * (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1), (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) := by ring_nf

lemma apply_factorial_identity {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (-1 / (k.factorial : ℚ)) *
        ((∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
          (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k)) /
         ((n - k).factorial : ℚ))) =
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (-(n.choose k : ℚ) / (n.factorial : ℚ)) *
        (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
          (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k))) := by
  apply Finset.sum_congr rfl
  intro k hk
  split_ifs with hk_zero
  · rfl
  · have hk_le : k ≤ n := by
      simp only [Finset.mem_range] at hk
      omega
    exact summand_eq_for_pos G n k hk_zero hk_le

lemma factor_neg_choose_div_factorial (D n : ℕ) (coeff : ℕ → ℚ) :
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (-(n.choose k : ℚ) / (n.factorial : ℚ)) *
        (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) =
    (-1 / (n.factorial : ℚ)) *
    (∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (n.choose k : ℚ) *
        (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k)))) := by
  have h₁ : ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (-(n.choose k : ℚ) / (n.factorial : ℚ)) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) = ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (-1 / (n.factorial : ℚ)) * ((n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k)))) := by
    apply Finset.sum_congr rfl
    intro k _
    by_cases hk0 : k = 0
    · simp [hk0]
    · have h₂ : (-(n.choose k : ℚ) / (n.factorial : ℚ)) = (-1 / (n.factorial : ℚ)) * (n.choose k : ℚ) := by
        field_simp [Nat.cast_ne_zero, Nat.factorial_ne_zero]
      rw [h₂]; ring_nf
  have h₂ : ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (-1 / (n.factorial : ℚ)) * ((n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k)))) = (-1 / (n.factorial : ℚ)) * ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) := by
    calc
      ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (-1 / (n.factorial : ℚ)) * ((n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k)))) =
          ∑ k ∈ Finset.range (n + 1), (-1 / (n.factorial : ℚ)) * (if k = 0 then 0 else (n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) := by
        apply Finset.sum_congr rfl
        intro k _
        by_cases hk0 : k = 0
        · simp [hk0]
        · have h₄ : (if k = 0 then 0 else (-1 / (n.factorial : ℚ)) * ((n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k)))) = (-1 / (n.factorial : ℚ)) * (if k = 0 then 0 else (n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) := by
            simp [hk0]
          rw [h₄]
      _ = (-1 / (n.factorial : ℚ)) * ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) := by
        rw [Finset.mul_sum]
  calc
    ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (-(n.choose k : ℚ) / (n.factorial : ℚ)) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) = ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (-1 / (n.factorial : ℚ)) * ((n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k)))) := h₁
    _ = (-1 / (n.factorial : ℚ)) * ∑ k ∈ Finset.range (n + 1), (if k = 0 then 0 else (n.choose k : ℚ) * (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) := h₂

lemma swap_and_apply_binomial (D n : ℕ) (coeff : ℕ → ℚ) :
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (n.choose k : ℚ) *
        (∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))) =
    ∑ j ∈ Finset.range (D + 1), coeff j * (((j : ℚ) + 1)^n - (j : ℚ)^n) := by
  conv_lhs =>
    arg 2
    ext k
    rw [show (if k = 0 then (0 : ℚ) else (n.choose k : ℚ) * ∑ j ∈ Finset.range (D + 1), coeff j * (j : ℚ)^(n - k))
           = ∑ j ∈ Finset.range (D + 1), (if k = 0 then 0 else (n.choose k : ℚ) * (coeff j * (j : ℚ)^(n - k)))
        by split_ifs with h
           · simp only [Finset.sum_const_zero]
           · rw [Finset.mul_sum]]
  rw [Finset.sum_comm]
  congr 1
  ext j
  conv_lhs =>
    arg 2
    ext k
    rw [show (if k = 0 then (0 : ℚ) else (n.choose k : ℚ) * (coeff j * (j : ℚ)^(n - k)))
           = coeff j * (if k = 0 then 0 else (n.choose k : ℚ) * (j : ℚ)^(n - k))
        by split_ifs <;> ring]
  rw [← Finset.mul_sum]
  rw [binomial_sum_minus_j_pow]

lemma factor_and_apply_binomial {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1),
      (if k = 0 then 0 else
        (-(n.choose k : ℚ) / (n.factorial : ℚ)) *
        (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
          (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) * (j : ℚ)^(n - k))) =
    (-1 / (n.factorial : ℚ)) *
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) := by
  have step1 := factor_neg_choose_div_factorial G.hilbertNumerator.natDegree n
    (fun j => ∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ))
  have step2 := swap_and_apply_binomial G.hilbertNumerator.natDegree n
    (fun j => ∑ m ∈ Finset.range (j + 1), (G.productPolynomial.coeff m : ℚ))
  rw [step1, step2]

lemma coeff_lhs_expand {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) ((1 - PowerSeries.exp ℚ) * partialSumGenFunc G) =
    (-1 / (n.factorial : ℚ)) *
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) *
      (((j : ℚ) + 1)^n - (j : ℚ)^n)) := by
  rw [coeff_mul_as_range_sum]
  rw [coeff_sum_substitute]
  rw [drop_k_eq_zero_term]
  rw [apply_factorial_identity]
  rw [factor_and_apply_binomial]

lemma coeff_lhs_eq_rhs {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) ((1 - PowerSeries.exp ℚ) * partialSumGenFunc G) =
    (PowerSeries.coeff n) (exp_poly_sub G.productPolynomial) := by
  rw [coeff_lhs_expand, telescoping_sum, exp_poly_sub_coeff]
  ring

lemma partialSumGenFunc_mul_one_sub_exp {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    (1 - PowerSeries.exp ℚ) * partialSumGenFunc G = exp_poly_sub G.productPolynomial := by
  ext n
  exact coeff_lhs_eq_rhs G n

lemma partialSumGenFunc_identity {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) :
    PowerSeries.X * partialSumGenFunc G =
    ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.B_series) := by
  have h1 : (1 - PowerSeries.exp ℚ) * partialSumGenFunc G = exp_poly_sub G.productPolynomial :=
    partialSumGenFunc_mul_one_sub_exp G
  have h2 : exp_poly_sub G.productPolynomial =
      ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series) :=
    productPolynomial_exp_transform G
  have h3 : (1 - PowerSeries.exp ℚ) * partialSumGenFunc G =
      ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series) := h1.trans h2
  have h4 : (PowerSeries.exp ℚ - 1) * partialSumGenFunc G =
      ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series) := by
    have neg_eq : PowerSeries.exp ℚ - 1 = -(1 - PowerSeries.exp ℚ) := by ring
    rw [neg_eq, neg_mul, h3]
    rw [show -(((-1 : ℚ)^G.m * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series)) =
        (-((-1 : ℚ)^G.m * (G.pi_m : ℚ))) • (PowerSeries.X ^ G.m * G.A_series)
        from (neg_smul _ _).symm]
    congr 1
    ring
  have h5 : bernoulliPowerSeries ℚ * (PowerSeries.exp ℚ - 1) * partialSumGenFunc G =
      bernoulliPowerSeries ℚ * (((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series)) := by
    rw [mul_assoc, h4]
  have h6 : bernoulliPowerSeries ℚ * (PowerSeries.exp ℚ - 1) = PowerSeries.X :=
    bernoulliPowerSeries_mul_exp_sub_one ℚ
  rw [h6] at h5
  have h7 : bernoulliPowerSeries ℚ * (((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.A_series)) =
      ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.B_series) := by
    simp only [NumericalSemigroupGenerators.B_series, mul_smul_comm]
    ring_nf
  rw [h7] at h5
  exact h5

lemma cond_equiv (m n : ℕ) : (m ≤ n + 1) ↔ (m - 1 ≤ n) := by aesop

lemma index_eq (m n : ℕ) (hm : 0 < m) (h : m ≤ n + 1) : (n + 1) - m = n - (m - 1) := by omega

lemma coeff_X_pow_shift {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff (n + 1)) (PowerSeries.X ^ G.m * G.B_series) =
    (PowerSeries.coeff n) (PowerSeries.X ^ (G.m - 1) * G.B_series) := by
  rw [PowerSeries.coeff_X_pow_mul', PowerSeries.coeff_X_pow_mul']
  simp only [cond_equiv]
  split_ifs with h
  · rw [index_eq G.m n G.hm_pos (cond_equiv G.m n |>.mpr h)]
  · rfl

lemma partialSumGenFunc_coeff_eq_bernoulli_term {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (PowerSeries.coeff n) (partialSumGenFunc G) =
    ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) *
      (PowerSeries.coeff n) (PowerSeries.X ^ (G.m - 1) * G.B_series) := by
  have h_identity := partialSumGenFunc_identity G
  have h_coeff : (PowerSeries.coeff (n + 1)) (PowerSeries.X * partialSumGenFunc G) =
      (PowerSeries.coeff (n + 1)) (((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) • (PowerSeries.X ^ G.m * G.B_series)) := by
    rw [h_identity]
  rw [PowerSeries.coeff_succ_X_mul] at h_coeff
  rw [PowerSeries.coeff_smul] at h_coeff
  rw [coeff_X_pow_shift G n] at h_coeff
  simp only [smul_eq_mul] at h_coeff
  exact h_coeff

lemma sum_part_equals_bernoulli_term_natDegree {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ k ∈ Finset.range (j + 1), (G.productPolynomial.coeff k : ℚ)) * (j : ℚ)^n) / (n.factorial : ℚ) =
    ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) *
      (PowerSeries.coeff n) (PowerSeries.X ^ (G.m - 1) * G.B_series) := by
  rw [← partialSumGenFunc_coeff, partialSumGenFunc_coeff_eq_bernoulli_term]

lemma gap_le_sup {S : NumericalSemigroup} (g : ℕ) (hg : g ∈ S.gaps) : g ≤ S.gaps.sup id :=
  Finset.le_sup (f := id) hg

lemma neg_neg_one_pow_eq (m : ℕ) : -((-1 : ℤ) ^ m) = (-1 : ℤ) ^ (m + 1) := by ring

lemma gap_sum_at_jstar {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (hnonempty : S.gaps.Nonempty) :
    ∑ g ∈ S.gaps.filter (· ≤ G.productPolynomial.natDegree + S.gaps.sup id),
      G.productPolynomial.coeff (G.productPolynomial.natDegree + S.gaps.sup id - g) =
    (-1) ^ G.m := by
  obtain ⟨gmax, hgmax_mem, hgmax_eq⟩ := Finset.exists_mem_eq_sup S.gaps hnonempty id
  simp only [id_eq] at hgmax_eq
  have hfilter : S.gaps.filter (· ≤ G.productPolynomial.natDegree + S.gaps.sup id) = S.gaps := by
    apply Finset.filter_true_of_mem
    intro g hg
    calc g ≤ S.gaps.sup id := Finset.le_sup (f := id) hg
         _ ≤ G.productPolynomial.natDegree + S.gaps.sup id := le_add_self
  rw [hfilter]
  rw [Finset.sum_eq_single_of_mem gmax hgmax_mem]
  · have hsub : G.productPolynomial.natDegree + S.gaps.sup id - gmax = G.productPolynomial.natDegree := by
      rw [hgmax_eq, Nat.add_sub_cancel]
    rw [hsub, Polynomial.coeff_natDegree, productPolynomial_leadingCoeff G]
  · intro g hg hne
    apply Polynomial.coeff_eq_zero_of_natDegree_lt
    have hle : g ≤ gmax := by
      have := Finset.le_sup (f := id) hg
      simp only [id_eq, hgmax_eq] at this
      exact this
    have hlt : g < gmax := lt_of_le_of_ne hle hne
    simp only [hgmax_eq]
    omega

lemma hilbertNumerator_coeff_at_jstar {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (hnonempty : S.gaps.Nonempty) :
    G.hilbertNumerator.coeff (G.productPolynomial.natDegree + S.gaps.sup id) = (-1 : ℤ) ^ (G.m + 1) := by
  set jstar := G.productPolynomial.natDegree + S.gaps.sup id with hjstar_def
  have hjstar_bound : jstar < G.hilbertNumeratorDegBound := by
    unfold NumericalSemigroupGenerators.hilbertNumeratorDegBound
    omega
  have hcoeff : G.hilbertNumerator.coeff jstar =
      (∑ k ∈ Finset.range (jstar + 1), G.productPolynomial.coeff k) -
      (∑ g ∈ S.gaps.filter (· ≤ jstar), G.productPolynomial.coeff (jstar - g)) := by
    simp only [NumericalSemigroupGenerators.hilbertNumerator]
    simp [Polynomial.coeff_monomial, Finset.mem_range, hjstar_bound]
  have h1 : ∑ k ∈ Finset.range (jstar + 1), G.productPolynomial.coeff k = 0 := by
    have hge : 1 ≤ S.gaps.sup id := by
      obtain ⟨gmax, hgmax_mem, hgmax_eq⟩ := Finset.exists_mem_eq_sup S.gaps hnonempty id
      simp only [id_eq] at hgmax_eq
      rw [hgmax_eq]
      have hgap_pos : 0 < gmax := by
        by_contra h0
        push_neg at h0
        interval_cases gmax
        · have : (0 : ℕ) ∈ S.gaps := hgmax_mem
          have hmem : 0 ∈ S.carrier := S.zero_mem
          have hnotmem : 0 ∉ S.carrier := by
            have : S.gaps = S.finite_complement.toFinset := rfl
            rw [this] at hgmax_mem
            rw [Set.Finite.mem_toFinset] at hgmax_mem
            exact hgmax_mem
          contradiction
      omega
    have h1 := NumericalSemigroupGenerators.sum_coeff_large_eq_sum_coeff_deg G jstar (by omega)
    rw [h1, NumericalSemigroupGenerators.sum_coeff_eq_eval_one G, NumericalSemigroupGenerators.productPolynomial_eval_one G]
  rw [hcoeff, h1, zero_sub]
  have hgap := gap_sum_at_jstar G hnonempty
  simp only at hgap
  rw [hgap, neg_neg_one_pow_eq]

lemma neg_one_pow_ne_zero' (m : ℕ) : ((-1 : ℤ) ^ (m + 1)) ≠ 0 := by
  exact Int.neg_one_pow_ne_zero

lemma hilbertNumerator_natDegree_ge_jstar {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S)
    (hnonempty : S.gaps.Nonempty) :
    G.productPolynomial.natDegree + S.gaps.sup id ≤ G.hilbertNumerator.natDegree := by
  apply Polynomial.le_natDegree_of_ne_zero
  rw [hilbertNumerator_coeff_at_jstar G hnonempty]
  exact neg_one_pow_ne_zero' G.m

lemma hilbertNumerator_natDegree_ge_prod_plus_gap {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g : ℕ) (hg : g ∈ S.gaps) :
    G.productPolynomial.natDegree + g ≤ G.hilbertNumerator.natDegree := by
  have hnonempty : S.gaps.Nonempty := ⟨g, hg⟩
  have hle : g ≤ S.gaps.sup id := gap_le_sup g hg
  calc G.productPolynomial.natDegree + g
      ≤ G.productPolynomial.natDegree + S.gaps.sup id := by omega
    _ ≤ G.hilbertNumerator.natDegree := hilbertNumerator_natDegree_ge_jstar G hnonempty

lemma forward_map_mem {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g j : ℕ)
    (hj : j ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·)) :
    j - g ∈ Finset.range (G.hilbertNumerator.natDegree + 1 - g) := by
  simp only [Finset.mem_filter, Finset.mem_range] at hj ⊢
  omega

lemma inverse_map_mem {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g k : ℕ)
    (hk : k ∈ Finset.range (G.hilbertNumerator.natDegree + 1 - g)) :
    k + g ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·) := by
  simp only [Finset.mem_filter, Finset.mem_range] at hk ⊢
  omega

lemma forward_inverse_id' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g j : ℕ)
    (hj : j ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·)) :
    (j - g) + g = j := by aesop

lemma inverse_forward_id' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g k : ℕ)
    (_hk : k ∈ Finset.range (G.hilbertNumerator.natDegree + 1 - g)) :
    (k + g) - g = k := by aesop

lemma summand_eq' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n g j : ℕ)
    (hj : j ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·)) :
    (G.productPolynomial.coeff (j - g) : ℚ) * (j : ℚ)^n =
    (G.productPolynomial.coeff (j - g) : ℚ) * ((↑(j - g) : ℚ) + (g : ℚ))^n := by aesop

lemma inner_sum_reindex {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n g : ℕ) (_hg : g ∈ S.gaps) :
    ∑ j ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·),
      (G.productPolynomial.coeff (j - g) : ℚ) * (j : ℚ)^n =
    ∑ k ∈ Finset.range (G.hilbertNumerator.natDegree + 1 - g),
      (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n := by
  apply Finset.sum_bij'
    (i := fun j _ => j - g)
    (j := fun k _ => k + g)
    (hi := fun j hj => forward_map_mem G g j hj)
    (hj := fun k hk => inverse_map_mem G g k hk)
  · intro j hj
    exact forward_inverse_id' G g j hj
  · intro k hk
    exact inverse_forward_id' G g k hk
  · intro j hj
    exact summand_eq' G n g j hj

lemma range_subset_from_degree_bound' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g : ℕ) (hg : g ∈ S.gaps) :
    Finset.range (G.productPolynomial.natDegree + 1) ⊆
    Finset.range (G.hilbertNumerator.natDegree + 1 - g) := by
  rw [Finset.range_subset_range]
  have h := hilbertNumerator_natDegree_ge_prod_plus_gap G g hg
  omega

lemma summand_zero_beyond_degree' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (g n k : ℕ)
    (_hk : k ∈ Finset.range (G.hilbertNumerator.natDegree + 1 - g))
    (hk_not : k ∉ Finset.range (G.productPolynomial.natDegree + 1)) :
    (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n = 0 := by
  have h₁ : (G.productPolynomial.coeff k : ℤ) = 0 := by
    have h₂ : k > G.productPolynomial.natDegree := by
      have h₃ : k ≥ G.productPolynomial.natDegree + 1 := by
        by_contra h
        have h₄ : k < G.productPolynomial.natDegree + 1 := by linarith
        have h₅ : k ∈ Finset.range (G.productPolynomial.natDegree + 1) := by
          simp [Finset.mem_range]
          omega
        exact hk_not h₅
      omega
    have h₃ : G.productPolynomial.coeff k = 0 := by
      apply Polynomial.coeff_eq_zero_of_natDegree_lt
      exact h₂
    norm_cast at h₃ ⊢
  have h₂ : (G.productPolynomial.coeff k : ℚ) = 0 := by
    norm_cast at h₁ ⊢
  calc
    (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ)) ^ n = 0 * ((k : ℚ) + (g : ℚ)) ^ n := by rw [h₂]
    _ = 0 := by simp

lemma inner_sum_extend_range' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n g : ℕ) (hg : g ∈ S.gaps) :
    ∑ k ∈ Finset.range (G.hilbertNumerator.natDegree + 1 - g),
      (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n =
    ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n := by
  symm
  apply Finset.sum_subset (range_subset_from_degree_bound' G g hg)
  intro k hk hk_not
  exact summand_zero_beyond_degree' G g n k hk hk_not

lemma distribute_j_pow_into_sum {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n j : ℕ) :
    (∑ g ∈ S.gaps.filter (· ≤ j), (G.productPolynomial.coeff (j - g) : ℚ)) * (j : ℚ)^n =
    ∑ g ∈ S.gaps.filter (· ≤ j), (G.productPolynomial.coeff (j - g) : ℚ) * (j : ℚ)^n := by
  rw [Finset.sum_mul]

lemma sum_exchange_condition' {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    ∀ (j : ℕ) (g : ℕ),
      (j ∈ Finset.range (G.hilbertNumerator.natDegree + 1) ∧
       g ∈ S.gaps.filter (· ≤ j)) ↔
      (j ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·) ∧
       g ∈ S.gaps) := by
  aesop

lemma distribute_and_swap_sums {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ g ∈ S.gaps.filter (· ≤ j), (G.productPolynomial.coeff (j - g) : ℚ)) * (j : ℚ)^n =
    ∑ g ∈ S.gaps, ∑ j ∈ (Finset.range (G.hilbertNumerator.natDegree + 1)).filter (g ≤ ·),
      (G.productPolynomial.coeff (j - g) : ℚ) * (j : ℚ)^n := by
  conv_lhs =>
    congr
    · skip
    · ext j
      rw [distribute_j_pow_into_sum G n j]
  exact Finset.sum_comm' (sum_exchange_condition' G)

lemma gap_sum_exchange {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ g ∈ S.gaps.filter (· ≤ j), (G.productPolynomial.coeff (j - g) : ℚ)) * (j : ℚ)^n =
    ∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n := by
  rw [distribute_and_swap_sums]
  apply Finset.sum_congr rfl
  intro g hg
  rw [inner_sum_reindex G n g hg, inner_sum_extend_range' G n g hg]

lemma binomial_sum_eq_add_pow (a b : ℚ) (n : ℕ) :
    ∑ k ∈ Finset.range (n + 1), (n.choose k : ℚ) * a^k * b^(n - k) = (a + b)^n := by
  have h₁ : (a + b)^n = ∑ k ∈ Finset.range (n + 1), (a : ℚ)^k * (b : ℚ)^(n - k) * (n.choose k : ℚ) := by
    rw [add_pow]
  have h₂ : ∑ k ∈ Finset.range (n + 1), (n.choose k : ℚ) * a^k * b^(n - k) =
      ∑ k ∈ Finset.range (n + 1), (a : ℚ)^k * (b : ℚ)^(n - k) * (n.choose k : ℚ) := by
    apply Finset.sum_congr rfl
    intro k _
    have h₃ : (n.choose k : ℚ) * a ^ k * b ^ (n - k) = (a : ℚ) ^ k * (b : ℚ) ^ (n - k) * (n.choose k : ℚ) := by
      ring_nf
    rw [h₃]
  have h₃ : ∑ k ∈ Finset.range (n + 1), (n.choose k : ℚ) * a^k * b^(n - k) = (a + b)^n := by
    calc
      _ = ∑ k ∈ Finset.range (n + 1), (a : ℚ) ^ k * (b : ℚ) ^ (n - k) * (n.choose k : ℚ) := by rw [h₂]
      _ = (a + b) ^ n := by
        linarith
  apply h₃

lemma triple_sum_exchange (gaps : Finset ℕ) (degP : ℕ) (coeff : ℕ → ℚ) (n : ℕ) :
    ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), ∑ r ∈ Finset.range (n + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
    ∑ r ∈ Finset.range (n + 1), ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
  have h₁ : ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), ∑ r ∈ Finset.range (n + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
    ∑ g ∈ gaps, ∑ r ∈ Finset.range (n + 1), ∑ k ∈ Finset.range (degP + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
    apply Finset.sum_congr rfl
    intro g _
    have h₂ : ∑ k ∈ Finset.range (degP + 1), ∑ r ∈ Finset.range (n + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
      ∑ r ∈ Finset.range (n + 1), ∑ k ∈ Finset.range (degP + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
      apply Finset.sum_comm
    rw [h₂]
  have h₂ : ∑ g ∈ gaps, ∑ r ∈ Finset.range (n + 1), ∑ k ∈ Finset.range (degP + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
    ∑ r ∈ Finset.range (n + 1), ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
    have h₃ : ∑ g ∈ gaps, ∑ r ∈ Finset.range (n + 1), ∑ k ∈ Finset.range (degP + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
      ∑ r ∈ Finset.range (n + 1), ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
      rw [Finset.sum_comm]
    exact h₃
  calc
    ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), ∑ r ∈ Finset.range (n + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
      ∑ g ∈ gaps, ∑ r ∈ Finset.range (n + 1), ∑ k ∈ Finset.range (degP + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by rw [h₁]
    _ = ∑ r ∈ Finset.range (n + 1), ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1),
        (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by rw [h₂]

lemma factor_binomial_coeff_from_double_sum (gaps : Finset ℕ) (degP : ℕ) (coeff : ℕ → ℚ) (n r : ℕ) :
    ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1),
      (coeff k) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
    (n.choose r : ℚ) * (∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ)^r) *
      (∑ g ∈ gaps, (g : ℚ)^(n - r)) := by
  have h1 : ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), (coeff k) * (n.choose r : ℚ) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
       ∑ g ∈ gaps, (n.choose r : ℚ) * ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
    apply Finset.sum_congr rfl
    intro g _
    have h2 : ∑ k ∈ Finset.range (degP + 1), (coeff k) * (n.choose r : ℚ) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
      (n.choose r : ℚ) * ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
      calc
        ∑ k ∈ Finset.range (degP + 1), (coeff k) * (n.choose r : ℚ) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
            ∑ k ∈ Finset.range (degP + 1), (n.choose r : ℚ) * ((coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r)) := by
          apply Finset.sum_congr rfl; intro k _; ring
        _ = (n.choose r : ℚ) * ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
          rw [Finset.mul_sum]
    exact h2
  have h2 : ∑ g ∈ gaps, (n.choose r : ℚ) * ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
       (n.choose r : ℚ) * ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
    rw [Finset.mul_sum]
  have h3 : (n.choose r : ℚ) * ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
      (n.choose r : ℚ) * (∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r) * (∑ g ∈ gaps, (g : ℚ) ^ (n - r)) := by
    have h4 : ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
        ∑ k ∈ Finset.range (degP + 1), ∑ g ∈ gaps, (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
      rw [Finset.sum_comm]
    have h5 : ∑ k ∈ Finset.range (degP + 1), ∑ g ∈ gaps, (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
        ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * ∑ g ∈ gaps, (g : ℚ) ^ (n - r) := by
      apply Finset.sum_congr rfl
      intro k _
      have h6 : ∑ g ∈ gaps, (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
          (coeff k) * (k : ℚ) ^ r * ∑ g ∈ gaps, (g : ℚ) ^ (n - r) := by
        simp [Finset.mul_sum, mul_assoc]
      rw [h6]
    have h6 : ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * ∑ g ∈ gaps, (g : ℚ) ^ (n - r) =
        (∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r) * (∑ g ∈ gaps, (g : ℚ) ^ (n - r)) := by
      calc
        ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * ∑ g ∈ gaps, (g : ℚ) ^ (n - r) =
            ∑ k ∈ Finset.range (degP + 1), ((coeff k) * (k : ℚ) ^ r * ∑ g ∈ gaps, (g : ℚ) ^ (n - r)) := by simp [mul_assoc]
        _ = ∑ k ∈ Finset.range (degP + 1), ((coeff k) * (k : ℚ) ^ r) * (∑ g ∈ gaps, (g : ℚ) ^ (n - r)) := by
          apply Finset.sum_congr rfl
          intro k _
          ring_nf
        _ = (∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r) * (∑ g ∈ gaps, (g : ℚ) ^ (n - r)) := by
          simp [Finset.sum_mul]
    rw [h4, h5, h6]
    ring
  calc
    ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), (coeff k) * (n.choose r : ℚ) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) =
        ∑ g ∈ gaps, (n.choose r : ℚ) * ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
      exact h1
    _ = (n.choose r : ℚ) * ∑ g ∈ gaps, ∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r * (g : ℚ) ^ (n - r) := by
      exact h2
    _ = (n.choose r : ℚ) * (∑ k ∈ Finset.range (degP + 1), (coeff k) * (k : ℚ) ^ r) * (∑ g ∈ gaps, (g : ℚ) ^ (n - r)) := by
      exact h3

lemma sum_choose_mul_div_factorial_eq_sum_div_factorials (n : ℕ) (A B : ℕ → ℚ) :
    ∑ r ∈ Finset.range (n + 1), ((n.choose r : ℚ) * A r * B r) / (n.factorial : ℚ) =
    ∑ r ∈ Finset.range (n + 1), (A r / (r.factorial : ℚ)) * (B r / ((n - r).factorial : ℚ)) := by
  have h_main : ∀ (r : ℕ), r ∈ Finset.range (n + 1) → ((n.choose r : ℚ) * A r * B r) / (n.factorial : ℚ) = (A r / (r.factorial : ℚ)) * (B r / ((n - r).factorial : ℚ)) := by
    intro r hr
    have h₁ : r ≤ n := by
      simp only [Finset.mem_range] at hr
      omega
    have h₂ : (n.choose r : ℕ) * r.factorial * (n - r).factorial = n.factorial := by
      rw [Nat.choose_mul_factorial_mul_factorial h₁]
    have h₃ : (n.choose r : ℚ) * (r.factorial : ℚ) * ((n - r).factorial : ℚ) = (n.factorial : ℚ) := by
      norm_cast at h₂ ⊢
    have h₄ : (n.factorial : ℚ) ≠ 0 := by positivity
    have h₅ : (r.factorial : ℚ) ≠ 0 := by positivity
    have h₆ : ((n - r).factorial : ℚ) ≠ 0 := by positivity
    have h₇ : ((n.choose r : ℚ) : ℚ) / (n.factorial : ℚ) = 1 / ((r.factorial : ℚ) * ((n - r).factorial : ℚ)) := by
      have h₇₁ : (n.choose r : ℚ) * (r.factorial : ℚ) * ((n - r).factorial : ℚ) = (n.factorial : ℚ) := by
        exact_mod_cast h₃
      have h₇₂ : (r.factorial : ℚ) * ((n - r).factorial : ℚ) ≠ 0 := by positivity
      field_simp [h₄, h₅, h₆, h₇₂] at h₇₁ ⊢
      nlinarith
    calc
      ((n.choose r : ℚ) * A r * B r) / (n.factorial : ℚ) = (n.choose r : ℚ) / (n.factorial : ℚ) * (A r * B r) := by
        field_simp [h₄]
      _ = (1 / ((r.factorial : ℚ) * ((n - r).factorial : ℚ))) * (A r * B r) := by rw [h₇]
      _ = (A r / (r.factorial : ℚ)) * (B r / ((n - r).factorial : ℚ)) := by
        have h₈ : (r.factorial : ℚ) ≠ 0 := by positivity
        have h₉ : ((n - r).factorial : ℚ) ≠ 0 := by positivity
        field_simp [h₈, h₉]
  have h_sum_eq : ∑ r ∈ Finset.range (n + 1), ((n.choose r : ℚ) * A r * B r) / (n.factorial : ℚ) = ∑ r ∈ Finset.range (n + 1), (A r / (r.factorial : ℚ)) * (B r / ((n - r).factorial : ℚ)) := by
    apply Finset.sum_congr rfl
    intro r hr
    rw [h_main r hr]
  apply h_sum_eq

lemma gap_sum_binomial_expand {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n) / (n.factorial : ℚ) =
    ∑ r ∈ Finset.range (n + 1),
      ((∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), (G.productPolynomial.coeff k : ℚ) * (k : ℚ)^r) / (r.factorial : ℚ)) *
      ((∑ g ∈ S.gaps, (g : ℚ)^(n - r)) / ((n - r).factorial : ℚ)) := by
  have binom_expand : ∀ (k g : ℕ),
      ((k : ℚ) + (g : ℚ))^n = ∑ r ∈ Finset.range (n + 1), (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
    intro k g
    exact (binomial_sum_eq_add_pow k g n).symm
  have step1 : ∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * ((k : ℚ) + (g : ℚ))^n =
    ∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      ∑ r ∈ Finset.range (n + 1),
        (G.productPolynomial.coeff k : ℚ) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
    congr 1
    ext g
    congr 1
    ext k
    rw [binom_expand k g]
    rw [Finset.mul_sum]
    congr 1
    ext r
    ring
  have step2 : ∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      ∑ r ∈ Finset.range (n + 1),
        (G.productPolynomial.coeff k : ℚ) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
    ∑ r ∈ Finset.range (n + 1), ∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) := by
    exact triple_sum_exchange S.gaps G.productPolynomial.natDegree (fun k => (G.productPolynomial.coeff k : ℚ)) n
  have step3 : ∀ r, ∑ g ∈ S.gaps, ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * (n.choose r : ℚ) * (k : ℚ)^r * (g : ℚ)^(n - r) =
    (n.choose r : ℚ) * (∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
        (G.productPolynomial.coeff k : ℚ) * (k : ℚ)^r) *
      (∑ g ∈ S.gaps, (g : ℚ)^(n - r)) := by
    intro r
    exact factor_binomial_coeff_from_double_sum S.gaps G.productPolynomial.natDegree
      (fun k => (G.productPolynomial.coeff k : ℚ)) n r
  rw [step1, step2]
  simp_rw [step3]
  rw [Finset.sum_div]
  exact sum_choose_mul_div_factorial_eq_sum_div_factorials n
    (fun r => ∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
        (G.productPolynomial.coeff k : ℚ) * (k : ℚ)^r)
    (fun r => ∑ g ∈ S.gaps, (g : ℚ)^(n - r))

lemma P_sum_to_exp_coeff {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (r : ℕ) :
    (∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * (k : ℚ)^r) / (r.factorial : ℚ) =
    ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) * (PowerSeries.coeff r) (PowerSeries.X ^ G.m * G.A_series) := by
  rw [← exp_poly_sub_coeff]
  rw [productPolynomial_exp_transform]
  simp only [map_smul, smul_eq_mul]

lemma gap_sum_to_coeff {S : NumericalSemigroup} (k : ℕ) :
    (∑ g ∈ S.gaps, (g : ℚ)^k) / (k.factorial : ℚ) =
    (PowerSeries.coeff k) (PowerSeries.mk fun j => ∑ g ∈ S.gaps, (g : ℚ)^j / (j.factorial : ℚ)) := by
  simp [PowerSeries.coeff_mk, Finset.sum_div]

lemma coeff_mul_range' {R : Type*} [Semiring R] (φ ψ : PowerSeries R) (n : ℕ) :
    (PowerSeries.coeff n) (φ * ψ) =
    ∑ r ∈ Finset.range (n + 1), (PowerSeries.coeff r) φ * (PowerSeries.coeff (n - r)) ψ := by
  rw [PowerSeries.coeff_mul, Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk]

lemma sum_factor_scalar' {α : Type*} [DecidableEq α] (s : Finset α) (c : ℚ) (a b : α → ℚ) :
    ∑ r ∈ s, (c * a r) * b r = c * ∑ r ∈ s, a r * b r := by
  simp_rw [mul_assoc, Finset.mul_sum]

lemma binomial_sum_to_convolution {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) :
    ∑ r ∈ Finset.range (n + 1),
      ((∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1), (G.productPolynomial.coeff k : ℚ) * (k : ℚ)^r) / (r.factorial : ℚ)) *
      ((∑ g ∈ S.gaps, (g : ℚ)^(n - r)) / ((n - r).factorial : ℚ)) =
    ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) *
      (PowerSeries.coeff n) (PowerSeries.X ^ G.m * G.A_series *
        PowerSeries.mk fun k => ∑ g ∈ S.gaps, (g : ℚ)^k / (k.factorial : ℚ)) := by
  have h1 : ∀ r, (∑ k ∈ Finset.range (G.productPolynomial.natDegree + 1),
      (G.productPolynomial.coeff k : ℚ) * (k : ℚ)^r) / (r.factorial : ℚ) =
      ((-1 : ℚ)^G.m * (G.pi_m : ℚ)) * (PowerSeries.coeff r) (PowerSeries.X ^ G.m * G.A_series) :=
    fun r => P_sum_to_exp_coeff G r
  have h2 : ∀ r, (∑ g ∈ S.gaps, (g : ℚ)^r) / (r.factorial : ℚ) =
      (PowerSeries.coeff r) (PowerSeries.mk fun j => ∑ g ∈ S.gaps, (g : ℚ)^j / (j.factorial : ℚ)) :=
    fun r => gap_sum_to_coeff r
  conv_lhs => arg 2; ext r; rw [h1 r, h2 (n - r)]
  rw [sum_factor_scalar']
  congr 1
  rw [← coeff_mul_range']

lemma gap_part_equals_gap_term_natDegree {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) (n : ℕ) :
    (∑ j ∈ Finset.range (G.hilbertNumerator.natDegree + 1),
      (∑ g ∈ S.gaps.filter (· ≤ j), (G.productPolynomial.coeff (j - g) : ℚ)) * (j : ℚ)^n) / (n.factorial : ℚ) =
    ((-1 : ℚ)^(G.m + 1) * (G.pi_m : ℚ)) * (-(PowerSeries.coeff n) (PowerSeries.X ^ G.m * G.A_series *
         PowerSeries.mk fun k => ∑ g ∈ S.gaps, (g : ℚ)^k / (k.factorial : ℚ))) := by
  rw [gap_sum_exchange]
  rw [gap_sum_binomial_expand]
  rw [binomial_sum_to_convolution]
  ring

lemma exp_poly_sub_hilbertNumerator_eq {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    exp_poly_sub G.hilbertNumerator = Q_exp_series G := by
  ext n
  rw [exp_poly_sub_coeff, Q_exp_series_coeff]
  rw [hilbert_sum_decomposition_natDegree]
  rw [sub_div]
  rw [sum_part_equals_bernoulli_term_natDegree, gap_part_equals_gap_term_natDegree]
  ring

lemma hilbertNumerator_exp_eq_Q_exp {S : NumericalSemigroup}
    (G : NumericalSemigroupGenerators S) :
    hilbertNumerator_exp_sub G = Q_exp_series G := by
  rw [hilbertNumerator_exp_sub_eq_exp_poly_sub, exp_poly_sub_hilbertNumerator_eq]

lemma alternatingPowerSum_eq_neg_factorial_coeff
    {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (n : ℕ) (hn : 1 ≤ n) :
    G.alternatingPowerSum n = -((n.factorial : ℚ) * (PowerSeries.coeff n) (Q_exp_series G)) := by
  rw [alternatingPowerSum_eq_coeff_hilbert_exp G n hn, hilbertNumerator_exp_eq_Q_exp]

lemma C_m_plus_p_formula {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    G.alternatingPowerSum (G.m + p) =
    ((-1 : ℚ)^G.m * (G.pi_m : ℚ) * ((G.m + p).factorial : ℚ) *
      (PowerSeries.coeff (p + 1)) G.B_series) +
    ((-1 : ℚ)^G.m * (G.pi_m : ℚ) *
      ∑ r ∈ Finset.range (p + 1),
        ((G.m + p).choose r : ℚ) * ((G.m + p - r).factorial : ℚ) *
          (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r) := by
  -- Step 1: Establish 1 ≤ m + p (needed for alternatingPowerSum_eq_neg_factorial_coeff)
  have hmp : 1 ≤ G.m + p := Nat.le_add_right 1 p |>.trans (Nat.add_le_add_right G.hm_pos p)
  -- Step 2: Relate to Q_exp_series coefficient via the bridge lemmas
  rw [alternatingPowerSum_eq_neg_factorial_coeff G (G.m + p) hmp]
  -- Step 3: Extract the coefficient
  rw [Q_exp_coeff_at_m_plus_p]
  -- Step 4: Use gap_term_final_form
  have h_gap := gap_term_final_form G p
  -- Step 5: Combine by distributing (m+p)! over the addition
  -- and applying the sign identity (-1)^{m+1} * (-1) = (-1)^{m+2} = (-1)^m
  calc -((↑(G.m + p).factorial : ℚ) *
          ((-1) ^ (G.m + 1) * ↑G.pi_m *
            ((PowerSeries.coeff (p + 1)) G.B_series +
             ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
               (PowerSeries.coeff (p - r)) G.A_series * (↑g) ^ r / (↑r.factorial))))
      = (-1) ^ G.m * ↑G.pi_m * ↑(G.m + p).factorial * (PowerSeries.coeff (p + 1)) G.B_series +
        (-1) ^ G.m * ↑G.pi_m * (↑(G.m + p).factorial *
          ∑ g ∈ S.gaps, ∑ r ∈ Finset.range (p + 1),
            (PowerSeries.coeff (p - r)) G.A_series * (↑g) ^ r / (↑r.factorial)) := by ring
    _ = (-1) ^ G.m * ↑G.pi_m * ↑(G.m + p).factorial * (PowerSeries.coeff (p + 1)) G.B_series +
        (-1) ^ G.m * ↑G.pi_m *
          ∑ r ∈ Finset.range (p + 1),
            ↑((G.m + p).choose r) * ↑(G.m + p - r).factorial *
              (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r := by rw [h_gap]

lemma K_invariant_expanded {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    G.K_invariant p =
    (p.factorial : ℚ) * (PowerSeries.coeff (p + 1)) G.B_series +
    ∑ r ∈ Finset.range (p + 1),
      ((p.factorial : ℚ) / (r.factorial : ℚ)) *
        (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r := by
  -- Step 1: Unfold K_invariant and substitute C_m_plus_p_formula
  unfold NumericalSemigroupGenerators.K_invariant
  rw [C_m_plus_p_formula]
  -- Step 2: Distribute the coefficient over the addition
  rw [mul_add]
  -- Step 3: Simplify the Bernoulli term
  have hpi : 0 < G.pi_m := pi_m_pos G
  rw [bernoulli_term_simplify G.m p G.pi_m hpi]
  -- Step 4: Simplify the gap sum term
  rw [gap_sum_simplify]

lemma bernoulli_coeff_to_T_delta {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    (p.factorial : ℚ) * (PowerSeries.coeff (p + 1)) G.B_series =
    (2 ^ (p + 1) : ℚ) / ((p : ℚ) + 1) * G.T_delta (p + 1) := by
  -- Expand T_delta definition
  unfold NumericalSemigroupGenerators.T_delta
  ring_nf
  -- Goal: p! * coeff = coeff * (1+p)^{-1} * (1+p)! * 2^{-p} * 2^p
  -- Use: 2^{-p} * 2^p = 1 and (1+p)!/(1+p) = p!
  have h1 : (2 : ℚ)⁻¹ ^ p * 2 ^ p = 1 := by
    rw [← mul_pow]
    simp
  rw [mul_assoc, mul_assoc, mul_assoc, h1, mul_one]
  -- Goal: p! * coeff = coeff * ((1+p)^{-1} * (1+p)!)
  -- We have (1+p)! = (1+p) * p!, so (1+p)^{-1} * (1+p)! = p!
  have h2 : (1 + (p : ℚ))⁻¹ * ((1 + p).factorial : ℚ) = (p.factorial : ℚ) := by
    rw [Nat.add_comm 1 p, Nat.factorial_succ, Nat.cast_mul, Nat.cast_succ]
    rw [add_comm]
    field_simp
  rw [h2]
  ring

lemma factorial_div_eq_choose_mul {p r : ℕ} (hr : r ≤ p) :
    (p.factorial : ℚ) / r.factorial = (p.choose r : ℚ) * (p - r).factorial := by
  have h_main : ((p.choose r : ℕ) : ℚ) * (r.factorial : ℚ) * ((p - r).factorial : ℚ) = (p.factorial : ℚ) := by
    have h₁ : (p.choose r : ℕ) * r.factorial * (p - r).factorial = p.factorial := by
      rw [← Nat.choose_mul_factorial_mul_factorial hr]
    norm_cast at h₁ ⊢
  have h_r_pos : (r.factorial : ℚ) ≠ 0 := by positivity
  field_simp [h_r_pos]
  linarith

lemma A_coeff_to_T_sigma {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p r : ℕ)
    (hr : r ≤ p) :
    ((p.factorial : ℚ) / (r.factorial : ℚ)) * (PowerSeries.coeff (p - r)) G.A_series =
    (p.choose r : ℚ) * G.T_sigma (p - r) := by
  -- Use the factorial identity: p!/r! = C(p,r) * (p-r)!
  have hfact := factorial_div_eq_choose_mul hr
  -- T_sigma (p - r) = (p-r)! * coeff (p - r) A_series by definition
  unfold NumericalSemigroupGenerators.T_sigma
  -- Substitute and rearrange
  rw [hfact]
  ring

lemma sum_A_coeff_eq_sum_T_sigma {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    ∑ r ∈ Finset.range (p + 1),
      ((p.factorial : ℚ) / (r.factorial : ℚ)) *
        (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r =
    ∑ r ∈ Finset.range (p + 1),
      (p.choose r : ℚ) * G.T_sigma (p - r) * S.gapPowerSum r := by
  apply Finset.sum_congr rfl
  intro r hr
  rw [Finset.mem_range] at hr
  have hrp : r ≤ p := Nat.lt_succ_iff.mp hr
  have h := A_coeff_to_T_sigma G p r hrp
  calc (p.factorial : ℚ) / (r.factorial : ℚ) *
        (PowerSeries.coeff (p - r)) G.A_series * S.gapPowerSum r
      = ((p.factorial : ℚ) / (r.factorial : ℚ) *
        (PowerSeries.coeff (p - r)) G.A_series) * S.gapPowerSum r := by ring
    _ = ((p.choose r : ℚ) * G.T_sigma (p - r)) * S.gapPowerSum r := by rw [h]
    _ = (p.choose r : ℚ) * G.T_sigma (p - r) * S.gapPowerSum r := by ring

lemma fels_conjecture_main {S : NumericalSemigroup} (G : NumericalSemigroupGenerators S) (p : ℕ) :
    G.K_invariant p =
      ∑ r ∈ Finset.range (p + 1),
        (p.choose r : ℚ) * G.T_sigma (p - r) * S.gapPowerSum r +
      (2 ^ (p + 1) : ℚ) / ((p : ℚ) + 1) * G.T_delta (p + 1) := by
  rw [K_invariant_expanded]
  rw [bernoulli_coeff_to_T_delta]
  rw [sum_A_coeff_eq_sum_T_sigma]
  ring

end FelsConjectureProof

theorem fels_conjecture (S : NumericalSemigroup) (G : NumericalSemigroupGenerators S) (p : ℕ) :
    G.K_invariant p =
      ∑ r ∈ Finset.range (p + 1),
        (p.choose r : ℚ) * G.T_sigma (p - r) * S.gapPowerSum r +
      (2 ^ (p + 1) : ℚ) / ((p : ℚ) + 1) * G.T_delta (p + 1) :=
  FelsConjectureProof.fels_conjecture_main G p
