"""Helpers for cursor-based pagination over gaiia Connection types."""

from __future__ import annotations

from typing import Any, AsyncIterator, Iterator


def extract_connection(data: dict[str, Any], path: str) -> dict[str, Any]:
    """Walk a dotted path into a GraphQL ``data`` payload and return the Connection node."""
    node: Any = data
    for part in path.split("."):
        if not isinstance(node, dict):
            raise KeyError(f"path {path!r} not found in response")
        node = node.get(part)
        if node is None:
            raise KeyError(f"path {path!r} not found in response (broke at {part!r})")
    if not isinstance(node, dict) or "pageInfo" not in node:
        raise ValueError(f"path {path!r} did not point to a Connection (no pageInfo)")
    return node


def iter_nodes(
    fetch_page: "Callable[[str | None], dict[str, Any]]",  # noqa: F821
    connection_path: str,
) -> Iterator[Any]:
    """Yield nodes one by one across all pages of a Connection.

    ``fetch_page`` takes the current ``after`` cursor (``None`` on the first
    page) and returns the full GraphQL ``data`` payload for that page.
    """
    cursor: str | None = None
    while True:
        data = fetch_page(cursor)
        conn = extract_connection(data, connection_path)
        for node in conn.get("nodes") or []:
            yield node
        page_info = conn.get("pageInfo") or {}
        if not page_info.get("hasNextPage"):
            return
        cursor = page_info.get("endCursor")
        if not cursor:
            return


async def aiter_nodes(
    fetch_page: "Callable[[str | None], Awaitable[dict[str, Any]]]",  # noqa: F821
    connection_path: str,
) -> AsyncIterator[Any]:
    """Async version of :func:`iter_nodes`."""
    cursor: str | None = None
    while True:
        data = await fetch_page(cursor)
        conn = extract_connection(data, connection_path)
        for node in conn.get("nodes") or []:
            yield node
        page_info = conn.get("pageInfo") or {}
        if not page_info.get("hasNextPage"):
            return
        cursor = page_info.get("endCursor")
        if not cursor:
            return
