"""
Coupling Energy & Coupling Angle — Theorems R and the Coupling Angle Invariant
===============================================================================

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

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

import math

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,
)

__all__ = [
    'coupling_energy',
    'upper_triangle',
    'lower_triangle',
    'coupling_angle',
    'coupling_torque',
    'gram_matrix',
    'trace_squared_decomposition',
]


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_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,
    }
