"""
Coupling Energy & Coupling Angle — Theorems R, T, U
====================================================

The coupling energy E(n) = seq3·seq6 + seq1·seq4 + seq7·seq5 is the master
arithmetic invariant of the Kaleidoscope. It equals half the off-diagonal
Frobenius contribution:

    E(n) = [tr(M²) - Σᵢ M[i,i]²] / 2 = Σ_{i<j} M[i,j]·M[j,i]

The coupling angle θ(n) = arccos(E(n) / (|u|·|v|)) measures alignment between
the upper and lower triangular halves of M(n):

    u(n) = (seq3, seq1, seq7)  — upper off-diagonal
    v(n) = (seq6, seq4, seq5)  — lower off-diagonal

Theorem T (Coupling-Smoothness): For n ≥ 24, the coupling angle satisfies

    θ(n) = arctan(√(seq5² + seq6²) / seq4)

where seq4 = rough part of L(n+1) (prime factors > n+1) and seq5 = smooth
part (prime factors ≤ n+1, absorbed by (n+1)!). The coupling angle is a
geometric smoothness detector for Lucas numbers.

Theorem U (Arithmetic Factorization): The coupling energy satisfies

    E(n) = (2φ)^{n+1} / D(n) + O(φ^{-n})

where D(n) = gcd(M(n+1), (n+1)!) × gcd(L(n+1), (n+1)!) decomposes over
primes p ≤ n+1. The constant C_odd = 2φ/9 corresponds to D = 9 = 3² at
positions where only p=3 contributes (via ord₂(3)=2 and α_L(3)=2).

(C) 2024-2026 Dustin Watkins / DataSphere AI, LLC. MIT License.
"""

import math
from math import gcd, factorial

from .kaleidoscope import (
    seq1_cycle_lb_numerator, seq2_mersenne_numerator, seq3_mersenne_gcd,
    seq4_lucas_numerator, seq5_lucas_gcd, seq6_mersenne_lucas_gcd,
    seq7_binary_preservation, seq8_cross_gcd, seq9_weight_moments,
    mersenne, lucas,
)

__all__ = [
    'coupling_energy',
    'upper_triangle',
    'lower_triangle',
    'coupling_angle',
    'coupling_angle_asymptotic',
    'coupling_smoothness',
    'arithmetic_denominator',
    'coupling_energy_predicted',
    'coupling_torque',
    'gram_matrix',
    'trace_squared_decomposition',
    'HYPERBOLOID_POINTS',
    'hyperboloid_point',
    'hyperboloid_all',
    'hyperboloid_pythagorean',
    'lattice_density_check',
]


def coupling_energy(n: int) -> int:
    """E(n) = seq3·seq6 + seq1·seq4 + seq7·seq5.

    The master arithmetic invariant. Controls:
    - Spectral gap (Theorem N): p₁² - 3p₂ = ½Σ(dᵢ-dⱼ)² + 3E
    - Trace of M² (Theorem P): tr(M²) = L(4n) + 2 + seq2² + seq8² + 2E
    """
    return (seq3_mersenne_gcd(n) * seq6_mersenne_lucas_gcd(n)
            + seq1_cycle_lb_numerator(n) * seq4_lucas_numerator(n)
            + seq7_binary_preservation(n) * seq5_lucas_gcd(n))


def upper_triangle(n: int) -> tuple:
    """u(n) = (M[0,1], M[0,2], M[1,2]) = (seq3, seq1, seq7).

    The upper off-diagonal entries of M(n), read row-by-row.
    """
    return (seq3_mersenne_gcd(n),
            seq1_cycle_lb_numerator(n),
            seq7_binary_preservation(n))


def lower_triangle(n: int) -> tuple:
    """v(n) = (M[1,0], M[2,0], M[2,1]) = (seq6, seq4, seq5).

    The lower off-diagonal entries of M(n), read column-by-column.
    """
    return (seq6_mersenne_lucas_gcd(n),
            seq4_lucas_numerator(n),
            seq5_lucas_gcd(n))


def _dot(a: tuple, b: tuple) -> int:
    """Euclidean inner product of two 3-tuples."""
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]


def _norm(a: tuple) -> float:
    """Euclidean norm of a 3-tuple."""
    return math.sqrt(a[0]**2 + a[1]**2 + a[2]**2)


def _cross(a: tuple, b: tuple) -> tuple:
    """Cross product of two 3-tuples."""
    return (a[1] * b[2] - a[2] * b[1],
            a[2] * b[0] - a[0] * b[2],
            a[0] * b[1] - a[1] * b[0])


def coupling_angle(n: int) -> float:
    """θ(n) = arccos(E(n) / (|u|·|v|)) in radians.

    Measures the alignment between upper and lower triangular halves.
    θ → 0 means M(n) approaches row-symmetric structure.
    """
    u = upper_triangle(n)
    v = lower_triangle(n)
    E = _dot(u, v)
    nu = _norm(u)
    nv = _norm(v)
    if nu < 1e-15 or nv < 1e-15:
        return 0.0
    cos_theta = E / (nu * nv)
    cos_theta = max(-1.0, min(1.0, cos_theta))
    return math.acos(cos_theta)


def coupling_angle_asymptotic(n: int) -> float:
    """Theorem T asymptotic coupling angle in radians.

    θ_T(n) = arctan(√(seq5² + seq6²) / seq4)

    Exact in the limit as seq1 dominates u = (seq3, seq1, seq7).
    For n ≥ 24, |θ(n) - θ_T(n)| < 0.002°.

    The formula reveals that the coupling angle is a geometric
    smoothness detector: seq5 = (n+1)-smooth part of L(n+1),
    seq4 = rough part. When L(n+1) is anomalously smooth
    (many small prime factors), θ spikes.
    """
    s4 = seq4_lucas_numerator(n)
    s5 = seq5_lucas_gcd(n)
    s6 = seq6_mersenne_lucas_gcd(n)
    if s4 == 0:
        return math.pi / 2
    return math.atan2(math.sqrt(s5**2 + s6**2), s4)


def coupling_smoothness(n: int) -> dict:
    """Lucas smoothness decomposition at position n.

    Decomposes L(n+1) into its (n+1)-smooth and rough parts via the
    factorial sieve, and computes the smoothness ratio that controls
    the asymptotic coupling angle (Theorem T).

    Returns:
        smooth: seq5(n) = gcd(L(n+1), (n+1)!) — smooth part
        rough: seq4(n) = L(n+1) / gcd(L(n+1), (n+1)!) — rough part
        ml_gcd: seq6(n) = gcd(2^n - 1, L(n)) — Mersenne-Lucas interference
        ratio: seq5/seq4 — smoothness ratio
        theta_exact: coupling_angle(n) in degrees
        theta_asymptotic: coupling_angle_asymptotic(n) in degrees
        error: |exact - asymptotic| in degrees
    """
    s4 = seq4_lucas_numerator(n)
    s5 = seq5_lucas_gcd(n)
    s6 = seq6_mersenne_lucas_gcd(n)
    theta_ex = math.degrees(coupling_angle(n))
    theta_as = math.degrees(coupling_angle_asymptotic(n))
    return {
        'smooth': s5,
        'rough': s4,
        'ml_gcd': s6,
        'ratio': s5 / s4 if s4 > 0 else float('inf'),
        'theta_exact': theta_ex,
        'theta_asymptotic': theta_as,
        'error': abs(theta_ex - theta_as),
    }


def arithmetic_denominator(n: int) -> tuple:
    """Theorem U: the arithmetic denominator D(n) and its factors.

    D(n) = gcd(M(n+1), (n+1)!) × gcd(L(n+1), (n+1)!)

    The coupling energy satisfies E(n) = (2φ)^{n+1} / D(n) to
    exponential precision.

    Returns:
        (D, gcd_mersenne, gcd_lucas) where D = gcd_mersenne × gcd_lucas.

    Key values:
        D = 1:  E/(2φ)^n → 2φ        (HIGH — coprime to factorial)
        D = 9:  E/(2φ)^n → 2φ/9      (C_odd — only p=3 contributes)
        D = 28: E/(2φ)^n → 2φ/28     (primes 7 from M, 2² from L)
    """
    Mn1 = mersenne(n + 1)
    Ln1 = lucas(n + 1)
    fn1 = factorial(n + 1)
    gM = gcd(Mn1, fn1)
    gL = gcd(Ln1, fn1)
    return (gM * gL, gM, gL)


def coupling_energy_predicted(n: int) -> float:
    """Theorem U predicted coupling energy: (2φ)^{n+1} / D(n).

    Uses the arithmetic factorization to compute E(n) from prime
    structure alone. Agrees with coupling_energy(n) to high relative
    precision for n ≥ 20.
    """
    PHI = (1 + math.sqrt(5)) / 2
    D, _, _ = arithmetic_denominator(n)
    return (2 * PHI) ** (n + 1) / D


def coupling_torque(n: int) -> float:
    """|u × v| = |u|·|v|·sin(θ) — the coupling torque.

    Measures the antisymmetric component of the off-diagonal interaction.
    """
    u = upper_triangle(n)
    v = lower_triangle(n)
    c = _cross(u, v)
    return _norm(c)


def gram_matrix(n: int) -> tuple:
    """The 2×2 Gram matrix G(n) = [[|u|², E], [E, |v|²]].

    Returns (|u|², E, |v|², det(G)).
    det(G) = |u|²|v|² - E² = |u×v|² = (coupling torque)².
    """
    u = upper_triangle(n)
    v = lower_triangle(n)
    uu = _dot(u, u)
    vv = _dot(v, v)
    E = _dot(u, v)
    return (uu, E, vv, uu * vv - E * E)


def trace_squared_decomposition(n: int) -> dict:
    """Decompose tr(M(n)²) = Σᵢ dᵢ² + 2E(n).

    Note: tr(M²) = Σᵢⱼ M[i,j]·M[j,i] ≠ ||M||²_F for non-symmetric M.
    ||M||²_F = Σ M[i,j]² uses squares, not cross-products.

    Returns:
        diag_sq: Σᵢ M[i,i]² = seq2² + seq8² + L(2n)²
        coupling: 2·E(n) = off-diagonal contribution to tr(M²)
        trace_sq: tr(M²) = diag_sq + coupling
    """
    s2 = seq2_mersenne_numerator(n)
    s8 = seq8_cross_gcd(n)
    l2n = seq9_weight_moments(n)
    E = coupling_energy(n)

    diag_sq = s2**2 + s8**2 + l2n**2
    return {
        'diag_sq': diag_sq,
        'coupling': 2 * E,
        'trace_sq': diag_sq + 2 * E,
    }




# =====================================================================
# Theorem V: The Kaleidoscope Hyperboloid
# =====================================================================

HYPERBOLOID_POINTS = {
    "fibonacci":  (1, 0),
    "mersenne":   (0, 1),
    "lucas":      (2, 0),
    "coupling":   (1, 1),
    "spectral":   (2, 1),
}


def hyperboloid_point(name_or_ab):
    """Evaluate a point on the Kaleidoscope Hyperboloid (Theorem V).

    The five canonical points form a rank-2 lattice generated by
    ln(phi) and ln(2). Each point (a, b) means t = a*ln(phi) + b*ln(2).
    """
    PHI = (1 + math.sqrt(5)) / 2
    LN_PHI = math.log(PHI)
    LN_2 = math.log(2)

    if isinstance(name_or_ab, str):
        name = name_or_ab.lower()
        a, b = HYPERBOLOID_POINTS[name]
    else:
        a, b = name_or_ab
        name = f"({a},{b})"

    t = a * LN_PHI + b * LN_2
    ch = math.cosh(t)
    sh = math.sinh(t)

    return {
        "name": name, "a": a, "b": b, "t": t,
        "cosh_t": ch, "sinh_t": sh,
        "verification": ch**2 - sh**2,
    }


def hyperboloid_all():
    """Evaluate all five canonical Hyperboloid points (Theorem V)."""
    return {name: hyperboloid_point(name) for name in HYPERBOLOID_POINTS}


def hyperboloid_pythagorean():
    """Coupling-point Pythagorean identity (Theorem V)."""
    S5 = math.sqrt(5)
    lhs = 5 * S5 + 3
    rhs = 3 * S5 + 5
    return {
        "lhs": lhs, "rhs": rhs,
        "lhs_sq": lhs**2, "rhs_sq": rhs**2,
        "difference": lhs**2 - rhs**2,
        "is_64": abs(lhs**2 - rhs**2 - 64) < 1e-10,
    }


def lattice_density_check(a_max=10):
    """Fractional parts of a*W^2 mod 1 (Kronecker density check)."""
    W2 = math.log2((1 + math.sqrt(5)) / 2)
    return [a * W2 % 1 for a in range(1, a_max + 1)]
