"""Draw the seam-realizability construction for Conjecture~\\ref{conj:seam-realizability}.

(a) Left: G_1, a stacked-ring triangulation with single-vertex level source
    S_1 = {0}. Vertices coloured by BFS-from-S_1 level (0, 1, 2, 3).
    The four colours visually correspond to the rooted tree of tire treads
    of G_1 -- a chain T_0 -> T_1 -> T_2 with O^{(T_d)} = G_1[L_{d+1}] on
    each tread.

(b) Right: H_5, the apex-removal seam construction. We take G_1 \\ {S_1}
    (octahedron-like, 9 vertices) and re-embed so the former fan-face
    around S_1 becomes the outer face, with L_1 as the new outer boundary
    of that disk. We then attach a triangulated annulus A_5 from L_1 to a
    fresh boundary 5-cycle, partial H_5. Vertices coloured by
    BFS-from-(partial H_5) level.

Visual claim: the BFS level labels match between (a) and (b) on
V(G_1) \\ {S_1}; the new level-0 ring of (b) is the boundary cycle
partial H_5 of length k = 5, replacing the single-vertex source S_1 of (a).
Hence T(H_5, partial H_5) is iso (combinatorial, O-preserved) to T(G_1, S_1).
"""
import math
import os
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# ---------------------------------------------------------------------------
# (a) Build G_1: source 0 + concentric triangular rings L_1, L_2, L_3.
# ---------------------------------------------------------------------------
RINGS = 3
pos_G = {0: (0.0, 0.0)}
ring_G = {0: [0]}
nxt = 1
for r in range(1, RINGS + 1):
    ids = []
    for j in range(3):
        ang = math.radians(90 + 120 * j)
        pos_G[nxt] = (r * math.cos(ang), r * math.sin(ang))
        ids.append(nxt)
        nxt += 1
    ring_G[r] = ids

G1 = nx.Graph()
G1.add_nodes_from(pos_G)
for v in ring_G[1]:
    G1.add_edge(0, v)
for r in range(1, RINGS + 1):
    a, b, c = ring_G[r]
    G1.add_edges_from([(a, b), (b, c), (c, a)])
for r in range(1, RINGS):
    inner, outer = ring_G[r], ring_G[r + 1]
    for j in range(3):
        G1.add_edge(inner[j], outer[j])              # spoke
        G1.add_edge(inner[j], outer[(j + 1) % 3])    # annulus diagonal

assert G1.number_of_edges() == 3 * G1.number_of_nodes() - 6, "G_1 not a triangulation"

level_G = nx.shortest_path_length(G1, source=0)

# ---------------------------------------------------------------------------
# (b) Build H_5: re-embed (G_1 \ {S_1}) so the former S_1 fan-face is the
# outer face (L_1 becomes the outer-most ring of that disk), then attach
# a triangulated annulus A_5 to a fresh 5-cycle partial H_5.
# ---------------------------------------------------------------------------
# Concentric placement: L_3 innermost (was G_1's outer face), L_1 outermost
# among G_1-derived vertices, partial H_5 outermost overall.
pos_H = {}
# Concentric placement with the SAME angles as panel (a): L_3 innermost,
# L_1 outermost among G_1-derived vertices. This is the topological flip
# (re-embedding) of (a); same vertex labels, same edges (minus S_1), but
# the ring with the smallest G_1-level is now at the largest radius.
RING_R = {1: 2.4, 2: 1.6, 3: 0.8}
for r in [1, 2, 3]:
    for j, v in enumerate(ring_G[r]):
        ang = math.radians(90 + 120 * j)
        pos_H[v] = (RING_R[r] * math.cos(ang), RING_R[r] * math.sin(ang))

# partial H_5 vertices u0..u4 on a regular pentagon
BOUNDARY = [f'u{j}' for j in range(5)]
R_BDY = 3.6
for j, u in enumerate(BOUNDARY):
    ang = math.radians(90 + 72 * j)
    pos_H[u] = (R_BDY * math.cos(ang), R_BDY * math.sin(ang))

H = nx.Graph()
H.add_nodes_from(pos_H)
# inherit G_1 \ {S_1} edges, but recompute embedding rotations are positional
for u, v in G1.edges():
    if 0 in (u, v):
        continue
    H.add_edge(u, v)
# partial H_5 cycle (the new outer boundary)
boundary_edges = [(BOUNDARY[j], BOUNDARY[(j + 1) % 5]) for j in range(5)]
H.add_edges_from(boundary_edges)
# annular edges A_5: a, b, c (= ring_G[1]) match to consecutive u_i ranges
a, b, c = ring_G[1]
annular_pairs = [
    (a, 'u0'), (a, 'u1'), (a, 'u2'),
    (b, 'u2'), (b, 'u3'), (b, 'u4'),
    (c, 'u4'), (c, 'u0'),
]
H.add_edges_from(annular_pairs)

# H is a triangulated planar disk: all bounded faces triangles, outer face
# the 5-cycle partial H_5. Verify edge count by Euler with f outer = 5-gon:
# t bounded triangles, F = t + 1, V - E + F = 2 => E = V + t - 1;
# 3t + 5 = 2E => t = 2V - 7. Here V = 14, so t = 21, E = 34.
assert H.number_of_edges() == 34, f"unexpected edge count {H.number_of_edges()}"

level_H = nx.multi_source_dijkstra_path_length(H, set(BOUNDARY))

# Verify the seam claim: BFS levels on V(G_1) \ {S_1} match between G_1 and H.
for v in G1.nodes():
    if v == 0:
        continue
    assert level_H[v] == level_G[v], (
        f"level mismatch at v={v}: G_1 level {level_G[v]}, H level {level_H[v]}"
    )

# ---------------------------------------------------------------------------
# Draw.
# ---------------------------------------------------------------------------
LEVEL_COLOR = {0: '#1e293b', 1: '#475569', 2: '#94a3b8', 3: '#cbd5e1'}

fig, axes = plt.subplots(1, 2, figsize=(16, 8.5))


def draw_panel(ax, graph, pos, levels, title, *, boundary=None, annular=None):
    nx.draw_networkx_edges(graph, pos, ax=ax, edge_color='#d1d5db', width=1.2)
    if annular:
        nx.draw_networkx_edges(graph, pos, edgelist=annular, ax=ax,
                                edge_color='#f59e0b', width=1.8)
    if boundary:
        nx.draw_networkx_edges(graph, pos, edgelist=boundary, ax=ax,
                                edge_color='#dc2626', width=2.4)
    for v, (x, y) in pos.items():
        lev = levels[v]
        ax.scatter([x], [y], s=560, color=LEVEL_COLOR[lev],
                   edgecolors='black', linewidths=1.0, zorder=3)
        ax.text(x, y, f'{v}\n$\\ell{{=}}{lev}$', ha='center', va='center',
                color='white', fontsize=8.5, fontweight='bold', zorder=4)
    ax.set_aspect('equal')
    ax.axis('off')
    ax.set_title(title, fontsize=11)


draw_panel(
    axes[0], G1, pos_G, level_G,
    r"$(a)$ $G_1$ with single-vertex source $S_1 = \{0\}$." "\n"
    r"BFS from $S_1$ gives levels $\ell = 0, 1, 2, 3$ "
    r"(rings $\{0\}, L_1, L_2, L_3$).",
)

draw_panel(
    axes[1], H, pos_H, level_H,
    r"$(b)$ $H_5$: apex-removal seam." "\n"
    r"Outer 5-cycle $\partial H_5$ (red) replaces $S_1$; "
    r"annulus $A_5$ (orange) glues $\partial H_5$ to $L_1$.",
    boundary=boundary_edges, annular=annular_pairs,
)

legend = [
    Line2D([0], [0], marker='o', color='w', label=r'level $\ell = 0$',
           markerfacecolor=LEVEL_COLOR[0], markeredgecolor='black', markersize=12),
    Line2D([0], [0], marker='o', color='w', label=r'level $\ell = 1$ ($L_1$)',
           markerfacecolor=LEVEL_COLOR[1], markeredgecolor='black', markersize=12),
    Line2D([0], [0], marker='o', color='w', label=r'level $\ell = 2$ ($L_2$)',
           markerfacecolor=LEVEL_COLOR[2], markeredgecolor='black', markersize=12),
    Line2D([0], [0], marker='o', color='w', label=r'level $\ell = 3$ ($L_3$)',
           markerfacecolor=LEVEL_COLOR[3], markeredgecolor='black', markersize=12),
    Line2D([0], [0], color='#dc2626', lw=2.4, label=r'$\partial H_5$ (boundary $k$-cycle, $k=5$)'),
    Line2D([0], [0], color='#f59e0b', lw=1.8, label=r'annulus $A_5$ edges'),
]
fig.legend(handles=legend, loc='lower center', ncol=3, fontsize=10, framealpha=0.95)

fig.suptitle(
    r"Seam realizability (Conjecture 1.22): "
    r"$\mathcal{T}(H_5, \partial H_5) \cong \mathcal{T}(G_1, S_1)$ "
    r"as rooted trees of tire treads (combinatorial, $O$-preserved). "
    r"BFS distances from $\partial H_5$ in $H_5$ reproduce $\ell_{G_1}$ "
    r"on $V(G_1) \setminus \{S_1\}$.", fontsize=12,
)
fig.tight_layout(rect=[0, 0.08, 1, 0.95])

out = os.path.join(OUT_DIR, 'fig_seam_construction.png')
fig.savefig(out, dpi=180, bbox_inches='tight')
plt.close(fig)

print(f'G_1: |V|={G1.number_of_nodes()}, |E|={G1.number_of_edges()}, '
      f'levels: {sorted(set(level_G.values()))}')
print(f'H_5: |V|={H.number_of_nodes()}, |E|={H.number_of_edges()}, '
      f'levels: {sorted(set(level_H.values()))}')
print(f'wrote {out}')
