"""Characterize the exception family for Lemma 5.8.

For each exception (md4 iso-class without a good contraction) at n=9..12,
extract:
  1. The deg-4 vertices and whether they form 2 disjoint triangles
  2. The high-degree "core" and its edge structure
  3. The bipartite-like structure between deg-4 set and core
  4. Whether the exception is constructed by some uniform recipe
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import networkx as nx
from collections import Counter, defaultdict
from triangulation_gen import enumerate_all_triangulations
from triangulation_gen_fast import enumerate_all_triangulations_fast


def is_md4(T):
    return min(T.degree(v) for v in T.nodes()) >= 4


def good_contractions_for_vertex(T, v):
    if T.degree(v) != 4:
        return []
    ip, emb = nx.check_planarity(T)
    if not ip:
        return []
    cyc = list(emb.neighbors_cw_order(v))
    if len(cyc) != 4:
        return []
    a, b, c, d = cyc
    good = []
    for (u, w, opp1, opp2) in [(a, c, b, d), (b, d, a, c)]:
        if T.has_edge(u, w):
            continue
        if T.degree(opp1) - 1 < 4 or T.degree(opp2) - 1 < 4:
            continue
        good.append((u, w))
    return good


def has_good_contraction(T):
    for v in T.nodes():
        if good_contractions_for_vertex(T, v):
            return True
    return False


def find_exceptions(n):
    if n >= 11:
        isos = enumerate_all_triangulations_fast(n)
    else:
        isos = enumerate_all_triangulations(n)
    md4_indices = [i for i, T in enumerate(isos) if is_md4(T)]
    exceptions = []
    for i in md4_indices:
        T = isos[i]
        # Skip md5+ (no deg-4 vertex, so contraction trivially N/A)
        if not any(T.degree(v) == 4 for v in T.nodes()):
            continue
        if not has_good_contraction(T):
            exceptions.append((i, T))
    return exceptions


def analyze_exception(T, label=""):
    """Print structural analysis of an exception triangulation."""
    deg4 = [v for v in T.nodes() if T.degree(v) == 4]
    high = [v for v in T.nodes() if T.degree(v) >= 5]
    print(f"  {label}: |deg-4|={len(deg4)}, |high|={len(high)}")
    # Subgraph on deg-4 vertices
    Td4 = T.subgraph(deg4)
    deg4_edges = sorted(Td4.edges())
    print(f"    deg-4 subgraph edges: {deg4_edges}")
    # Find triangles in deg-4 subgraph
    triangles = []
    deg4_set = set(deg4)
    for v in deg4:
        for u in deg4:
            if u >= v:
                continue
            for w in deg4:
                if w >= u:
                    continue
                if T.has_edge(v, u) and T.has_edge(u, w) and T.has_edge(v, w):
                    triangles.append(tuple(sorted([v, u, w])))
    triangles = sorted(set(triangles))
    print(f"    triangles within deg-4 set: {triangles}")
    # Each deg-4 vertex's high-degree neighbors
    print(f"    deg-4 vertices -> their high-degree neighbors:")
    for v in deg4:
        hi_nbrs = sorted(u for u in T.neighbors(v) if T.degree(u) >= 5)
        print(f"      {v}: {hi_nbrs}")
    # Subgraph on high-degree vertices
    Thigh = T.subgraph(high)
    high_edges = sorted(Thigh.edges())
    high_degs = {v: Thigh.degree(v) for v in high}
    print(f"    high-degree subgraph edges: {high_edges}")
    print(f"    high-degree subgraph degrees: {high_degs}")
    # Bipartite-like check: each deg-4 vertex has exactly 2 high neighbors?
    deg4_high_counts = Counter()
    for v in deg4:
        hi_nbrs = [u for u in T.neighbors(v) if T.degree(u) >= 5]
        deg4_high_counts[len(hi_nbrs)] += 1
    print(f"    # deg-4 vertices grouped by # high neighbors: "
          f"{dict(deg4_high_counts)}")
    # Each high vertex's deg-4 neighbors
    print(f"    high vertices -> # deg-4 neighbors:")
    for h in high:
        d4_nbrs = [u for u in T.neighbors(h) if T.degree(u) == 4]
        print(f"      {h} (deg {T.degree(h)}): {len(d4_nbrs)} deg-4 nbrs")


def main():
    for n in [9, 10, 11, 12]:
        print(f"\n=== n={n} ===")
        exceptions = find_exceptions(n)
        print(f"  {len(exceptions)} exception(s) (excluding md5+)")
        for idx, T in exceptions:
            print(f"\n  iso[{idx}]:")
            analyze_exception(T)


if __name__ == "__main__":
    main()
