"""For each claimed (R1) pinch vertex, classify it as:
  (a) a cut-vertex of O = G[L_{d+1} cap V_{C'}]   (level d+1 pinch)
  (b) a cut-vertex of G[L_d cap V_{C'}]            (level d pinch)
  (c) other
"""
import sys
from collections import defaultdict

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


def face_edges_set(face):
    return frozenset(frozenset(e) for e in face)


def is_cut_vertex(G, v):
    """Return True if removing v disconnects the connected component of v."""
    if v not in G:
        return False
    H = G.copy()
    H.delete_vertex(v)
    if H.order() == 0:
        return False
    return not H.is_connected()


def main():
    counts = defaultdict(int)
    n_components = 0
    n_pinches = 0
    sample_each = {}
    for n in range(7, 12):
        for G in graphs.triangulations(n):
            G.is_planar(set_embedding=True)
            emb = G.get_embedding()
            faces = G.faces(embedding=emb)
            for v_source in G.vertices():
                dist = G.shortest_path_lengths(v_source)
                face_depths = []
                for face in faces:
                    verts = set()
                    for u, v in face:
                        verts.add(u); verts.add(v)
                    d = min(dist[u] for u in verts)
                    face_depths.append((face, d, verts))
                max_d = max(fd[1] for fd in face_depths)
                for target_d in range(max_d + 1):
                    cands = [(i, fd) for i, fd in enumerate(face_depths)
                             if fd[1] == target_d]
                    if not cands:
                        continue
                    edge_to_idxs = defaultdict(list)
                    for k, (i, fd) in enumerate(cands):
                        for e in [frozenset(ed) for ed in fd[0]]:
                            edge_to_idxs[e].append(k)
                    n_c = len(cands)
                    adj = [[] for _ in range(n_c)]
                    for idxs in edge_to_idxs.values():
                        if len(idxs) >= 2:
                            for a in idxs:
                                for b in idxs:
                                    if a != b:
                                        adj[a].append(b)
                    visited = [False] * n_c
                    comps = []
                    for s in range(n_c):
                        if visited[s]:
                            continue
                        stack = [s]; comp = []
                        while stack:
                            x = stack.pop()
                            if visited[x]: continue
                            visited[x] = True; comp.append(cands[x])
                            for y in adj[x]:
                                if not visited[y]: stack.append(y)
                        comps.append(comp)

                    for comp in comps:
                        n_components += 1
                        comp_fes_set = {face_edges_set(fd[0])
                                        for _, fd in comp}
                        vertices_in_comp = set()
                        for fes in comp_fes_set:
                            for e in fes:
                                vertices_in_comp.update(e)
                        Vd = {v for v in vertices_in_comp if dist[v] == target_d}
                        Vdp1 = {v for v in vertices_in_comp if dist[v] == target_d + 1}
                        O = G.subgraph(vertices=list(Vdp1))
                        Bout_graph = G.subgraph(vertices=list(Vd))
                        for v in vertices_in_comp:
                            rot = emb[v]
                            in_c = []
                            for i in range(len(rot)):
                                u = rot[i]
                                w = rot[(i + 1) % len(rot)]
                                tri = frozenset({frozenset({v, u}),
                                                  frozenset({u, w}),
                                                  frozenset({v, w})})
                                in_c.append(tri in comp_fes_set)
                            n_true = sum(in_c)
                            if n_true == 0 or n_true == len(in_c):
                                continue
                            n_runs = sum(
                                1 for i in range(len(in_c))
                                if in_c[i] and not in_c[(i - 1) % len(in_c)])
                            if n_runs <= 1:
                                continue
                            # Pinch at v.  Classify.
                            n_pinches += 1
                            level_v = dist[v]
                            if level_v == target_d:
                                lvl_label = 'd'
                                # Check if v is a cut vertex of B_out_graph
                                cut_bout = is_cut_vertex(Bout_graph, v)
                                key = ('d', cut_bout)
                            elif level_v == target_d + 1:
                                lvl_label = 'd+1'
                                cut_o = is_cut_vertex(O, v)
                                key = ('d+1', cut_o)
                            else:
                                key = ('other', None)
                            counts[key] += 1
                            if key not in sample_each:
                                sample_each[key] = {
                                    'n': n, 'source': int(v_source),
                                    'depth': target_d, 'pinch_v': v,
                                    'level_v': level_v,
                                    'edges': sorted(tuple(sorted(e))
                                                    for e in G.edges(labels=False)),
                                }

    print(f"Total components: {n_components}")
    print(f"Total pinches: {n_pinches}")
    print()
    print("Classification by (level of pinch, is cut-vertex of corresponding side):")
    for k, c in sorted(counts.items()):
        print(f"  {k}: {c}")
    print()
    print("Samples:")
    for k, sample in sample_each.items():
        print(f"  key={k}: {sample}")


if __name__ == '__main__':
    main()
