from typing import TYPE_CHECKING, Optional
import random
import asyncio
import json


if TYPE_CHECKING:
    from .Tab import Tab

class ElementoWeb:
    def __init__(self, tab: "Tab", object_id: Optional[str]):
        self.tab = tab
        self.object_id = object_id
        self._node_id = None  # Lazy resolved

    @classmethod
    def from_node(cls, tab: "Tab", node_id: int) -> "ElementoWeb":
        inst = cls(tab, object_id=None)
        inst._node_id = node_id
        return inst

    async def _asegurar_object_id(self):
        if not self.object_id and self._node_id:
            try:
                resolved = await self.tab._enviar("DOM.resolveNode", {"nodeId": self._node_id})
                self.object_id = resolved["object"]["objectId"]
            except Exception as e:
                print(f"⚠️ No se pudo resolver objectId desde nodeId: {e}")

    async def scroll_into_view(self):
        try:
            await self._asegurar_object_id()
            await self.tab._enviar("Runtime.callFunctionOn", {
                "objectId": self.object_id,
                "functionDeclaration": "function() { this.scrollIntoView({block: 'center'}); }",
                "awaitPromise": True
            })
            if self.tab.verbose:
                print("📜 Elemento desplazado a la vista.")
        except Exception as e:
            print(f"⚠️ Error al hacer scroll hacia el elemento: {e}")

    async def click(self):
        try:
            await self.scroll_into_view()
            await self._asegurar_object_id()
            if not self.object_id:
                raise ValueError("No se puede obtener objectId del elemento para hacer click.")

            # Intenta obtener coordenadas del nodo
            node_result = await self.tab._enviar("DOM.describeNode", {
                "objectId": self.object_id
            })
            node_id = node_result["node"]["nodeId"]

            try:
                box_model = await self.tab._enviar("DOM.getBoxModel", {"nodeId": node_id})
                content = box_model["model"]["content"]
                x = (content[0] + content[2]) / 2
                y = (content[1] + content[5]) / 2
            except:
                quads_result = await self.tab._enviar("DOM.getContentQuads", {"nodeId": node_id})
                quad = quads_result["quads"][0]
                x = (quad[0] + quad[4]) / 2
                y = (quad[1] + quad[5]) / 2

            # 🧠 Enfocar el elemento antes de clickear
            await self.tab._enviar("DOM.focus", {
                "objectId": self.object_id
            })

            # 🎯 Movimiento humanoide opcional
            start_x, start_y = x + random.uniform(-100, 100), y + random.uniform(-100, 100)
            steps = random.randint(5, 12)
            for i in range(1, steps + 1):
                curr_x = start_x + (x - start_x) * i / steps + random.uniform(-1, 1)
                curr_y = start_y + (y - start_y) * i / steps + random.uniform(-1, 1)
                await self.tab._enviar("Input.dispatchMouseEvent", {
                    "type": "mouseMoved",
                    "x": curr_x,
                    "y": curr_y,
                })
                await asyncio.sleep(random.uniform(0.01, 0.05))

            # 👆 Mouse Down
            await self.tab._enviar("Input.dispatchMouseEvent", {
                "type": "mousePressed",
                "x": x,
                "y": y,
                "button": "left",
                "clickCount": 1
            })

            await asyncio.sleep(random.uniform(0.05, 0.15))

            # 👇 Mouse Up
            await self.tab._enviar("Input.dispatchMouseEvent", {
                "type": "mouseReleased",
                "x": x,
                "y": y,
                "button": "left",
                "clickCount": 1
            })

            await asyncio.sleep(random.uniform(0.01, 0.05))

            # 🖱️ Click manual adicional
            await self.tab._enviar("Input.dispatchMouseEvent", {
                "type": "mouseClicked",
                "x": x,
                "y": y,
                "button": "left",
                "clickCount": 1
            })

            if self.tab.verbose:
                print(f"🖱️ Click humano simulado en ({x:.1f}, {y:.1f})")

        except Exception as e:
            print(f"⚠️ Error al hacer click físico: {e}")
            print("🧪 Intentando fallback con JavaScript click()...")
            await self.click_js()

    async def click_js(self):
        try:
            await self._asegurar_object_id()
            if not self.object_id:
                print("⚠️ No se puede hacer click JS: objectId no disponible.")
                return
            await self.tab._enviar("Runtime.callFunctionOn", {
                "objectId": self.object_id,
                "functionDeclaration": "function() { this.click(); }",
                "awaitPromise": True
            })
            if self.tab.verbose:
                print("🖱️ Click simulado por JavaScript (element.click())")
        except Exception as e:
            print(f"⚠️ Error al ejecutar click en JS: {e}")

    async def obtener_texto(self) -> Optional[str]:
        try:
            await self._asegurar_object_id()
            result = await self.tab._enviar("Runtime.callFunctionOn", {
                "objectId": self.object_id,
                "functionDeclaration": "function() { return this.textContent; }",
                "returnByValue": True
            })
            return result.get("result", {}).get("value")
        except Exception as e:
            print(f"⚠️ Error al obtener texto del elemento: {e}")
            return None

    async def escribir_texto(self, texto: str):
        try:
            await self._asegurar_object_id()
            await self.tab._enviar("Runtime.callFunctionOn", {
                "objectId": self.object_id,
                "functionDeclaration": f"function() {{ this.value = {json.dumps(texto)}; this.dispatchEvent(new Event('input')); }}",
                "awaitPromise": True
            })
            if self.tab.verbose:
                print(f"⌨️ Texto escrito en elemento: '{texto}'")
        except Exception as e:
            print(f"⚠️ Error al escribir texto: {e}")


    async def encontrar_hijo_clickeable(self) -> Optional["ElementoWeb"]:
        try:
            await self._asegurar_object_id()
            resultado = await self.tab._enviar("Runtime.callFunctionOn", {
                "objectId": self.object_id,
                "functionDeclaration": """
                function() {
                    const candidatos = this.querySelectorAll("span, div, a, button");
                    for (const el of candidatos) {
                        const style = window.getComputedStyle(el);
                        const visible = style.display !== "none" && style.visibility !== "hidden";
                        const interactivo = style.pointerEvents !== "none";
                        if (visible && interactivo) return el;
                    }
                    return this;
                }
                """,
                "returnByValue": False
            })
            if "result" in resultado and "objectId" in resultado["result"]:
                return ElementoWeb(self.tab, resultado["result"]["objectId"])
        except Exception as e:
            print(f"⚠️ No se pudo encontrar hijo clickeable: {e}")
        return None