"""Empirical check: does pure greedy online 4-coloring succeed under the canonical quadrilateral sequencing?

The user's conjecture-of-the-day: for the canonical sequence, every non-ring-completion
move (anchor drop, level add, join) admits a local 4-coloring choice, and ring completions
introduce no new vertices, so the only question is whether the existing coloring agrees
on the ring-completion quad's edges.

Under proper greedy online coloring this latter check is automatic: every G'-edge
constraint is enforced as the second endpoint is colored. So the empirical question
becomes: does pure greedy (smallest color avoiding already-colored G'-neighbors) ever
get stuck at a non-ring-completion move with all four colors forbidden?

We run greedy in a canonical vertex order (depth, then vertex id) and classify every
failure as one of:
  - 'no_color_available': a new vertex saw all 4 colors among colored G'-neighbors
  - 'ring_completion_monochromatic': a ring completion's quad has a monochromatic edge
  - 'success': global proper 4-coloring of G'.
"""
import os, sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))  # repo root for `lib`
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))  # sibling experiment modules
from typing import Any
from sage.all import Graph, graphs  # type: ignore[attr-defined]  # pylint: disable=no-name-in-module
from lib.colored_graphs import canonize_and_save_graph
from plane_depth_sequencing import (
    quadrilateral_sequencing,
    _quad_vertices,
)


def greedy_4_color_under_sequence(
    g_prime: Graph,
    sequence: list[frozenset[frozenset[Any]]],
    depth_labelling: dict[Any, int],
) -> tuple[dict[Any, int], str, int | None]:
    """Run greedy online 4-coloring under the canonical sequence.

    Returns (partial_coloring, status, failing_step).
    status is one of:
      - 'success'
      - 'no_color_available' (failing_step is the sequence index)
      - 'ring_completion_monochromatic' (failing_step is the sequence index)
    """
    coloring: dict[Any, int] = {}
    slice_vertices: set[Any] = set()
    adj: dict[Any, set[Any]] = {v: set(g_prime.neighbors(v)) for v in g_prime.vertices()}

    for step, quad in enumerate(sequence):
        quad_v = _quad_vertices(quad)
        new_vertices = quad_v - slice_vertices

        if not new_vertices:
            # Ring completion: verify the existing coloring is proper on the quad's edges.
            quad_v_list = list(quad_v)
            for i in range(len(quad_v_list)):
                for j in range(i + 1, len(quad_v_list)):
                    u, v = quad_v_list[i], quad_v_list[j]
                    if v in adj[u] and coloring[u] == coloring[v]:
                        return coloring, 'ring_completion_monochromatic', step
            continue

        # Color new vertices in a canonical order (depth, then vertex id).
        for v in sorted(new_vertices, key=lambda x: (depth_labelling[x], x)):
            used = {coloring[u] for u in adj[v] if u in coloring}
            avail = [c for c in (0, 1, 2, 3) if c not in used]
            if not avail:
                return coloring, 'no_color_available', step
            coloring[v] = min(avail)
            slice_vertices.add(v)

    return coloring, 'success', None


def _outer_cycle_from_embedding(g: Graph) -> list[Any]:
    """Pick a deterministic outer cycle: the first face Sage returns from the embedding."""
    g.is_planar(set_embedding=True)
    embedding = g.get_embedding()
    faces = g.faces(embedding)
    return [u for u, v in faces[0]]


def check_graph(g: Graph) -> dict[str, Any]:
    """Run sequencing + greedy coloring on a single graph. Returns a result dict."""
    outer_cycle = _outer_cycle_from_embedding(g)
    try:
        seq_result = quadrilateral_sequencing(g, outer_cycle)
    except Exception as exc:  # pylint: disable=broad-except
        return {
            'graph6': g.graph6_string(),
            'status': 'sequencing_error',
            'error': str(exc),
        }
    coloring, status, failing_step = greedy_4_color_under_sequence(
        seq_result['deep_embedding'],
        seq_result['sequence'],
        seq_result['depth_labelling'],
    )
    return {
        'graph6': g.graph6_string(),
        'status': status,
        'failing_step': failing_step,
        'coloring_size': len(coloring),
        'num_quads': len(seq_result['quadrilaterals']),
        'move_codes': seq_result['move_codes'],
    }


def run_enumeration(min_order: int, max_order: int) -> dict[int, dict[str, Any]]:
    """Iterate over all 3-connected triangulations of order in [min_order, max_order]."""
    summary: dict[int, dict[str, Any]] = {}
    for n in range(min_order, max_order + 1):
        total = 0
        by_status: dict[str, int] = {}
        failures: list[dict[str, Any]] = []
        for g in graphs.planar_graphs(n, minimum_connectivity=3, maximum_face_size=3):
            total += 1
            result = check_graph(g)
            status = result['status']
            by_status[status] = by_status.get(status, 0) + 1
            if status != 'success':
                failures.append(result)
            if total % 200 == 0:
                print(f"  n={n}: checked {total}, statuses so far: {by_status}")
        summary[n] = {'total': total, 'by_status': by_status, 'failures': failures}
        print(f"n={n}: total={total}, by_status={by_status}")
        for f in failures[:5]:
            print(f"  failure {f['graph6']}: {f['status']} at step {f['failing_step']}")
        if len(failures) > 5:
            print(f"  ... ({len(failures) - 5} more failures)")
    return summary


if __name__ == "__main__":
    import sys

    min_order = int(sys.argv[1]) if len(sys.argv) > 1 else 4
    max_order = int(sys.argv[2]) if len(sys.argv) > 2 else 9

    summary = run_enumeration(min_order, max_order)

    print()
    print("=" * 60)
    print("Final summary:")
    for n, s in summary.items():
        print(f"  n={n}: total={s['total']}, by_status={s['by_status']}")

    # Save up to first 10 failure graphs per order for inspection.
    saved = 0
    for n, s in summary.items():
        for f in s['failures'][:10]:
            try:
                g = Graph(f['graph6'])
                canonical, graph_dir = canonize_and_save_graph(g)
                print(f"  saved n={n} {f['graph6']} ({f['status']}) -> {graph_dir}")
                saved += 1
            except Exception as exc:  # pylint: disable=broad-except
                print(f"  failed to save {f['graph6']}: {exc}")
    print(f"Saved {saved} failure graphs total.")
