"""Ariadne / graphql-core integration helpers.

Provides ``extract_requested_fields`` which inspects a ``GraphQLResolveInfo``
object and returns a ``RequestedFields`` instance that tells ``paginate``
exactly which parts of the connection the GraphQL client is asking for.
This lets the paginator skip the page query entirely when only
``totalCount`` is requested, or omit the extra-row fetch when neither
``hasNextPage`` nor ``hasPreviousPage`` is in the selection.

Supported selection patterns:

* ``edges { … }`` or ``nodes { … }`` → ``RequestedFields.edges = True``
* ``pageInfo { totalCount }`` → ``RequestedFields.total_count = True``
* ``pageInfo { hasNextPage }`` → ``RequestedFields.has_next_page = True``
* ``pageInfo { hasPreviousPage }`` → ``RequestedFields.has_previous_page = True``
* ``pageInfo { startCursor }`` → ``RequestedFields.start_cursor = True``
* ``pageInfo { endCursor }`` → ``RequestedFields.end_cursor = True``

Fragment spreads and inline fragments are resolved recursively.
"""

from typing import Any

from graphql import (
    FieldNode,
    FragmentSpreadNode,
    GraphQLResolveInfo,
    InlineFragmentNode,
    SelectionSetNode,
)

from sqlalchemy_relay_pagination.types import RequestedFields


def _collect_field_names(
    selection_set: SelectionSetNode | None,
    fragments: dict[str, Any],
) -> set[str]:
    """Recursively collect all field names from a selection set."""
    if selection_set is None:
        return set()
    names: set[str] = set()
    for selection in selection_set.selections:
        if isinstance(selection, FieldNode):
            names.add(selection.name.value)
        elif isinstance(selection, InlineFragmentNode):
            names |= _collect_field_names(selection.selection_set, fragments)
        elif isinstance(selection, FragmentSpreadNode):
            fragment = fragments.get(selection.name.value)
            if fragment is not None:
                names |= _collect_field_names(fragment.selection_set, fragments)
    return names


def _find_field_node(
    selection_set: SelectionSetNode | None,
    field_name: str,
    fragments: dict[str, Any],
) -> FieldNode | None:
    """Return the first ``FieldNode`` with the given name, or ``None``."""
    if selection_set is None:
        return None
    for selection in selection_set.selections:
        if isinstance(selection, FieldNode) and selection.name.value == field_name:
            return selection
        if isinstance(selection, InlineFragmentNode):
            found = _find_field_node(selection.selection_set, field_name, fragments)
            if found:
                return found
        elif isinstance(selection, FragmentSpreadNode):
            fragment = fragments.get(selection.name.value)
            if fragment:
                found = _find_field_node(fragment.selection_set, field_name, fragments)
                if found:
                    return found
    return None


def extract_requested_fields(info: GraphQLResolveInfo) -> RequestedFields:
    """Derive a ``RequestedFields`` from an Ariadne ``GraphQLResolveInfo``.

    Inspects the resolver's selection set (including fragments) to determine
    which connection fields the client is actually requesting, enabling
    ``paginate`` to skip unnecessary database queries.

    Args:
        info: The ``GraphQLResolveInfo`` passed to the field resolver.

    Returns:
        A ``RequestedFields`` instance with only the requested flags set.
    """
    fragments = info.fragments
    field_node = info.field_nodes[0]
    top_level = _collect_field_names(field_node.selection_set, fragments)

    has_edges = "edges" in top_level or "nodes" in top_level

    has_next_page = False
    has_previous_page = False
    has_start_cursor = False
    has_end_cursor = False
    has_total_count = False

    page_info_node = _find_field_node(field_node.selection_set, "pageInfo", fragments)
    if page_info_node is not None:
        page_info_fields = _collect_field_names(page_info_node.selection_set, fragments)
        has_next_page = "hasNextPage" in page_info_fields
        has_previous_page = "hasPreviousPage" in page_info_fields
        has_start_cursor = "startCursor" in page_info_fields
        has_end_cursor = "endCursor" in page_info_fields
        has_total_count = "totalCount" in page_info_fields

    return RequestedFields(
        edges=has_edges,
        has_next_page=has_next_page,
        has_previous_page=has_previous_page,
        start_cursor=has_start_cursor,
        end_cursor=has_end_cursor,
        total_count=has_total_count,
    )
