"""
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


class FramesHandler:
    """Frame handling utilities for Playwright pages."""

    @staticmethod
    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

    @staticmethod
    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))

    @staticmethod
    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 = FramesHandler.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 {FramesHandler.compute_frame_path(frame)} search failed: {e}")
                continue
        
        return None, None


__all__ = ["FramesHandler"]
