"""
Frame handling utilities for Playwright browser automation.

This module provides utilities for working with frames across a Playwright page,
including collecting all frames in a tree structure, computing frame paths, and
finding elements across frames.
"""

from typing import Any, List, Optional, Tuple
from playwright.async_api import Frame, Page


def collect_all_frames(page: Page) -> List[Frame]:
    """
    Return a flat list of the main frame and all descendant frames (depth-first order).
    
    Args:
        page: Playwright Page object
        
    Returns:
        List of Frame objects in depth-first order
    """
    frames = []

    def visit(frame: Frame):
        frames.append(frame)
        for child in frame.child_frames:
            visit(child)

    visit(page.main_frame)
    return frames


def compute_frame_path(frame: Frame) -> str:
    """
    Compute a stable path-like identifier for a frame using child index hops from main.
    
    Example: "main/0/2" meaning main -> first child -> third child.
    
    Args:
        frame: Playwright Frame object
        
    Returns:
        String path identifier for the frame
    """
    path_parts = []
    current = frame
    while current is not None:
        parent = current.parent_frame
        if parent is None:
            path_parts.append("main")
            break
        try:
            index_in_parent = parent.child_frames.index(current)
        except ValueError:
            index_in_parent = -1
        path_parts.append(str(index_in_parent))
        current = parent
    return "/".join(reversed(path_parts))


async def find_element_in_frames(
    page: Page,
    locator: str,
    value: str,
    timeout_ms: int = 5000,
    logger=None
) -> Tuple[Optional[Any], Optional[Frame]]:
    """
    Find element across all frames using specified locator strategy.
    
    Args:
        page: Playwright page object
        locator: Locator type ('aria-label', 'id', 'xpath', 'text', etc.)
        value: Value to search for
        timeout_ms: Timeout in milliseconds
        logger: Optional logger for debug messages
        
    Returns:
        Tuple of (element_handle, frame) or (None, None) if not found
    """
    frames = collect_all_frames(page)
    
    for frame in frames:
        try:
            el = None
            if locator == "xpath":
                el = await frame.query_selector(f"xpath={value}")
            elif locator == "aria-label":
                el = await frame.query_selector(f"[aria-label='{value}']")
            elif locator == "id":
                el = await frame.query_selector(f"#{value}")
            elif locator == "text":
                el = await frame.query_selector(f"text={value}")
            elif locator == "placeholder":
                el = await frame.query_selector(f"[placeholder='{value}']")
            elif locator == "role":
                el = await frame.query_selector(f"[role='{value}']")
            else:
                el = await frame.query_selector(value)
            
            if el:
                return el, frame
        except Exception as e:
            if logger:
                logger.debug(f"Frame {compute_frame_path(frame)} search failed: {e}")
            continue
    
    return None, None
