"""GraphQL client with streaming pagination for memory-efficient data fetching."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/08_utils_graphql.ipynb.

# %% ../nbs/08_utils_graphql.ipynb 2
from __future__ import annotations
from .utils_api import AsyncAPIClient
from typing import AsyncGenerator, Dict, Any, List, Optional
import logging
from nbdev.showdoc import show_doc

logger = logging.getLogger(__name__)

# %% auto 0
__all__ = ['logger', 'GraphQLClient']

# %% ../nbs/08_utils_graphql.ipynb 5
class GraphQLClient:
    """GraphQL client with streaming pagination for memory-efficient data fetching."""
    
    def __init__(
        self,
        api_client: AsyncAPIClient, # Initialized AsyncAPIClient instance
        endpoint: str = '' # GraphQL endpoint path (default: '' for base URL)
    ):
        self.api_client = api_client
        self.endpoint = endpoint
    
    async def execute_query(
        self,
        query: str, # GraphQL query string
        variables: dict = None # Query variables
    ) -> Dict[str, Any]:
        """Execute a single GraphQL query and return the JSON response."""
        payload = {'query': query}
        if variables:
            payload['variables'] = variables
        
        response = await self.api_client.request(
            method='POST',
            endpoint=self.endpoint,
            json=payload
        )
        
        data = response.json()
        
        # Check for GraphQL errors
        if 'errors' in data:
            error_msg = data['errors'][0].get('message', 'Unknown GraphQL error')
            raise ValueError(f"GraphQL error: {error_msg}")
        
        return data
    
    async def execute_mutation(
        self,
        mutation: str, # GraphQL mutation string
        variables: dict = None # Mutation variables
    ) -> Dict[str, Any]:
        """Execute a GraphQL mutation (alias for execute_query)."""
        return await self.execute_query(mutation, variables)
    
    async def fetch_pages_generator(
        self,
        query_template: str, # GraphQL query with $variables placeholders
        variables: dict, # Initial variables (must include cursor key)
        items_path: list[str], # JSONPath to list of items (e.g., ['data', 'users', 'nodes'])
        cursor_path: list[str], # JSONPath to next cursor (e.g., ['data', 'users', 'pageInfo', 'endCursor'])
        has_next_path: list[str] = None, # Optional path to hasNextPage boolean (if None, checks cursor != None)
        cursor_var: str = 'cursor' # Variable name for cursor in query (default: 'cursor')
    ) -> AsyncGenerator[List[Dict], None]:
        """Stream paginated GraphQL data page-by-page using async generator."""
        has_next = True
        page_count = 0
        
        while has_next:
            # Execute query with current cursor
            response = await self.execute_query(query_template, variables)
            
            # Extract items using path
            items = self._get_nested_value(response, items_path)
            if not items:
                logger.warning(f"No items found at path {items_path}")
                break
            
            page_count += 1
            logger.info(f"Fetched page {page_count} with {len(items)} items")
            
            # Yield batch (CRITICAL: do not accumulate)
            yield items
            
            # Check if there's a next page
            if has_next_path:
                has_next = self._get_nested_value(response, has_next_path)
            else:
                # Fallback: check if cursor exists
                next_cursor = self._get_nested_value(response, cursor_path)
                has_next = next_cursor is not None
            
            # Update cursor for next iteration
            if has_next:
                next_cursor = self._get_nested_value(response, cursor_path)
                variables[cursor_var] = next_cursor
    
    def _get_nested_value(
        self, 
        data: dict, 
        path: list[str]
    ) -> Any:
        """Extract nested value from dict using path list."""
        result = data
        for key in path:
            if isinstance(result, dict) and key in result:
                result = result[key]
            else:
                return None
        return result
