"""
Exceptional Cascade — Theorem X
================================

The E₈ root system decomposes through the maximal chain

    E₈ ⊃ E₇ ⊃ E₆ ⊃ D₅ ⊃ D₄ ⊃ D₃ ⊃ A₂ ⊃ A₁

and the BCC (3×3×3 sign-pattern) cube reveals this decomposition
geometrically.  At each step G → H the complement G\\H carries
exactly the minuscule representation of H (doubled for complex reps),
and the roots populate specific BCC orbits determined by their Lie type.

Three structural laws govern the decomposition:

1. **Spinor-Corner Law** — Only the three E-type layers contribute
   spinor (half-integer) roots to BCC corners: 16 = 8 + 4 + 4.

2. **Edge Saturation Law** — All 12 edge roots live in the D₃→A₂
   transition (SU(4)→SU(3)), where colour symmetry emerges.

3. **Null Vector Cascade** — (1,0,−1) has eigenvalue 0 for every
   layer except D₃→A₂, where it becomes eigenvalue −1 (the A_n
   anti-null phenomenon localised to the orthogonal→unitary break).

Additional results:
- tr(BCC_E₈) = |E₇| = 126 (branching identity).
- BCC eigenvalues: E₆ = {24,6,0}, E₇ = {42,18,0}, E₈ = {63±3√33, 0}.
- √33 = √(1 + 2⁵) = √(1 + dim S⁺(SO(10))) — spinor origin.
- 26 (center of E₈\\E₇ layer) = dim(fund F₄).
"""

from __future__ import annotations

import math
from itertools import combinations, product as iprod
from typing import Dict, List, Tuple

import numpy as np

# ---------------------------------------------------------------------------
# Root system builders
# ---------------------------------------------------------------------------

def _e8_roots() -> np.ndarray:
    """Return the 240 roots of E₈ as rows of an (240, 8) array."""
    roots = []
    for i, j in combinations(range(8), 2):
        for si in (1, -1):
            for sj in (1, -1):
                r = [0] * 8
                r[i], r[j] = si, sj
                roots.append(r)
    for signs in iprod((1, -1), repeat=8):
        if sum(1 for s in signs if s == -1) % 2 == 0:
            roots.append([s * 0.5 for s in signs])
    return np.array(roots)


_E8_ROOTS: np.ndarray | None = None


def e8_roots() -> np.ndarray:
    """Cached 240×8 array of E₈ roots."""
    global _E8_ROOTS
    if _E8_ROOTS is None:
        _E8_ROOTS = _e8_roots()
    return _E8_ROOTS


def _root_set(arr: np.ndarray):
    return set(map(tuple, arr.tolist()))


# ---------------------------------------------------------------------------
# Cascade chain
# ---------------------------------------------------------------------------

def cascade_chain(coords: Tuple[int, int, int] = (0, 1, 2)):
    """Build the full E₈ ⊃ … ⊃ A₁ cascade and BCC cube data.

    Returns a list of dicts, one per layer (top to bottom):
        name, roots_above, roots_below, layer_size,
        bcc_corner, bcc_edge, bcc_face, bcc_center,
        grid_2d, eigenvalues_2d, null_eigenvalue
    """
    R = e8_roots()
    sets: Dict[str, set] = {}
    sets["E8"] = _root_set(R)
    sets["E7"] = {r for r in sets["E8"] if abs(r[6] - r[7]) < 1e-10}
    sets["E6"] = {r for r in sets["E8"]
                  if abs(r[5] - r[6]) < 1e-10 and abs(r[6] - r[7]) < 1e-10}
    for dim, name in [(5, "D5"), (4, "D4"), (3, "D3")]:
        s = set()
        for i in range(dim):
            for j in range(i + 1, dim):
                for si in (1, -1):
                    for sj in (1, -1):
                        r = [0] * 8
                        r[i], r[j] = si, sj
                        s.add(tuple(r))
        sets[name] = s
    a2 = set()
    for i in range(3):
        for j in range(3):
            if i != j:
                r = [0] * 8
                r[i], r[j] = 1, -1
                a2.add(tuple(r))
    sets["A2"] = a2
    a1 = set()
    for r in [(1, -1), (-1, 1)]:
        v = [0] * 8
        v[0], v[1] = r
        a1.add(tuple(v))
    sets["A1"] = a1

    chain_names = ["E8", "E7", "E6", "D5", "D4", "D3", "A2", "A1"]
    result: List[dict] = []

    for idx in range(1, len(chain_names)):
        above = chain_names[idx - 1]
        below = chain_names[idx]
        layer = sets[above] - sets[below]
        layer_arr = np.array(list(layer))

        # 3D BCC cube
        cube = np.zeros((3, 3, 3), dtype=int)
        for r in layer:
            sp = tuple(
                1 if r[c] > 1e-10 else (-1 if r[c] < -1e-10 else 0)
                for c in coords
            )
            i = {1: 0, 0: 1, -1: 2}[sp[0]]
            j = {1: 0, 0: 1, -1: 2}[sp[1]]
            k = {1: 0, 0: 1, -1: 2}[sp[2]]
            cube[i][j][k] += 1

        co = int(cube[0, 0, 0])
        ed = int(cube[0, 0, 1])
        fa = int(cube[0, 1, 1])
        ce = int(cube[1, 1, 1])

        # 2D grid (sum over third coord)
        grid = np.zeros((3, 3), dtype=int)
        for r in layer:
            sp = tuple(
                1 if r[c] > 1e-10 else (-1 if r[c] < -1e-10 else 0)
                for c in coords[:2]
            )
            i = {1: 0, 0: 1, -1: 2}[sp[0]]
            j = {1: 0, 0: 1, -1: 2}[sp[1]]
            grid[i][j] += 1

        evals = sorted(np.linalg.eigvals(grid.astype(float)).real, reverse=True)
        v = np.array([1, 0, -1])
        gv = grid @ v
        is_null = bool(np.allclose(gv, 0))

        result.append({
            "step": f"{above}→{below}",
            "above": above,
            "below": below,
            "layer_size": len(layer),
            "bcc_corner": co,
            "bcc_edge": ed,
            "bcc_face": fa,
            "bcc_center": ce,
            "grid_2d": grid,
            "eigenvalues_2d": evals,
            "null_eigenvalue": is_null,
        })

    return result


# ---------------------------------------------------------------------------
# BCC grid for any root system
# ---------------------------------------------------------------------------

def bcc_grid(root_system: str, coords: Tuple[int, ...] = (0, 1)):
    """Return the BCC sign-pattern grid for a named root system.

    Supported: 'E8', 'E7', 'E6', 'F4', 'D3'–'D8', 'A2', 'A1'.
    Returns a 3×3 numpy array (int).
    """
    roots = _build_roots(root_system)
    grid = np.zeros((3, 3), dtype=int)
    for r in roots:
        sp = tuple(
            1 if r[c] > 1e-10 else (-1 if r[c] < -1e-10 else 0)
            for c in coords
        )
        i = {1: 0, 0: 1, -1: 2}[sp[0]]
        j = {1: 0, 0: 1, -1: 2}[sp[1]]
        grid[i][j] += 1
    return grid


def _build_roots(name: str):
    if name == "E8":
        return [tuple(r) for r in e8_roots().tolist()]
    if name == "F4":
        return _f4_roots()
    # Dn
    if name.startswith("D"):
        dim = int(name[1:])
        roots = []
        for i in range(dim):
            for j in range(i + 1, dim):
                for si in (1, -1):
                    for sj in (1, -1):
                        roots.append(tuple(
                            si if k == i else (sj if k == j else 0)
                            for k in range(dim)
                        ))
        return roots
    # An
    if name.startswith("A"):
        n = int(name[1:])
        dim = n + 1
        roots = []
        for i in range(dim):
            for j in range(dim):
                if i != j:
                    roots.append(tuple(
                        1 if k == i else (-1 if k == j else 0)
                        for k in range(dim)
                    ))
        return roots
    # E7, E6 via E8 filtering
    e8 = [tuple(r) for r in e8_roots().tolist()]
    if name == "E7":
        return [r for r in e8 if abs(r[6] - r[7]) < 1e-10]
    if name == "E6":
        return [r for r in e8
                if abs(r[5] - r[6]) < 1e-10 and abs(r[6] - r[7]) < 1e-10]
    raise ValueError(f"Unknown root system: {name}")


def _f4_roots():
    """48 roots of F₄ in its natural R⁴ coordinates."""
    roots = []
    # 24 long roots: ±eᵢ±eⱼ
    for i in range(4):
        for j in range(i + 1, 4):
            for si in (1, -1):
                for sj in (1, -1):
                    r = [0, 0, 0, 0]
                    r[i], r[j] = si, sj
                    roots.append(tuple(r))
    # 8 short roots: ±eᵢ
    for i in range(4):
        for s in (1, -1):
            r = [0, 0, 0, 0]
            r[i] = s
            roots.append(tuple(r))
    # 16 short roots: (±½)⁴
    for signs in iprod((1, -1), repeat=4):
        roots.append(tuple(s * 0.5 for s in signs))
    return roots


# ---------------------------------------------------------------------------
# Spectral analysis of BCC grids
# ---------------------------------------------------------------------------

def bcc_eigenvalues(root_system: str, coords: Tuple[int, ...] = (0, 1)):
    """Eigenvalues of the BCC grid for *root_system* (sorted descending)."""
    g = bcc_grid(root_system, coords)
    return sorted(np.linalg.eigvals(g.astype(float)).real, reverse=True)


def bcc_perron_alpha(root_system: str, coords: Tuple[int, ...] = (0, 1)):
    """Perron eigenvector ratio α for bisymmetric BCC grid (α, 1, α).

    Returns (alpha, discriminant_factor) where disc = δ²·disc_factor
    and δ = 2a − c.
    """
    g = bcc_grid(root_system, coords)
    a, b, c = int(g[0, 0]), int(g[0, 1]), int(g[1, 1])
    delta = 2 * a - c
    if delta == 0 or b == 0:
        return (1.0, 0)
    disc = delta ** 2 + 8 * b ** 2
    alpha = (delta + math.sqrt(disc)) / (4 * b)
    disc_factor = 1 + 8 * (b / delta) ** 2
    return (alpha, disc_factor)


# ---------------------------------------------------------------------------
# Cascade constants
# ---------------------------------------------------------------------------

#: Minuscule representation dimensions for each step
MINUSCULE_DIMS = {
    "E8→E7": 56,
    "E7→E6": 27,
    "E6→D5": 16,
    "D5→D4": 8,
    "D4→D3": 6,
    "D3→A2": 3,
    "A2→A1": 2,
}

#: BCC orbit multiplicities per layer (corner, edge, face, center)
CASCADE_ORBITS = {
    "E8→E7": (8, 0, 4, 26),
    "E7→E6": (4, 0, 2, 10),
    "E6→D5": (4, 0, 0, 0),
    "D5→D4": (0, 0, 2, 4),
    "D4→D3": (0, 0, 2, 0),
    "D3→A2": (0, 1, 0, 0),
    "A2→A1": (0, 0, 0, 0),
}

#: E₈ BCC grid eigenvalues (exact algebraic)
E8_BCC_EIGENVALUE_1 = 63 + 3 * math.sqrt(33)  # ≈ 80.234
E8_BCC_EIGENVALUE_2 = 63 - 3 * math.sqrt(33)  # ≈ 45.766


# ---------------------------------------------------------------------------
# Theorem Y — Anti-Null Embedding Theorem
# ---------------------------------------------------------------------------

def null_eigenvalue(root_system: str, coords: Tuple[int, ...] = (0, 1)) -> int:
    """Eigenvalue of the null vector (1, 0, −1) for *root_system*'s BCC grid.

    Theorem Y states λ = N₊₊ − N₊₋ where
      N₊₊ = #{roots with coord_a > 0 AND coord_b > 0}
      N₊₋ = #{roots with coord_a > 0 AND coord_b < 0}

    Classification:
      A_n (n ≥ 2): λ = −1  (difference-type embedding)
      B_n, C_n, D_n, E, F: λ = 0  (sign-flip symmetric)
      G_2: λ = −2  (two root orbits × (−1) each)
    """
    g = bcc_grid(root_system, coords)
    return int(g[0, 0] - g[0, 2])


def copositivity_counts(
    root_system: str, coords: Tuple[int, ...] = (0, 1)
) -> Tuple[int, int]:
    """Return (N₊₊, N₊₋) — the co-positive and anti-positive root counts.

    N₊₊ = number of roots with both projected coordinates positive.
    N₊₋ = number of roots with coord_a positive, coord_b negative.

    Their difference λ = N₊₊ − N₊₋ is the null-vector eigenvalue (Theorem Y).
    """
    g = bcc_grid(root_system, coords)
    return int(g[0, 0]), int(g[0, 2])


def an_bcc_formula(n: int) -> np.ndarray:
    """Closed-form BCC grid for A_n (n ≥ 2) in coords (0, 1).

    M = [[0, n−1, 1], [n−1, (n−1)(n−2), n−1], [1, n−1, 0]]

    Derived from the difference-type embedding e_i − e_j in R^{n+1}.
    """
    if n < 2:
        raise ValueError("A_n BCC formula requires n >= 2")
    return np.array([
        [0, n - 1, 1],
        [n - 1, (n - 1) * (n - 2), n - 1],
        [1, n - 1, 0],
    ], dtype=int)


def _g2_roots() -> List[Tuple[float, ...]]:
    """12 roots of G₂ in R³ (trace-zero hyperplane)."""
    roots: List[Tuple[float, ...]] = []
    from itertools import permutations
    for p in set(permutations((1, -1, 0))):
        roots.append(p)
    for p in set(permutations((2, -1, -1))):
        roots.append(p)
    for p in set(permutations((-2, 1, 1))):
        roots.append(p)
    return roots


def _build_roots_extended(name: str):
    """Extended root builder supporting B_n, C_n, G_2 in addition to _build_roots."""
    if name == "G2":
        return _g2_roots()
    if name.startswith("B"):
        n = int(name[1:])
        roots = []
        # Short roots: ±e_i
        for i in range(n):
            for s in (1, -1):
                r = tuple(s if k == i else 0 for k in range(n))
                roots.append(r)
        # Long roots: ±e_i ± e_j
        for i in range(n):
            for j in range(i + 1, n):
                for si in (1, -1):
                    for sj in (1, -1):
                        r = tuple(
                            si if k == i else (sj if k == j else 0)
                            for k in range(n)
                        )
                        roots.append(r)
        return roots
    if name.startswith("C"):
        n = int(name[1:])
        roots = []
        # Long roots: ±2e_i
        for i in range(n):
            for s in (1, -1):
                r = tuple(2 * s if k == i else 0 for k in range(n))
                roots.append(r)
        # Short roots: ±e_i ± e_j
        for i in range(n):
            for j in range(i + 1, n):
                for si in (1, -1):
                    for sj in (1, -1):
                        r = tuple(
                            si if k == i else (sj if k == j else 0)
                            for k in range(n)
                        )
                        roots.append(r)
        return roots
    return _build_roots(name)


def is_antinull(root_system: str, coords: Tuple[int, ...] = (0, 1)) -> bool:
    """True iff root_system has non-zero null-vector eigenvalue (anti-null)."""
    return null_eigenvalue(root_system, coords) != 0


def antinull_classification() -> Dict[str, int]:
    """Return {root_system: null_eigenvalue} for all classical + exceptional systems."""
    result = {}
    for name in ["A2", "A3", "A4", "A5", "A6", "A7"]:
        n = int(name[1:])
        roots = _build_roots(name)
        g = np.zeros((3, 3), dtype=int)
        for r in roots:
            sp0 = 0 if r[0] > 1e-10 else (2 if r[0] < -1e-10 else 1)
            sp1 = 0 if r[1] > 1e-10 else (2 if r[1] < -1e-10 else 1)
            g[sp0, sp1] += 1
        result[name] = int(g[0, 0] - g[0, 2])

    for name in ["D4", "D5", "D6"]:
        result[name] = null_eigenvalue(name)

    for name in ["E6", "E7", "E8", "F4"]:
        result[name] = null_eigenvalue(name)

    # B_n, C_n, G_2 via extended builder
    for name in ["B3", "B4", "B5", "C3", "C4"]:
        roots = _build_roots_extended(name)
        g = np.zeros((3, 3), dtype=int)
        for r in roots:
            sp0 = 0 if r[0] > 1e-10 else (2 if r[0] < -1e-10 else 1)
            sp1 = 0 if r[1] > 1e-10 else (2 if r[1] < -1e-10 else 1)
            g[sp0, sp1] += 1
        result[name] = int(g[0, 0] - g[0, 2])

    # G_2
    roots = _build_roots_extended("G2")
    g = np.zeros((3, 3), dtype=int)
    for r in roots:
        sp0 = 0 if r[0] > 1e-10 else (2 if r[0] < -1e-10 else 1)
        sp1 = 0 if r[1] > 1e-10 else (2 if r[1] < -1e-10 else 1)
        g[sp0, sp1] += 1
    result["G2"] = int(g[0, 0] - g[0, 2])

    return result
