"""Broader search for an icosahedron preimage at n=12.

Key changes from find_icosa_preimage_fast:
  - Increase branching limits (p1_branch=8, p2_branch=8, cap=65536)
  - Filter outputs by 5-regularity (faster than full iso check)
    Note: any 5-regular planar triangulation on 12 vertices IS iso to the
    icosahedron (unique 5-regular triangulation at this size)
  - Report progress every 100 iso-classes
"""
import sys, os, time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import networkx as nx
from collections import defaultdict
from itertools import product as iproduct
from triangulation_gen_fast import enumerate_all_triangulations_fast
from level_cycles import compute_levels, level_sources
from depth_monovariant_check import (
    faces_of_subgraph, canonical_face, edges_of_face,
    apex_levels_for_edge, identify_outer_face,
)
from full_resolution_check import phase1_resolve_isolated
from simple_level_resolution_coverage import (
    break_source_triangle, all_source_triangle_breaks,
    phase2_outer_incident_choices, apply_phase2_choices,
    phase1_branch, is_triangulation,
)


def is_5_regular(G):
    return all(d == 5 for v, d in G.degree())


def simple_level_resolutions_broad(G, source_set, p1_branch=8, p2_branch=8,
                                     cap=2048):
    """Same as simple_level_resolutions but with wider branching."""
    levels = compute_levels(G, source_set)
    by_level = defaultdict(list)
    for v, lv in levels.items():
        by_level[lv].append(v)
    seen_outputs = set()
    for Gc in all_source_triangle_breaks(G, source_set):
        for G_p1 in phase1_branch(Gc, levels, by_level,
                                    branch_limit=p1_branch):
            per_level_choices = []
            for k in sorted(by_level):
                if k == 0:
                    continue
                if len(by_level[k]) < 3:
                    continue
                nodes_k = by_level[k]
                ip, emb = nx.check_planarity(G_p1)
                if not ip:
                    continue
                pcs = phase2_outer_incident_choices(G_p1, emb, levels,
                                                     nodes_k, k,
                                                     include_even=True)
                pcs = [c[:p2_branch] for c in pcs]
                per_level_choices.extend(pcs)
            if not per_level_choices:
                sig = tuple(sorted(G_p1.edges()))
                if sig not in seen_outputs:
                    seen_outputs.add(sig)
                    yield G_p1
                continue
            total = 1
            for c in per_level_choices:
                total *= len(c)
            if total > cap:
                choices = [c[0] for c in per_level_choices]
                Gp = apply_phase2_choices(G_p1, choices)
                sig = tuple(sorted(Gp.edges()))
                if sig not in seen_outputs:
                    seen_outputs.add(sig)
                    yield Gp
                continue
            for combo in iproduct(*per_level_choices):
                Gp = apply_phase2_choices(G_p1, combo)
                sig = tuple(sorted(Gp.edges()))
                if sig not in seen_outputs:
                    seen_outputs.add(sig)
                    yield Gp


def main():
    print("Loading n=12 iso-classes...")
    t0 = time.time()
    isos = enumerate_all_triangulations_fast(12)
    print(f"  {len(isos)} iso-classes loaded ({time.time()-t0:.1f}s)")
    md5_idx = [i for i, T in enumerate(isos)
                if all(d == 5 for _, d in T.degree())]
    print(f"  md5 iso-classes: {md5_idx}")
    icosa = isos[md5_idx[0]]

    found = []
    start = time.time()
    for src_idx, G in enumerate(isos):
        if src_idx % 50 == 0:
            elapsed = time.time() - start
            print(f"  [{src_idx}/{len(isos)}] ({elapsed:.1f}s) "
                  f"found={len(found)}", flush=True)
        ip, emb = nx.check_planarity(G)
        if not ip:
            continue
        for kind, lab, source_set in level_sources(G, emb):
            count = 0
            for Gp in simple_level_resolutions_broad(G, source_set):
                count += 1
                if count > 100:
                    break
                if not is_triangulation(Gp, 12):
                    continue
                if not is_5_regular(Gp):
                    continue
                # Now Gp is iso to icosa (uniqueness of 5-regular at n=12)
                levels = compute_levels(G, source_set)
                even = [v for v in Gp.nodes()
                         if levels.get(v, 0) % 2 == 0]
                odd = [v for v in Gp.nodes()
                        if levels.get(v, 0) % 2 == 1]
                if not (nx.is_bipartite(Gp.subgraph(even)) and
                        nx.is_bipartite(Gp.subgraph(odd))):
                    continue
                G_edges = set(frozenset(e) for e in G.edges())
                Gp_edges = set(frozenset(e) for e in Gp.edges())
                if G_edges == Gp_edges:
                    continue
                found.append({
                    'src_idx': src_idx,
                    'source': (kind, lab),
                    'source_set': sorted(source_set),
                })
                print(f"  FOUND PREIMAGE: src_iso={src_idx}, "
                      f"source=({kind}, {lab})")
                if len(found) >= 3:
                    break
            if len(found) >= 3:
                break
        if len(found) >= 3:
            break

    print(f"\nTotal preimages found: {len(found)} (elapsed {time.time()-start:.1f}s)")
    for ex in found:
        print(f"  {ex}")


if __name__ == "__main__":
    main()
