"""
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 backend.utils.frames_handler import collect_all_frames, compute_frame_path


# 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")
]


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


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


async def get_actionable_elements(
    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 = collect_all_frames(page)
    
    if selectors is None:
        selectors = DEFAULT_ACTIONABLE_SELECTORS
    
    for frame in frames:
        frame_ctx = {
            "frame_url": frame.url,
            "frame_name": frame.name,
            "frame_path": 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 extract_element_info(h, kind, frame_ctx)
                    elements.append(element_info)
                except Exception:
                    # Skip problematic elements
                    continue
                    
    return elements
