"""
Perfect Cuboid Analysis — Structural Insights
===============================================

Deeper analysis of the constraint structure, implementing
framework patterns:
  - AX23 (Cascade): constraint propagation analysis
  - AX34/M2 (Constitutive wound): gap measurement
  - AX37 (Holographic): self-similarity in Euler brick families
  - T14 (NeAynNeGayr): fidelity spectrum of near-misses
"""

from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Sequence

from .search import check_cuboid, CuboidResult, is_perfect_square


# ---------------------------------------------------------------------------
# Cascade Analysis — AX23
# ---------------------------------------------------------------------------

@dataclass
class CascadeAnalysis:
    """Analysis of constraint propagation through the 4-equation system.

    The 4 equations form a cascade (AX23):
      Eq1 constrains Eq2 (shared a)
      Eq2 constrains Eq3 (shared c from Eq2, b from Eq1)
      Eq4 is fully determined by any 2 of {Eq1, Eq2, Eq3}

    This dataclass captures the propagation structure.
    """
    a: int
    b: int
    c: int
    # Which equations are satisfiable individually?
    eq1_satisfiable: bool  # a² + b²
    eq2_satisfiable: bool  # a² + c²
    eq3_satisfiable: bool  # b² + c²
    eq4_satisfiable: bool  # a² + b² + c²
    # Cascade depth: how far constraints propagate before failing
    cascade_depth: int  # 0-4
    # Where the cascade breaks
    first_failure: str | None  # "eq1", "eq2", "eq3", "eq4", or None


def analyze_cascade(a: int, b: int, c: int) -> CascadeAnalysis:
    """Trace constraint propagation through the 4-equation cascade."""
    eq1 = is_perfect_square(a * a + b * b)
    eq2 = is_perfect_square(a * a + c * c)
    eq3 = is_perfect_square(b * b + c * c)
    eq4 = is_perfect_square(a * a + b * b + c * c)

    # Cascade depth: how many consecutive equations (in order) pass
    depth = 0
    first_fail = None
    for i, (sat, name) in enumerate([(eq1, "eq1"), (eq2, "eq2"),
                                       (eq3, "eq3"), (eq4, "eq4")]):
        if sat:
            depth += 1
        elif first_fail is None:
            first_fail = name

    return CascadeAnalysis(
        a=a, b=b, c=c,
        eq1_satisfiable=eq1,
        eq2_satisfiable=eq2,
        eq3_satisfiable=eq3,
        eq4_satisfiable=eq4,
        cascade_depth=depth,
        first_failure=first_fail,
    )


# ---------------------------------------------------------------------------
# Modular Obstruction Analysis
# ---------------------------------------------------------------------------

def analyze_modular_obstruction(a: int, b: int, c: int,
                                 primes: Sequence[int] = (2, 3, 5, 7, 11, 13)
                                 ) -> dict:
    """Analyze the modular residue structure of a triple.

    For each prime p, compute the residues of a², b², c², and their
    sums modulo p. This reveals which primes create obstructions
    for the space diagonal.

    The "arithmetic propagation" mechanism (Yelle 2026) works by
    showing that certain prime divisors of face-triangle remainders
    must propagate to the space diagonal, creating contradictions.
    """
    analysis = {}
    s_ab = a * a + b * b
    s_ac = a * a + c * c
    s_bc = b * b + c * c
    s_space = a * a + b * b + c * c

    for p in primes:
        residues = {
            "a²_mod_p": (a * a) % p,
            "b²_mod_p": (b * b) % p,
            "c²_mod_p": (c * c) % p,
            "s_ab_mod_p": s_ab % p,
            "s_ac_mod_p": s_ac % p,
            "s_bc_mod_p": s_bc % p,
            "s_space_mod_p": s_space % p,
            # Quadratic residues modulo p
            "qr_set": set(x * x % p for x in range(p)),
        }

        # Check if each sum is a quadratic residue mod p
        qr = residues["qr_set"]
        residues["s_ab_is_qr"] = residues["s_ab_mod_p"] in qr
        residues["s_ac_is_qr"] = residues["s_ac_mod_p"] in qr
        residues["s_bc_is_qr"] = residues["s_bc_mod_p"] in qr
        residues["s_space_is_qr"] = residues["s_space_mod_p"] in qr

        # Key: if s_space is NOT a QR mod p, no solution exists
        residues["modular_obstruction"] = not residues["s_space_is_qr"]

        analysis[p] = residues

    return analysis


# ---------------------------------------------------------------------------
# Fidelity Spectrum — T14 (NeAynNeGayr)
# ---------------------------------------------------------------------------

def compute_fidelity_spectrum(limit: int) -> list[dict]:
    """Compute the fidelity distribution across all triples.

    T14: Every Euler brick has fidelity 0 < F < 1 relative to
    the perfect cuboid ideal. We measure:
      F = equations_satisfied / 4

    This creates a continuous spectrum (AX21) where:
      F = 0/4 = 0.00: no equation satisfied
      F = 1/4 = 0.25: one face diagonal integer
      F = 2/4 = 0.50: two face diagonals integer
      F = 3/4 = 0.75: Euler brick (three face diagonals)
      F = 4/4 = 1.00: Perfect cuboid (unreachable supremum?)

    The distribution reveals the convergent abstraction funnel:
    ∞ → many → few → ? → 0 (or 1?)
    """
    spectrum = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}

    for a in range(1, limit + 1):
        for b in range(a, limit + 1):
            for c in range(b, limit + 1):
                r = check_cuboid(a, b, c)
                spectrum[r.equations_satisfied] += 1

    total = sum(spectrum.values())
    result = []
    for eq_count, count in sorted(spectrum.items()):
        result.append({
            "equations_satisfied": eq_count,
            "fidelity": eq_count / 4,
            "count": count,
            "proportion": count / total if total > 0 else 0,
        })

    return result


# ---------------------------------------------------------------------------
# Holographic Self-Similarity — AX37
# ---------------------------------------------------------------------------

def check_holographic_property(euler_bricks: list[CuboidResult]) -> dict:
    """Check if Euler brick families exhibit holographic self-similarity.

    AX37: Parts contain compressed images of wholes.

    Hypothesis: Euler brick triples exhibit scaling patterns —
    if (a, b, c) is an Euler brick, then k*(a, b, c) is also one.
    This would be the holographic seed property: the structure
    of the smallest Euler brick encodes the structure of the family.
    """
    if not euler_bricks:
        return {"holographic_seed": False, "families": []}

    families = []
    seen = set()

    for brick in euler_bricks:
        key = (brick.a, brick.b, brick.c)
        if key in seen:
            continue

        # Check if this is a multiple of a smaller Euler brick
        g = math.gcd(math.gcd(brick.a, brick.b), brick.c)
        primitive = (brick.a // g, brick.b // g, brick.c // g)
        primitive_r = check_cuboid(*primitive)

        family = {
            "primitive": primitive,
            "primitive_is_euler": primitive_r.is_euler_brick,
            "scaling_factor": g,
            "members": [key],
        }

        # Check multiples
        for k in range(2, 6):
            multiple = (brick.a * k, brick.b * k, brick.c * k)
            mr = check_cuboid(*multiple)
            if mr.is_euler_brick:
                family["members"].append(multiple)
                seen.add(multiple)

        families.append(family)
        seen.add(key)

    return {
        "holographic_seed": any(f["primitive_is_euler"] for f in families),
        "families": families,
        "total_families": len(families),
    }
