"""Utilities for working with planar embeddings of graphs."""
import math
from typing import Any

from sage.all import Graph  # type: ignore


def outer_face(g: Graph) -> list[Any]:
    """Return the vertices of the outer (unbounded) face of g using its planar embedding and positions."""
    pos = g.get_pos()
    embedding = g.get_embedding()
    if not pos or not embedding:
        raise Exception("Position and embedding required to find outer face")

    visited: set[tuple[Any, Any]] = set()
    faces: list[list[Any]] = []

    for u in g.vertices():
        for v in embedding[u]:
            if (u, v) not in visited:
                face: list[Any] = []
                cu, cv = u, v
                while (cu, cv) not in visited:
                    visited.add((cu, cv))
                    face.append(cu)
                    neighbors = embedding[cv]
                    cw = neighbors[(neighbors.index(cu) + 1) % len(neighbors)]
                    cu, cv = cv, cw
                faces.append(face)

    def signed_area(face: list[Any]) -> float:
        coords = [pos[v] for v in face]
        n = len(coords)
        return sum(
            coords[i][0] * coords[(i + 1) % n][1] - coords[(i + 1) % n][0] * coords[i][1]
            for i in range(n)
        ) / 2

    return min(faces, key=signed_area)


def get_embedding_from_pos(g: Graph) -> dict[Any, list[Any]]:
    """Compute a combinatorial planar embedding by sorting each vertex's neighbors by angle."""
    pos_or_none = g.get_pos()
    if pos_or_none is None:
        raise Exception("Cannot get embedding without position")
    pos: dict[Any, Any] = pos_or_none
    return {
        v: sorted(g.neighbors(v), key=lambda w: math.atan2(pos[w][1] - pos[v][1], pos[w][0] - pos[v][0]))
        for v in g.vertices()
    }
