#!/usr/bin/env python3
"""
Multi-Strand Braid Word Solver.

Full 16-modulus chiral torus: up to 8 strands, each with (L_id, L_ref).
Generates multi-strand braid words with coprimality constraints.
"""
import sys, math, itertools, random
from typing import List, Tuple
sys.path.insert(0, '/home/allaun/SilverSight/scripts')
from verify_wrapping import is_sidon

# ---------- Coprime CRT ----------

def pairwise_coprime(mods: List[int]) -> bool:
    for i in range(len(mods)):
        for j in range(i+1, len(mods)):
            if math.gcd(mods[i], mods[j]) != 1:
                return False
    return True

def crt_lift(residues: List[int], moduli: List[int]) -> int:
    assert pairwise_coprime(moduli), f"not coprime: {moduli}"
    x = residues[0]
    m = moduli[0]
    for i in range(1, len(moduli)):
        inv = pow(m % moduli[i], -1, moduli[i])
        t = ((residues[i] - x) * inv) % moduli[i]
        x += t * m
        m *= moduli[i]
    return x

def F_multi(a: int, S: int, moduli: List[int]) -> int:
    residues = [a % moduli[0]] + [(S - a) % Li for Li in moduli[1:]]
    return crt_lift(residues, moduli)

def moduli_from_pairs(pairs: List[Tuple[int,int]]) -> List[int]:
    return [v for p in pairs for v in p]

# ---------- Generate valid coprime configurations ----------

PRIME_POOL = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]

def coprime_pairs(count: int, pool: List[int] = None) -> List[Tuple[int,int]]:
    """Generate `count` coprime pairs using distinct primes."""
    if pool is None:
        pool = PRIME_POOL
    used = set()
    pairs = []
    p_idx = 0
    for _ in range(count):
        p1, p2 = pool[p_idx], pool[p_idx+1]
        pairs.append((p1, p2))
        p_idx += 2
    return pairs

def cross(pairs: List[Tuple[int,int]], strand: int, over: bool) -> List[Tuple]:
    """Cross strand `strand` (over or under), return new config or None."""
    new = [p for p in pairs]
    Li, Lr = new[strand]
    if over:
        new[strand] = (Li + 2, max(Lr - 1, 2))
    else:
        new[strand] = (max(Li - 1, 2), Lr + 2)
    mods = moduli_from_pairs(new)
    return new if pairwise_coprime(mods) else None

def braid_word(pairs_seq: List[List[Tuple]]) -> str:
    """Build braid word from a sequence of configurations."""
    parts = []
    for i in range(1, len(pairs_seq)):
        prev, curr = pairs_seq[i-1], pairs_seq[i]
        for s in range(len(curr)):
            if curr[s] == prev[s]:
                continue
            Li, Lr = curr[s]
            typ = "⁺" if Li > Lr else "⁻"
            parts.append(f"σ_{s+1}{typ}")
    return " · ".join(parts) if parts else "1"

# ---------- Multi-strand Sidon search ----------

def multi_search(A0: List[int], S: int, num_strands: int = 2, max_steps: int = 3):
    """BFS for multi-strand braid words to Sidon."""
    init = coprime_pairs(num_strands)
    queue = [(init, 0, A0, [init])]
    visited = set()
    results = []
    
    while queue and len(results) < 20:
        pairs, depth, A, path = queue.pop(0)
        key = (tuple(pairs), tuple(A))
        if key in visited: continue
        visited.add(key)
        
        mods = moduli_from_pairs(pairs)
        M = 1
        for m in mods: M *= m
        maxA = max(A)
        
        if M > maxA:
            FA = [F_multi(a, S, mods) for a in A]
            if is_sidon(FA):
                results.append({
                    'word': braid_word(path),
                    'steps': depth, 'FA': FA, 'M': M,
                    'path': path
                })
                continue
        
        if depth >= max_steps:
            continue
        
        for s in range(num_strands):
            for over in [True, False]:
                crossed = cross(pairs, s, over)
                if crossed is None:
                    continue
                mods2 = moduli_from_pairs(crossed)
                new_A = [F_multi(a, S, mods2) for a in A]
                queue.append((crossed, depth+1, new_A, path + [crossed]))
    
    return results

# ---------- Braid axiom tests ----------

def test_involution():
    """σᵢ² = id: two over-crossings should return to original."""
    print("=" * 60)
    print("BRAID AXIOM TESTS")
    print("=" * 60)
    
    A0, S = [1, 2, 5, 6], 7
    init = coprime_pairs(2)  # [(2,3), (5,7)]
    
    # σ₁ over then σ₁ over
    s1 = cross(init, 0, True)
    s1a = cross(s1, 0, True) if s1 else None
    print(f"\n  σ₁²: {(2,3)} → over→ {s1[0] if s1 else '? ()'}"
          f" → over→ {s1a[0] if s1a else '? ()'}"
          f" back to initial: {s1a == init if s1a else False}")

def test_far_commute():
    """σᵢσⱼ = σⱼσᵢ for |i−j| ≥ 2: strand 1 and 3 commute."""
    A0, S = [1, 2, 5, 6], 7
    init = coprime_pairs(3)
    print(f"\n  σ₁σ₃ vs σ₃σ₁ on {init}:")
    
    # σ₁ then σ₃
    s1 = cross(init, 0, True)
    s1s3 = cross(s1, 2, False) if s1 else None
    # σ₃ then σ₁
    s3 = cross(init, 2, False)
    s3s1 = cross(s3, 0, True) if s3 else None
    
    if s1s3 and s3s1:
        # Same final configuration?
        same = s1s3 == s3s1
        w1 = braid_word([init, s1, s1s3])
        w2 = braid_word([init, s3, s3s1])
        print(f"    σ₁σ₃: {w1}  → {s1s3}")
        print(f"    σ₃σ₁: {w2}  → {s3s1}")
        print(f"    Same: {same}")

def test_single_sidon():
    """Find single-step Sidon paths for each strand."""
    print("\n" + "=" * 60)
    print("MULTI-STRAND SIDON SEARCH (2 strands)")
    print("=" * 60)
    A0, S = [1, 2, 5, 6], 7
    results = multi_search(A0, S, num_strands=2, max_steps=2)
    print(f"  Results: {len(results)}")
    for r in sorted(results, key=lambda x: x['steps'])[:5]:
        print(f"  Word: {r['word']:20s} Steps={r['steps']} M={r['M']:4d} FA={r['FA']}")

def test_strand_interaction():
    """Test 2-strand configurations produce distinct results."""
    print("\n" + "=" * 60)
    print("STRAND INTERACTION")
    print("=" * 60)
    
    A0, S = [1, 2, 5, 6], 7
    configs = [
        ([(2, 3), (5, 7)], "under, under"),
        ([(4, 2), (5, 7)], "over on 1, under on 2"),  # but gcd(4,2)=2!
    ]
    
    for pairs, desc in configs:
        mods = moduli_from_pairs(pairs)
        if not pairwise_coprime(mods):
            continue
        FA = [F_multi(a, S, mods) for a in A0]
        M = 1
        for m in mods: M *= m
        sidon = is_sidon(FA)
        print(f"  {desc:30s} mods={mods} M={M:3d} Sidon={sidon} FA={FA}")

if __name__ == "__main__":
    test_involution()
    test_far_commute()
    test_single_sidon()
    test_strand_interaction()
