"""
Watkins Spectral Kaleidoscope — Integer Sequences
==================================================

The complete integer content of the Watkins Spectral Kaleidoscope.
Nine sequences arising from quantum stabilizer weight-enumerators
parametrized by the golden ratio.

┌─────┬─────────────────────────────┬──────────┬───────────┐
│  #  │ Sequence                   │ σ-param  │ OEIS     │
├─────┼─────────────────────────────┼──────────┼───────────┤
│  1  │ Cycle L-B numerators       │ 0       │ A393329  │
│  2  │ Mersenne numerators        │ 0       │ A394248  │
│  3  │ Mersenne-factorial GCD     │ 0       │ A394249  │
│  4  │ Lucas numerators           │ 1       │ (pending)│
│  5  │ Lucas-factorial GCD        │ 1       │ (pending)│
│  6  │ Mersenne-Lucas GCD         │ 0∩1     │ (pending)│
│  7  │ Binary Preservation        │ bridge  │ (new)    │
│  8  │ Cross-GCD                  │ bridge  │ (new)    │
│  9  │ Weight-Enumerator Moments  │ trace   │ A005248  │
└─────┴─────────────────────────────┴──────────┴───────────┘

© 2024-2026 Dustin Watkins & DataSphere AI
"""

from math import gcd, factorial
from functools import lru_cache
from typing import List, Dict, Tuple, Optional, Callable
from dataclasses import dataclass

import numpy as np

__all__ = [
    'lucas', 'fibonacci', 'mersenne', 'popcount', 'prime_factors',
    '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',
    'generate_all_sequences', 'verify_pairings', 'find_seq8_spikes',
    'SequenceInfo', 'KaleidoscopeMatrix', 'PrimeTracker',
    'KaleidoscopeTensor',
]

# ════════════════════════════════════════════════════════════════════════════════
# FUNDAMENTAL SEQUENCES
# ════════════════════════════════════════════════════════════════════════════════

@lru_cache(maxsize=1024)
def lucas(n: int) -> int:
    """Lucas number L(n) = φ^n + ψ^n."""
    if n == 0: return 2
    if n == 1: return 1
    a, b = 2, 1
    for _ in range(n - 1):
        a, b = b, a + b
    return b

@lru_cache(maxsize=1024)
def fibonacci(n: int) -> int:
    """Fibonacci number F(n)."""
    if n == 0: return 0
    if n == 1: return 1
    a, b = 0, 1
    for _ in range(n - 1):
        a, b = b, a + b
    return b

def mersenne(n: int) -> int:
    """Mersenne number M(n) = 2^n - 1."""
    return (1 << n) - 1

def popcount(x: int) -> int:
    """Count set bits (Hamming weight)."""
    return bin(x).count('1')

def prime_factors(n: int) -> List[int]:
    """Return list of prime factors with multiplicity."""
    if n <= 1:
        return []
    factors = []
    d = 2
    while d * d <= n:
        while n % d == 0:
            factors.append(d)
            n //= d
        d += 1
    if n > 1:
        factors.append(n)
    return factors

# ════════════════════════════════════════════════════════════════════════════════
# KALEIDOSCOPE SEQUENCES
# ════════════════════════════════════════════════════════════════════════════════

def seq1_cycle_lb_numerator(n: int) -> int:
    """A393329: Cycle Lagrange-Burmann numerators (σ=0)."""
    if n == 0: return 1
    M = mersenne(n + 1)
    return M // gcd(M, factorial(n + 1))

def seq2_mersenne_numerator(n: int) -> int:
    """A394248: Reduced numerators of (2^{n+2}-1)/(n+1)!."""
    M = mersenne(n + 2)
    return M // gcd(M, factorial(n + 1))

def seq3_mersenne_gcd(n: int) -> int:
    """A394249: gcd(2^{n+2}-1, (n+1)!)."""
    return gcd(mersenne(n + 2), factorial(n + 1))

def seq4_lucas_numerator(n: int) -> int:
    """Reduced numerators of L(n+1)/(n+1)!."""
    L = lucas(n + 1)
    return L // gcd(L, factorial(n + 1))

def seq5_lucas_gcd(n: int) -> int:
    """gcd(L(n+1), (n+1)!)."""
    return gcd(lucas(n + 1), factorial(n + 1))

def seq6_mersenne_lucas_gcd(n: int) -> int:
    """gcd(2^n - 1, L(n)) — Mersenne-Lucas interference.

    Divisibility rules (CRT-derived):
      - 3 | a(n) iff n ≡ 2 (mod 4)
      - 7 | a(n) iff n ≡ 12 (mod 24)
      - 31 | a(n) iff n ≡ 15 (mod 30)
    """
    if n == 0: return 2
    return gcd(mersenne(n), lucas(n))

def seq7_binary_preservation(n: int) -> int:
    """popcount(seq1(n) XOR seq4(n)) — Hamming distance across Watkins Bridge."""
    return popcount(seq1_cycle_lb_numerator(n) ^ seq4_lucas_numerator(n))

def seq8_cross_gcd(n: int) -> int:
    """gcd(seq1(n), seq4(n)) — shared primes across Mersenne/Lucas numerators.

    Extremely sparse; all spike values (>1) are prime.
    """
    return gcd(seq1_cycle_lb_numerator(n), seq4_lucas_numerator(n))

def seq9_weight_moments(k: int) -> int:
    """L(2k) = trace(T*^k) — even-indexed Lucas numbers.

    These are the phi^2-powered integer moments from the weight-enumerator
    transfer matrix. Equals A005248 (Lucas bisection); the quantum
    interpretation as trace(T*^k) is new.
    """
    return lucas(2 * k)

# ════════════════════════════════════════════════════════════════════════════════
# UTILITIES
# ════════════════════════════════════════════════════════════════════════════════

def generate_all_sequences(count: int = 20) -> Dict[str, List[int]]:
    """Generate all 9 Kaleidoscope sequences."""
    return {
        'A393329': [seq1_cycle_lb_numerator(n) for n in range(count)],
        'A394248': [seq2_mersenne_numerator(n) for n in range(count)],
        'A394249': [seq3_mersenne_gcd(n) for n in range(count)],
        'lucas_num': [seq4_lucas_numerator(n) for n in range(count)],
        'lucas_gcd': [seq5_lucas_gcd(n) for n in range(count)],
        'ml_interference': [seq6_mersenne_lucas_gcd(n) for n in range(1, count + 1)],
        'binary_preservation': [seq7_binary_preservation(n) for n in range(count)],
        'cross_gcd': [seq8_cross_gcd(n) for n in range(count)],
        'A005248': [seq9_weight_moments(k) for k in range(count)],
    }

def verify_pairings(count: int = 20) -> bool:
    """Verify numerator x GCD = base value for both families."""
    for n in range(count):
        # Mersenne: seq2 x seq3 = 2^{n+2} - 1
        if seq2_mersenne_numerator(n) * seq3_mersenne_gcd(n) != mersenne(n + 2):
            return False
        # Lucas: seq4 x seq5 = L_{n+1}
        if seq4_lucas_numerator(n) * seq5_lucas_gcd(n) != lucas(n + 1):
            return False
    return True

def find_seq8_spikes(limit: int = 100) -> List[tuple]:
    """Find all spikes (values > 1) in seq8. All spike values are prime."""
    return [(n, seq8_cross_gcd(n)) for n in range(limit) if seq8_cross_gcd(n) > 1]


# ════════════════════════════════════════════════════════════════════════════════
# 3x3 MATRIX CLASS
# ════════════════════════════════════════════════════════════════════════════════

@dataclass
class SequenceInfo:
    """Metadata for a Kaleidoscope sequence."""
    index: int           # Sequence number (1-9)
    row: int             # Matrix row (0, 1, 2)
    col: int             # Matrix column (0, 1, 2)
    name: str            # Human-readable name
    oeis: Optional[str]  # OEIS ID if assigned
    func: Callable       # Generator function
    sigma: str           # Parametrization label
    channel: str         # Operation channel label


class KaleidoscopeMatrix:
    """The 3x3 operator matrix organizing the Watkins Kaleidoscope sequences.

    Rows (sigma-parametrizations):
        0: Mersenne (binary, sigma=0)
        1: Bridge (interference, sigma=0 cap 1)
        2: Lucas (golden, sigma=1)

    Columns (operation channels):
        0: Numerators (lambda-channel, coherence)
        1: GCDs (kappa-channel, curvature)
        2: Preservation (eta-channel, entropy/flow)
    """

    ROW_LABELS = ['sigma=0 (Mersenne)', 'Bridge (0 cap 1)', 'sigma=1 (Lucas)']
    COL_LABELS = ['Numerators (lambda)', 'GCDs (kappa)', 'Preservation (eta)']

    def __init__(self):
        self._matrix: List[List[SequenceInfo]] = [
            [
                SequenceInfo(2, 0, 0, 'Mersenne numerators', 'A394248', seq2_mersenne_numerator, 'sigma=0', 'lambda'),
                SequenceInfo(3, 0, 1, 'Mersenne-factorial GCD', 'A394249', seq3_mersenne_gcd, 'sigma=0', 'kappa'),
                SequenceInfo(1, 0, 2, 'Cycle L-B numerators', 'A393329', seq1_cycle_lb_numerator, 'sigma=0', 'eta'),
            ],
            [
                SequenceInfo(6, 1, 0, 'Mersenne-Lucas interference', None, seq6_mersenne_lucas_gcd, 'bridge', 'lambda'),
                SequenceInfo(8, 1, 1, 'Cross-GCD (prime spikes)', None, seq8_cross_gcd, 'bridge', 'kappa'),
                SequenceInfo(7, 1, 2, 'Binary preservation', None, seq7_binary_preservation, 'bridge', 'eta'),
            ],
            [
                SequenceInfo(4, 2, 0, 'Lucas numerators', None, seq4_lucas_numerator, 'sigma=1', 'lambda'),
                SequenceInfo(5, 2, 1, 'Lucas-factorial GCD', None, seq5_lucas_gcd, 'sigma=1', 'kappa'),
                SequenceInfo(9, 2, 2, 'Weight moments', 'A005248', seq9_weight_moments, 'sigma=1', 'eta'),
            ],
        ]
        self._by_index = {}
        for row in self._matrix:
            for info in row:
                self._by_index[info.index] = info

    def __getitem__(self, key) -> SequenceInfo:
        """Access by (row, col) tuple or sequence number."""
        if isinstance(key, tuple):
            r, c = key
            return self._matrix[r][c]
        return self._by_index[key]

    def generate(self, row: int, col: int, count: int = 20, start: int = 0) -> List[int]:
        """Generate terms from sequence at position (row, col)."""
        func = self._matrix[row][col].func
        return [func(n) for n in range(start, start + count)]

    def generate_by_index(self, idx: int, count: int = 20, start: int = 0) -> List[int]:
        """Generate terms from sequence by its number (1-9)."""
        func = self._by_index[idx].func
        return [func(n) for n in range(start, start + count)]

    def row(self, r: int, count: int = 20) -> Dict[str, List[int]]:
        """Generate all sequences in a row (same sigma-parametrization)."""
        return {self._matrix[r][c].name: self.generate(r, c, count) for c in range(3)}

    def col(self, c: int, count: int = 20) -> Dict[str, List[int]]:
        """Generate all sequences in a column (same operation channel)."""
        return {self._matrix[r][c].name: self.generate(r, c, count) for r in range(3)}

    def full_matrix(self, n: int) -> np.ndarray:
        """Evaluate all 9 sequences at index n, return as 3x3 numpy array."""
        return np.array([
            [self._matrix[r][c].func(n) for c in range(3)]
            for r in range(3)
        ])


class PrimeTracker:
    """Track prime appearances across all 9 sequences."""

    def __init__(self, matrix: KaleidoscopeMatrix):
        self.matrix = matrix

    def find_spikes(self, row: int, col: int, limit: int = 100) -> List[Tuple[int, int, List[int]]]:
        """Find (position, value, factors) where sequence value > 1."""
        func = self.matrix[row, col].func
        return [(n, v, prime_factors(v)) for n in range(limit) if (v := func(n)) > 1]

    def prime_positions(self, p: int, row: int, col: int, limit: int = 200) -> List[int]:
        """Find all positions where prime p divides sequence(row, col)."""
        func = self.matrix[row, col].func
        return [n for n in range(limit) if func(n) % p == 0]

    def prime_period(self, p: int, row: int, col: int, limit: int = 200) -> Optional[int]:
        """Detect periodicity of prime p in sequence(row, col)."""
        positions = self.prime_positions(p, row, col, limit)
        if len(positions) < 2:
            return None
        diffs = [positions[i + 1] - positions[i] for i in range(len(positions) - 1)]
        return diffs[0] if len(set(diffs)) == 1 else None

    def cross_prime_map(self, limit: int = 100) -> Dict[int, Dict[str, List[int]]]:
        """For each prime in the Bridge row, find where it appears in all 9 cells."""
        bridge_primes: set = set()
        for c in range(3):
            for _, _, factors in self.find_spikes(1, c, limit):
                bridge_primes.update(factors)
        result = {}
        for p in sorted(bridge_primes):
            result[p] = {}
            for r in range(3):
                for c in range(3):
                    info = self.matrix[r, c]
                    positions = self.prime_positions(p, r, c, limit)[:5]
                    if positions:
                        result[p][f"({r},{c}) {info.name[:15]}"] = positions
        return result

    def generate_spike_sequence(self, limit: int = 200) -> Tuple[List[int], List[int]]:
        """Generate spike positions and values from Cross-GCD (#8)."""
        positions, values = [], []
        for n in range(limit):
            val = seq8_cross_gcd(n)
            if val > 1:
                positions.append(n)
                values.append(val)
        return positions, values


# ════════════════════════════════════════════════════════════════════════════════
# RE-EXPORT TENSOR (avoids circular import: seq funcs defined above)
# ════════════════════════════════════════════════════════════════════════════════

from .tensor import KaleidoscopeTensor
