"""Closed-chain consistency under SR + PDS with both endpoints constrained.

A planar triangulation $G$ with PDS source vertex has tire decomposition:
- Innermost: T_1 has degenerate B_in (just the source vertex);
  B_out = L_1 (the source's neighbour cycle).
- Outermost: T_n has B_out = outer-triangle face boundary (length 3
  for a triangulated disk), or B_out = L_{d_max}.

For proper edge 3-colouring of G', the outer face's dual vertex
(degree 3, incident to the 3 outer-triangle edge duals) requires
σ at the outer triangle to be a permutation of {1,2,3}.

So the closed-chain question is: starting from T_1's σ_U-support
(full proper cycle colourings of C_{m_1}), forward-propagate through
T_2, ..., T_n; does the final state at L_n = outer triangle contain
at least one permutation of {1,2,3}?
"""
from __future__ import annotations

from itertools import permutations

import numpy as np

from tire_fiber_chords import d_positions_for, u_positions_for
from tire_fiber_chords_fast import proper_cycle_colorings, induced_sigma_vec
from sr_chain_consistency import joint_support_SR


def degenerate_inner_sigma_U(m: int) -> set:
    """For tire with degenerate B_in (single apex) and B_out cycle of
    length m: σ at the m outer-spoke pendant positions.

    Each cycle-coloring of C_m forces σ at each vertex = third colour
    avoiding the two cycle edges at that vertex.  So σ-support is the
    set of induced-σ vectors over all proper cycle 3-colorings of C_m.
    """
    if m < 3:
        return set()
    c = proper_cycle_colorings(m)
    sigma = induced_sigma_vec(c)
    return set(map(tuple, sigma.tolist()))


def closed_chain_propagate(chain: list[tuple[int, int]], inner_degenerate: bool = True):
    """Forward-propagate state through the tire chain.

    chain = [(m_1, k_1), (m_2, k_2), ...] for tires T_1, ..., T_n.
    Adjacency: k_{i+1} = m_i.
    If inner_degenerate: T_1 has degenerate B_in (k_1 is the apex,
    treated as a single vertex); use the degenerate-inner σ_U support.
    Otherwise: use the joint support's σ_U projection (treat L_0 free).

    Returns: final state, trajectory of state sizes, and whether the
    outer-triangle constraint is satisfied (if m_n == 3).
    """
    # Adjacency check
    for i in range(len(chain) - 1):
        if chain[i][0] != chain[i + 1][1]:
            return {'error': f'adjacency mismatch at i={i}'}

    pis = [joint_support_SR(m, k) for (m, k) in chain]

    # Initial state
    m_1, k_1 = chain[0]
    if inner_degenerate:
        state = degenerate_inner_sigma_U(m_1)
    else:
        state = {p[0] for p in pis[0]}  # σ_U from full joint support
    trajectory = [len(state)]

    # Forward through T_2, T_3, ...
    failed_at = None
    for i in range(1, len(chain)):
        pi = pis[i]
        new_state = set()
        for sigma_outer, sigma_inner in pi:
            if sigma_inner in state:
                new_state.add(sigma_outer)
        # Also try reflection (T_{i+1} may see γ in reversed orientation):
        new_state_rev = set()
        for sigma_outer, sigma_inner in pi:
            if sigma_inner[::-1] in state:
                new_state_rev.add(sigma_outer)
        # Pick whichever gives more (best-case orientation)
        if len(new_state_rev) > len(new_state):
            new_state = new_state_rev
        trajectory.append(len(new_state))
        if not new_state:
            failed_at = i
            break
        state = new_state

    if failed_at is not None:
        return {
            'chain': chain,
            'compatible': False,
            'failed_at': failed_at,
            'trajectory': trajectory,
        }

    # Outer-triangle constraint: if m_n == 3, state contains a permutation?
    m_n = chain[-1][0]
    outer_constraint_satisfied = None
    if m_n == 3:
        perms = set(permutations([1, 2, 3]))
        contains_perm = state & perms
        outer_constraint_satisfied = bool(contains_perm)
    return {
        'chain': chain,
        'compatible': True,
        'final_state_size': len(state),
        'trajectory': trajectory,
        'final_state_sample': sorted(state)[:5],
        'outer_constraint_satisfied': outer_constraint_satisfied,
    }


# --- Test scenarios ---
# Each chain is a tire-decomposition of a planar triangulated disk
# with source vertex degree d_0 (giving m_1 = d_0) and outer triangle
# (m_n = 3).  Cycles grow then shrink.

CHAINS = [
    # Source degree 5 (Birkhoff min), growing/shrinking.
    [(5, 1), (6, 5), (5, 6), (3, 5)],
    [(5, 1), (7, 5), (5, 7), (3, 5)],
    [(5, 1), (8, 5), (6, 8), (3, 6)],
    [(5, 1), (8, 5), (8, 8), (5, 8), (3, 5)],

    # Source degree 6
    [(6, 1), (7, 6), (5, 7), (3, 5)],
    [(6, 1), (8, 6), (6, 8), (3, 6)],
    [(6, 1), (9, 6), (8, 9), (5, 8), (3, 5)],

    # Longer chain
    [(5, 1), (6, 5), (8, 6), (9, 8), (8, 9), (6, 8), (3, 6)],
    [(6, 1), (8, 6), (10, 8), (10, 10), (8, 10), (5, 8), (3, 5)],

    # Edge case: degree 7
    [(7, 1), (9, 7), (7, 9), (3, 7)],
]


def main():
    print("Closed-chain consistency under SR + PDS")
    print("Each chain: source-degenerate T_1 ... outer-triangle T_n.")
    print()
    print(f"{'chain':<55s} {'trajectory':<40s} {'outer-perm':>10s}")
    print("-" * 110)

    for chain in CHAINS:
        # Use inner_degenerate=True for the first tire; k_1 in the tuple
        # is a placeholder (degenerate apex has no real cycle).
        r = closed_chain_propagate(chain, inner_degenerate=True)
        chain_str = " | ".join(f"({m},{k})" for (m, k) in chain)
        if r.get('error'):
            print(f"  {chain_str}: ERROR {r['error']}")
            continue
        if r.get('compatible'):
            traj_str = "→".join(str(x) for x in r['trajectory'])
            outer = (
                "✓"
                if r['outer_constraint_satisfied']
                else "✗ NO PERM!"
                if r['outer_constraint_satisfied'] is not None
                else "n/a"
            )
            print(f"  {chain_str:<53s} {traj_str:<40s} {outer:>10s}")
            if r['outer_constraint_satisfied'] is False:
                print(f"      → final state sample: {r['final_state_sample']}")
        else:
            print(f"  {chain_str:<53s} {'INCOMPATIBLE at i='+str(r['failed_at']):<40s} {'×':>10s}")
            print(f"      trajectory: {r['trajectory']}")


if __name__ == '__main__':
    main()
