"""
Element extraction utilities for Playwright browser automation.

This module provides utilities for extracting comprehensive element information
from Playwright element handles, including attributes, styles, bounding boxes, and visibility checks.
"""

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

from .frames_handler import FramesHandler


class ElementExtractor:
    """
    Utilities for extracting comprehensive element information from Playwright element handles.
    
    This class provides methods for element information extraction, visibility checks,
    and collecting actionable elements across frames.
    """
    
    # Default selectors for actionable elements
    DEFAULT_ACTIONABLE_SELECTORS = [
        ("button", "button"),
        ("input", "input"),
        ("a", "link"),
        ("div[role='listitem']", "chat_item"),
        ("div[role='textbox']", "chat_textbox"),
        ("[role='button']", "role_button"),
        ("[role='tab']", "tab"),
        ("[role='menuitem']", "menu_item"),
        ("[role='option']", "option"),
        ("[role='checkbox']", "checkbox"),
        ("[role='radio']", "radio"),
        ("[role='textbox']", "textbox"),
        ("[role='combobox']", "combobox"),
        ("[role='listbox']", "listbox"),
        ("[role='grid']", "grid"),
        ("[role='row']", "row"),
        ("[role='cell']", "cell"),
        ("[tabindex='0']", "focusable"),
        ("[onclick]", "clickable"),
        ("[data-testid]", "test_element"),
        ("[data-automation-id]", "automation_element"),
        ("[title*='Send (Ctrl+Enter)']", "send_button"),
        ("[aria-label*='Send (Ctrl+Enter)']", "send_button"),
        ("[name*='send']", "send_button"),
        ("[data-testid*='schedule-message-wrapper']", "send_button"),
        ("[data-automationid='splitbuttonprimary']", "button")
    ]
    
    @staticmethod
    async def extract_element_info(
        element_handle: ElementHandle,
        element_kind: str,
        frame_context: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """
        Extract comprehensive information from a Playwright element handle.
        
        Args:
            element_handle: Playwright ElementHandle object
            element_kind: Type/kind of element (e.g., "button", "input", "link")
            frame_context: Optional frame context dictionary to include in element info
            
        Returns:
            Dictionary containing comprehensive element information including:
            - Basic attributes (tag, text, id, name, etc.)
            - ARIA attributes (aria-label, aria-labelledby, etc.)
            - Computed styles (display, visibility, opacity, etc.)
            - Bounding box information (x, y, width, height, visible)
            - Frame context (if provided)
        """
        element_info = {
            "type": element_kind,
            "tag": await element_handle.evaluate("el => el.tagName.toLowerCase()"),
            "text": await element_handle.inner_text(),
            "aria-label": await element_handle.get_attribute("aria-label"),
            "aria-labelledby": await element_handle.get_attribute("aria-labelledby"),
            "aria-describedby": await element_handle.get_attribute("aria-describedby"),
            "id": await element_handle.get_attribute("id"),
            "name": await element_handle.get_attribute("name"),
            "placeholder": await element_handle.get_attribute("placeholder"),
            "role": await element_handle.get_attribute("role"),
            "title": await element_handle.get_attribute("title"),
            "class": await element_handle.get_attribute("class"),
            "href": await element_handle.get_attribute("href"),
            "value": await element_handle.get_attribute("value"),
            "type": await element_handle.get_attribute("type"),
            "disabled": await element_handle.get_attribute("disabled"),
            "hidden": await element_handle.get_attribute("hidden"),
            "tabindex": await element_handle.get_attribute("tabindex"),
            "data-testid": await element_handle.get_attribute("data-testid"),
            "data-automation-id": await element_handle.get_attribute("data-automation-id"),
        }
        
        # Add frame context if provided
        if frame_context:
            element_info.update(frame_context)
        
        # Get computed styles for visibility
        try:
            styles = await element_handle.evaluate("""
                el => {
                    const style = window.getComputedStyle(el);
                    return {
                        display: style.display,
                        visibility: style.visibility,
                        opacity: style.opacity,
                        position: style.position,
                        zIndex: style.zIndex,
                        width: style.width,
                        height: style.height
                    };
                }
            """)
            # Filter out non-numeric CSS values before updating
            filtered_styles = {}
            for key, value in styles.items():
                if key in ['display', 'visibility', 'position']:
                    filtered_styles[key] = value
                elif key in ['opacity', 'zIndex']:
                    try:
                        filtered_styles[key] = float(value) if value != 'auto' else 0
                    except (ValueError, TypeError):
                        filtered_styles[key] = 0
                elif key in ['width', 'height']:
                    try:
                        filtered_styles[key] = float(value) if value != 'auto' else 0
                    except (ValueError, TypeError):
                        filtered_styles[key] = 0
            element_info.update(filtered_styles)
        except Exception:
            pass
        
        # Get bounding box for position context
        try:
            bbox = await element_handle.bounding_box()
            if bbox:
                element_info.update({
                    "x": bbox["x"],
                    "y": bbox["y"],
                    "width": bbox["width"],
                    "height": bbox["height"],
                    "visible": bbox["width"] > 0 and bbox["height"] > 0
                })
        except Exception:
            element_info["visible"] = False
        
        return element_info
    
    @staticmethod
    async def check_element_visibility(element_handle: ElementHandle) -> bool:
        """
        Check if an element is visible by examining bounding box and computed styles.
        
        Args:
            element_handle: Playwright ElementHandle object
            
        Returns:
            True if element is visible, False otherwise
        """
        try:
            # Check bounding box
            bbox = await element_handle.bounding_box()
            if not bbox or bbox.get("width", 0) <= 0 or bbox.get("height", 0) <= 0:
                return False
            
            # Check visibility styles
            is_visible = await element_handle.evaluate("""
                el => {
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none' && 
                           style.visibility !== 'hidden' && 
                           style.opacity !== '0';
                }
            """)
            return is_visible
        except Exception:
            return False
    
    @classmethod
    async def get_actionable_elements(
        cls,
        page: Page,
        selectors: Optional[List[Tuple[str, str]]] = None
    ) -> List[Dict[str, Any]]:
        """
        Collect all actionable elements from a page across all frames.
        
        Enhanced element collection with better context and accessibility information.
        
        Args:
            page: Playwright Page object
            selectors: Optional list of (selector, kind) tuples.
                       If None, uses DEFAULT_ACTIONABLE_SELECTORS
            
        Returns:
            List of element information dictionaries from all frames
        """
        elements = []
        frames = FramesHandler.collect_all_frames(page)
        
        if selectors is None:
            selectors = cls.DEFAULT_ACTIONABLE_SELECTORS
        
        for frame in frames:
            frame_ctx = {
                "frame_url": frame.url,
                "frame_name": frame.name,
                "frame_path": FramesHandler.compute_frame_path(frame),
            }
            
            for selector, kind in selectors:
                try:
                    handles = await frame.query_selector_all(selector)
                except Exception:
                    handles = []
                
                for h in handles:
                    try:
                        element_info = await cls.extract_element_info(h, kind, frame_ctx)
                        elements.append(element_info)
                    except Exception:
                        # Skip problematic elements
                        continue
                        
        return elements


# Backward compatibility: keep module-level functions as wrappers
async def extract_element_info(
    element_handle: ElementHandle,
    element_kind: str,
    frame_context: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
    """Module-level function wrapper for backward compatibility."""
    return await ElementExtractor.extract_element_info(element_handle, element_kind, frame_context)


async def check_element_visibility(element_handle: ElementHandle) -> bool:
    """Module-level function wrapper for backward compatibility."""
    return await ElementExtractor.check_element_visibility(element_handle)


async def get_actionable_elements(
    page: Page,
    selectors: Optional[List[Tuple[str, str]]] = None
) -> List[Dict[str, Any]]:
    """Module-level function wrapper for backward compatibility."""
    return await ElementExtractor.get_actionable_elements(page, selectors)
