"""
Playwright: lista elementos clicáveis numa URL (Chromium headless) e ações click/fill.
Requer: pip install playwright && playwright install chromium
"""

from __future__ import annotations

import re
from typing import Any
from urllib.parse import urljoin, urlparse

_INTERACTIVE_SELECTORS = ",".join(
    [
        'a[href]',
        "button",
        '[role="button"]',
        '[role="link"]',
        '[role="tab"]',
        '[role="menuitem"]',
        'input[type="button"]',
        'input[type="submit"]',
        'input[type="reset"]',
        'input[type="checkbox"]',
        'input[type="radio"]',
        'input[type="search"]',
        'input[type="text"]',
        'input[type="email"]',
        'input[type="password"]',
        'input:not([type])',
        "textarea",
        "select",
        "summary",
        '[tabindex]:not([tabindex="-1"])',
    ]
)


def _clamp(s: str, n: int) -> str:
    s = " ".join(s.split())
    return s if len(s) <= n else s[: n - 1] + "…"


def _css_escape_id(ident: str) -> str:
    if re.match(r"^[A-Za-z_][\w-]*$", ident):
        return f"#{ident}"
    escaped = ident.replace("\\", "\\\\").replace('"', '\\"')
    return f'[id="{escaped}"]'


try:
    from playwright.async_api import async_playwright as _async_playwright_check  # noqa: F401

    _PLAYWRIGHT_IMPORT_OK = True
except ImportError:
    _PLAYWRIGHT_IMPORT_OK = False


def _playwright_unavailable() -> dict[str, Any]:
    return {
        "success": False,
        "error": "playwright_not_installed",
        "hint": "pip install playwright && playwright install chromium",
    }


async def list_clickable_elements(
    url: str,
    *,
    max_elements: int = 80,
    timeout_ms: int = 35_000,
    wait_until: str = "domcontentloaded",
    viewport_width: int = 1365,
    viewport_height: int = 900,
) -> dict[str, Any]:
    if not _PLAYWRIGHT_IMPORT_OK:
        return _playwright_unavailable()
    from playwright.async_api import async_playwright

    if not url or not str(url).strip().startswith(("http://", "https://")):
        return {"success": False, "error": "url_invalid", "hint": "Use uma URL absoluta https:// ou http://"}

    max_elements = max(1, min(200, int(max_elements)))
    timeout_ms = max(3000, min(120_000, int(timeout_ms)))
    wu = wait_until if wait_until in ("domcontentloaded", "load", "networkidle", "commit") else "domcontentloaded"

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        try:
            context = await browser.new_context(
                viewport={"width": int(viewport_width), "height": int(viewport_height)},
                user_agent=(
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                    "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
                ),
            )
            page = await context.new_page()
            await page.goto(str(url).strip(), wait_until=wu, timeout=timeout_ms)
            await page.wait_for_timeout(400)

            base = page.url
            parsed_base = urlparse(base)

            raw_list: list[dict[str, Any]] = await page.evaluate(
                """([sel, maxN]) => {
                  const nodes = Array.from(document.querySelectorAll(sel));
                  const out = [];
                  for (const el of nodes) {
                    if (out.length >= maxN) break;
                    const rect = el.getBoundingClientRect();
                    if (rect.width < 2 || rect.height < 2) continue;
                    const st = window.getComputedStyle(el);
                    if (st.visibility === 'hidden' || st.display === 'none' || st.opacity === '0') continue;
                    if (rect.bottom < 0 || rect.right < 0) continue;
                    if (rect.top > window.innerHeight || rect.left > window.innerWidth) continue;
                    const tag = el.tagName.toLowerCase();
                    const role = el.getAttribute('role') || '';
                    const idAttr = el.getAttribute('id');
                    const href = tag === 'a' ? el.getAttribute('href') : null;
                    const typeAttr = tag === 'input' ? (el.getAttribute('type') || 'text') : null;
                    const name = (el.getAttribute('aria-label') || '').trim()
                      || (el.getAttribute('title') || '').trim()
                      || (el.getAttribute('placeholder') || '').trim();
                    const text = (el.innerText || '').trim().replace(/\\s+/g, ' ');
                    const textPreview = text.slice(0, 200);
                    const displayName = name || textPreview.slice(0, 120);
                    out.push({
                      tag,
                      role,
                      id_attr: idAttr,
                      href_raw: href,
                      type_attr: typeAttr,
                      name: displayName,
                      text_preview: textPreview,
                      box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
                    });
                  }
                  return out;
                }""",
                [_INTERACTIVE_SELECTORS, max_elements],
            )

            elements: list[dict[str, Any]] = []
            for i, row in enumerate(raw_list):
                href_abs = None
                if row.get("href_raw"):
                    try:
                        href_abs = urljoin(base, row["href_raw"])
                    except Exception:
                        href_abs = row["href_raw"]

                id_attr = row.get("id_attr")
                tag = row.get("tag") or "*"
                role = (row.get("role") or "").lower()
                name = row.get("name") or ""
                text_preview = row.get("text_preview") or ""

                suggested: dict[str, Any] = {}
                if id_attr:
                    suggested["selector"] = _css_escape_id(id_attr)
                elif tag == "a" and href_abs:
                    suggested["href"] = href_abs
                    suggested["hint"] = "usar params.href no playwright_click com este URL absoluto"
                elif name and len(name) >= 2:
                    suggested["match_text"] = _clamp(name, 80)
                    if role in ("button", "link", "tab", "menuitem"):
                        suggested["role"] = role
                    elif tag == "button":
                        suggested["role"] = "button"
                    elif tag == "a":
                        suggested["role"] = "link"
                elif text_preview and len(text_preview.strip()) >= 2:
                    suggested["match_text"] = _clamp(text_preview, 80)
                    if tag == "button":
                        suggested["role"] = "button"
                    elif tag == "a":
                        suggested["role"] = "link"

                elements.append(
                    {
                        "index": i,
                        "tag": tag,
                        "role": role or None,
                        "name": _clamp(name, 160) if name else None,
                        "text_preview": _clamp(text_preview, 200) if text_preview else None,
                        "href": href_abs,
                        "id_attr": id_attr,
                        "input_type": row.get("type_attr"),
                        "box_viewport": row.get("box"),
                        "suggested_playwright": suggested if suggested else None,
                    }
                )

            title = await page.title()
            return {
                "success": True,
                "action": "playwright_elements",
                "url_final": base,
                "url_requested": str(url).strip(),
                "title": title,
                "origin": f"{parsed_base.scheme}://{parsed_base.netloc}",
                "elements": elements,
                "count": len(elements),
                "note": "Chromium headless separado do browser do utilizador. Para clicar: playwright_click com url + selector OU href OU match_text (+ role opcional).",
            }
        finally:
            await browser.close()


async def perform_click(
    url: str,
    *,
    selector: str | None = None,
    href: str | None = None,
    match_text: str | None = None,
    role: str | None = None,
    timeout_ms: int = 25_000,
    wait_until: str = "domcontentloaded",
    viewport_width: int = 1365,
    viewport_height: int = 900,
) -> dict[str, Any]:
    if not _PLAYWRIGHT_IMPORT_OK:
        return _playwright_unavailable()
    from playwright.async_api import async_playwright

    if not url or not str(url).strip().startswith(("http://", "https://")):
        return {"success": False, "error": "url_invalid"}

    timeout_ms = max(3000, min(90_000, int(timeout_ms)))
    wu = wait_until if wait_until in ("domcontentloaded", "load", "networkidle", "commit") else "domcontentloaded"

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        try:
            context = await browser.new_context(
                viewport={"width": int(viewport_width), "height": int(viewport_height)},
            )
            page = await context.new_page()
            await page.goto(str(url).strip(), wait_until=wu, timeout=timeout_ms)
            await page.wait_for_timeout(300)

            if href and str(href).strip():
                h = str(href).strip()
                clicked = await page.evaluate(
                    """(targetHref) => {
                      const list = Array.from(document.querySelectorAll('a[href]'));
                      for (const a of list) {
                        try {
                          if (a.href === targetHref) { a.click(); return 'href_resolved'; }
                        } catch (e) {}
                      }
                      for (const a of list) {
                        const raw = a.getAttribute('href') || '';
                        if (raw === targetHref || targetHref.endsWith(raw)) { a.click(); return 'attr'; }
                      }
                      return null;
                    }""",
                    h,
                )
                if not clicked:
                    await page.get_by_role("link", name=re.compile(re.escape(h[:80]))).first.click(
                        timeout=timeout_ms
                    )
            elif selector and str(selector).strip():
                await page.locator(str(selector).strip()).first.click(timeout=timeout_ms)
            elif match_text and str(match_text).strip():
                t = str(match_text).strip()
                r = (role or "").strip().lower()
                if r in ("button", "link", "tab", "menuitem", "textbox", "checkbox", "radio", "combobox"):
                    await page.get_by_role(r, name=re.compile(re.escape(t[:120]))).first.click(timeout=timeout_ms)
                else:
                    await page.get_by_text(t, exact=False).first.click(timeout=timeout_ms)
            else:
                return {
                    "success": False,
                    "error": "missing_target",
                    "hint": "Passe params.selector, params.href (links), ou params.match_text (+ params.role opcional).",
                }

            return {
                "success": True,
                "action": "playwright_click",
                "url": page.url,
            }
        finally:
            await browser.close()


async def perform_fill(
    url: str,
    *,
    selector: str | None = None,
    match_placeholder: str | None = None,
    text: str = "",
    timeout_ms: int = 25_000,
    wait_until: str = "domcontentloaded",
    viewport_width: int = 1365,
    viewport_height: int = 900,
    clear_first: bool = True,
) -> dict[str, Any]:
    if not _PLAYWRIGHT_IMPORT_OK:
        return _playwright_unavailable()
    from playwright.async_api import async_playwright

    if not url or not str(url).strip().startswith(("http://", "https://")):
        return {"success": False, "error": "url_invalid"}

    timeout_ms = max(3000, min(90_000, int(timeout_ms)))
    wu = wait_until if wait_until in ("domcontentloaded", "load", "networkidle", "commit") else "domcontentloaded"

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        try:
            context = await browser.new_context(
                viewport={"width": int(viewport_width), "height": int(viewport_height)},
            )
            page = await context.new_page()
            await page.goto(str(url).strip(), wait_until=wu, timeout=timeout_ms)
            await page.wait_for_timeout(300)

            if selector and str(selector).strip():
                loc = page.locator(str(selector).strip()).first
            elif match_placeholder and str(match_placeholder).strip():
                loc = page.get_by_placeholder(str(match_placeholder).strip()).first
            else:
                return {
                    "success": False,
                    "error": "missing_target",
                    "hint": "Passe params.selector ou params.match_placeholder.",
                }

            if clear_first:
                await loc.clear(timeout=timeout_ms)
            await loc.fill(str(text), timeout=timeout_ms)

            return {
                "success": True,
                "action": "playwright_fill",
                "url": page.url,
            }
        finally:
            await browser.close()
