"""For |S| = 8 bad colourings with hit = 8 G'-pentagons,
report the distribution of p_G (# total G'-pentagons in the reduced
dual). The structural claim is that p_G ≥ 9 always in this sub-case.

If p_G ≥ 9 always: hit = 8 < p_G ≥ 9, so ≥ 1 G'-pentagon uncovered. ✓
If p_G = 8 sometimes: those would be true structural gaps.

Run with: sage experiments/check_S8_hit8_pG.py
"""
import os
import sys
import time

from sage.all import Graph
from sage.graphs.graph_generators import graphs

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)

from check_conj_final_scaled import (
    apply_reduction,
    proper_3_edge_colorings,
    matches_chord_apex_kempe,
    kempe_cycle_set,
    edge_idx,
)
from check_heawood_on_kempe import dual_of, vertices_of_kempe


def is_g_prime_pentagon(f, named):
    if len(f) != 5: return False
    fset = {frozenset(e) for e in f}
    return not (named['side_0'] in fset or named['side_1'] in fset
                or named['spike'] in fset or named['merged'] in fset)


def test_one(D, n_G, tri_idx):
    D.is_planar(set_embedding=True)
    s8_hit8_cases = []   # list of (p_G, parent_v_cyc_degs)
    # Get cyclic degree sequence of triangulation vertices for the
    # face we're examining
    for face in D.faces():
        if len(face) != 5: continue
        for i_red in range(5):
            res = apply_reduction(D, face, i_red, 9999)
            if res is None: continue
            H = res['H']; named = res['named']
            H.is_planar(set_embedding=True)
            edges, colorings = proper_3_edge_colorings(H)
            cand = [c for c in colorings
                    if matches_chord_apex_kempe(edges, c, named)]
            v_n = 9999
            for col in cand:
                target = {named['side_0'], named['spike']}
                lower_flank = None
                for f in H.faces():
                    if target.issubset({frozenset(e) for e in f}):
                        lower_flank = f; break
                if lower_flank is None or len(lower_flank) != 5: continue
                arc_verts = [e[0] for e in lower_flank]
                if v_n not in arc_verts: continue
                k = arc_verts.index(v_n)
                cyc = arc_verts[k:] + arc_verts[:k]
                A_i = next(iter(named['side_0'] - {v_n}))
                A_ip1 = next(iter(named['spike'] - {v_n}))
                if cyc[1] == A_i and cyc[4] == A_ip1:
                    P_1, P_2 = cyc[2], cyc[3]
                elif cyc[1] == A_ip1 and cyc[4] == A_i:
                    P_2, P_1 = cyc[2], cyc[3]
                else: continue
                merged_idx = edge_idx(edges, named['merged'])
                c_col = col[merged_idx]
                c_0_col = col[edge_idx(edges, named['side_0'])]
                c_1_col = col[edge_idx(edges, named['side_1'])]
                e_AiP1 = edge_idx(edges, frozenset((A_i, P_1)))
                e_P1P2 = edge_idx(edges, frozenset((P_1, P_2)))
                if e_AiP1 is None or e_P1P2 is None: continue
                if col[e_AiP1] != c_1_col or col[e_P1P2] != c_0_col:
                    continue
                a = c_col
                other = [x for x in range(3) if x != a]
                kc_b = kempe_cycle_set(edges, col, merged_idx, (a, other[0]))
                kc_c = kempe_cycle_set(edges, col, merged_idx, (a, other[1]))
                V_b = vertices_of_kempe(edges, kc_b)
                V_c = vertices_of_kempe(edges, kc_c)
                V_union = V_b | V_c
                S = set(H.vertices()) - V_union
                if P_1 in V_union: continue
                if len(S) != 8: continue

                p_total = 0
                p_hit = 0
                for f in H.faces():
                    if not is_g_prime_pentagon(f, named): continue
                    p_total += 1
                    verts = {u for (u, v) in f} | {v for (u, v) in f}
                    if verts & S: p_hit += 1

                if p_hit == 8:
                    s8_hit8_cases.append({
                        'p_G': p_total, 'n_G': n_G, 'tri_idx': tri_idx,
                        'i_red': i_red,
                    })
    return s8_hit8_cases


def main(max_n=20, time_budget_per_n=1800):
    print("|S| = 8 colourings with hit = 8: p_G distribution\n")
    grand_cases = []
    for n in range(12, max_n + 1):
        start = time.time()
        try:
            triangulations = list(graphs.triangulations(n, minimum_degree=5))
        except Exception as ex:
            print(f"n={n}: cannot enumerate ({ex})")
            continue
        n_count = 0
        for tri_idx, G in enumerate(triangulations):
            if time.time() - start > time_budget_per_n:
                print(f"  n={n}: timeout at tri {tri_idx}")
                break
            G.is_planar(set_embedding=True)
            D = dual_of(G)
            cases = test_one(D, n, tri_idx)
            grand_cases.extend(cases)
            n_count += len(cases)
        elapsed = time.time() - start
        print(f"n={n}: {n_count} (|S|=8, hit=8) cases [{elapsed:.0f}s]")
        sys.stdout.flush()
    print()
    print("=" * 70)
    print(f"Total |S|=8 hit=8 cases: {len(grand_cases)}")
    p_G_dist = {}
    for c in grand_cases:
        p_G_dist[c['p_G']] = p_G_dist.get(c['p_G'], 0) + 1
    print("\np_G distribution among |S|=8 hit=8 cases:")
    for p in sorted(p_G_dist):
        print(f"  p_G = {p}: {p_G_dist[p]}")
    if 8 in p_G_dist and p_G_dist[8] > 0:
        print("\n⚠ STRUCTURAL GAP: p_G = 8 occurs with hit = 8")
        print("  So the G'-pentagon fallback is genuinely contradicted")
        print("  in these cases.")
    else:
        print("\n✓ p_G = 8 NEVER co-occurs with hit = 8. So in |S| = 8")
        print("  bad colourings, p_G ≥ 9 whenever hit = 8, giving")
        print("  ≥ 1 uncovered G'-pentagon. The fallback is empirically")
        print("  closed.")


if __name__ == '__main__':
    main()
