#!/usr/bin/env python3
"""Find UI elements by role or name via AT-SPI.

Usage:
    find-element.py --role button
    find-element.py --name "Save"
    find-element.py --role button --name "OK"
    find-element.py --window "Firefox" --role link

Options:
    --role ROLE        Filter by role (button, label, text, menu-item, etc.)
    --name PATTERN     Filter by name (substring, case-insensitive)
    --window PATTERN   Limit search to matching window
    --app APP          Limit search to application
    --json             Output as JSON
    --limit N          Max results (default: 20)
"""

import argparse
import json
import sys

import pyatspi

from common import find_windows, get_all_windows, find_elements, set_debug, log_debug


def get_element_info(element):
    """Extract useful info from an accessible element."""
    info = {
        "role": element.getRoleName(),
        "name": element.name or "",
    }

    # Get description if available
    desc = element.description
    if desc:
        info["description"] = desc

    # Get states
    state = element.getState()
    states = []
    for s in pyatspi.STATE_VALUE_TO_NAME:
        if state.contains(s):
            states.append(pyatspi.STATE_VALUE_TO_NAME[s])
    info["states"] = states

    # Get geometry
    try:
        component = element.queryComponent()
        if component:
            rect = component.getExtents(pyatspi.DESKTOP_COORDS)
            info["geometry"] = {
                "x": rect.x,
                "y": rect.y,
                "width": rect.width,
                "height": rect.height,
            }
    except Exception as e:
        log_debug(f"Failed to get geometry for {info['name']}: {e}")

    # Get text content if available
    try:
        text_iface = element.queryText()
        if text_iface and text_iface.characterCount > 0:
            text = text_iface.getText(0, min(100, text_iface.characterCount))
            if text:
                info["text"] = text
    except Exception as e:
        log_debug(f"Failed to get text for {info['name']}: {e}")

    # Build path for context
    path = []
    parent = element
    for _ in range(5):  # Max 5 levels up
        parent = parent.parent
        if not parent:
            break
        if parent.name:
            path.insert(0, parent.name)
        elif parent.getRoleName() not in ("application", "desktop frame"):
            path.insert(0, f"[{parent.getRoleName()}]")
    if path:
        info["path"] = " > ".join(path)

    return info


def print_results(results, as_json=False):
    """Print search results."""
    if as_json:
        print(json.dumps(results, indent=2))
        return

    if not results:
        print("No matching elements found.")
        return

    for i, elem in enumerate(results, 1):
        geo = elem.get("geometry", {})
        geo_str = f"({geo['x']},{geo['y']} {geo['width']}x{geo['height']})" if geo else ""

        print(f"\n{i}. [{elem['role']}] {elem['name'] or '(unnamed)'}")
        if elem.get("text"):
            print(f"   Text: {elem['text'][:60]}{'...' if len(elem.get('text', '')) > 60 else ''}")
        if geo_str:
            print(f"   Position: {geo_str}")
        if elem.get("path"):
            print(f"   Path: {elem['path']}")

        # Key states only
        key_states = [s for s in elem.get("states", [])
                      if s in ("focused", "selected", "checked", "expanded", "disabled")]
        if key_states:
            print(f"   States: {', '.join(key_states)}")


def main():
    parser = argparse.ArgumentParser(description="Find UI elements via AT-SPI")
    parser.add_argument("--role", "-r", help="Filter by role (button, label, text, etc.)")
    parser.add_argument("--name", "-n", help="Filter by name (substring match)")
    parser.add_argument("--window", "-w", help="Limit to matching window")
    parser.add_argument("--app", "-a", help="Limit to application")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    parser.add_argument("--limit", type=int, default=20, help="Max results")
    parser.add_argument("--debug", action="store_true",
                        help="Show debug messages for AT-SPI errors")
    args = parser.parse_args()

    if args.debug:
        set_debug(True)

    if not args.role and not args.name:
        parser.print_help()
        print("\nExamples:")
        print("  find-element.py --role button              # All buttons")
        print("  find-element.py --name 'Save'              # Elements named 'Save'")
        print("  find-element.py --role button --name OK    # OK buttons")
        print("  find-element.py -w Firefox -r link         # Links in Firefox")
        sys.exit(1)

    try:
        windows = find_windows(pattern=args.window, app_name=args.app)
    except Exception as e:
        print(f"Error accessing AT-SPI: {e}", file=sys.stderr)
        sys.exit(1)

    if not windows:
        if args.window or args.app:
            print("No matching windows found.", file=sys.stderr)
        else:
            # Search all windows
            windows = get_all_windows()

    all_results = []
    for win in windows:
        elements = find_elements(
            win["accessible"],
            role=args.role,
            name=args.name,
            limit=args.limit - len(all_results)
        )
        # Convert raw accessibles to info dicts
        all_results.extend(get_element_info(elem) for elem in elements)
        if len(all_results) >= args.limit:
            break

    print_results(all_results, args.json)


if __name__ == "__main__":
    main()
