"""
HealingLocator — a transparent wrapper around Playwright's Locator that
self-heals when the underlying element is not found.

Usage:
  locator = HealingLocator(page.locator("#submit"), "Submit button", pipeline)
  await locator.click()          # heals automatically if #submit is gone
  await locator.fill("value")
  count = await locator.count()
"""

from __future__ import annotations

import logging
from typing import Any, Optional

from playwright.async_api import Locator, Page

from playwright_healer.pipeline import HealingPipeline

logger = logging.getLogger("playwright_healer.locator")


class HealingLocator:
    """
    Wraps a Playwright Locator to self-heal on interaction.

    If the element is not found when an action is performed, the healing
    pipeline is invoked with the original selector and description.
    All Playwright Locator methods are forwarded transparently.
    """

    def __init__(
        self,
        selector: str,
        description: str,
        pipeline: HealingPipeline,
        initial_locator: Optional[Locator] = None,
    ) -> None:
        self._selector = selector
        self._description = description
        self._pipeline = pipeline
        self._cached_locator: Optional[Locator] = initial_locator

    async def _resolve(self) -> Locator:
        """Return a valid locator, healing if necessary."""
        if self._cached_locator is not None:
            try:
                count = await self._cached_locator.count()
                if count > 0:
                    return self._cached_locator
            except Exception:
                pass
        locator = await self._pipeline.find(self._selector, self._description)
        self._cached_locator = locator
        return locator

    # ------------------------------------------------------------------
    # Playwright Locator API forwarding
    # ------------------------------------------------------------------

    async def click(self, **kwargs: Any) -> None:
        return await (await self._resolve()).click(**kwargs)

    async def fill(self, value: str, **kwargs: Any) -> None:
        return await (await self._resolve()).fill(value, **kwargs)

    async def type(self, text: str, **kwargs: Any) -> None:
        return await (await self._resolve()).type(text, **kwargs)

    async def press(self, key: str, **kwargs: Any) -> None:
        return await (await self._resolve()).press(key, **kwargs)

    async def check(self, **kwargs: Any) -> None:
        return await (await self._resolve()).check(**kwargs)

    async def uncheck(self, **kwargs: Any) -> None:
        return await (await self._resolve()).uncheck(**kwargs)

    async def select_option(self, value: Any = None, **kwargs: Any) -> Any:
        return await (await self._resolve()).select_option(value, **kwargs)

    async def hover(self, **kwargs: Any) -> None:
        return await (await self._resolve()).hover(**kwargs)

    async def focus(self, **kwargs: Any) -> None:
        return await (await self._resolve()).focus(**kwargs)

    async def clear(self, **kwargs: Any) -> None:
        return await (await self._resolve()).clear(**kwargs)

    async def tap(self, **kwargs: Any) -> None:
        return await (await self._resolve()).tap(**kwargs)

    async def dispatch_event(self, event_type: str, **kwargs: Any) -> None:
        return await (await self._resolve()).dispatch_event(event_type, **kwargs)

    async def scroll_into_view_if_needed(self, **kwargs: Any) -> None:
        return await (await self._resolve()).scroll_into_view_if_needed(**kwargs)

    async def wait_for(self, **kwargs: Any) -> None:
        return await (await self._resolve()).wait_for(**kwargs)

    async def inner_text(self, **kwargs: Any) -> str:
        return await (await self._resolve()).inner_text(**kwargs)

    async def inner_html(self, **kwargs: Any) -> str:
        return await (await self._resolve()).inner_html(**kwargs)

    async def text_content(self, **kwargs: Any) -> Optional[str]:
        return await (await self._resolve()).text_content(**kwargs)

    async def input_value(self, **kwargs: Any) -> str:
        return await (await self._resolve()).input_value(**kwargs)

    async def get_attribute(self, name: str, **kwargs: Any) -> Optional[str]:
        return await (await self._resolve()).get_attribute(name, **kwargs)

    async def is_visible(self, **kwargs: Any) -> bool:
        return await (await self._resolve()).is_visible(**kwargs)

    async def is_hidden(self, **kwargs: Any) -> bool:
        return await (await self._resolve()).is_hidden(**kwargs)

    async def is_enabled(self, **kwargs: Any) -> bool:
        return await (await self._resolve()).is_enabled(**kwargs)

    async def is_disabled(self, **kwargs: Any) -> bool:
        return await (await self._resolve()).is_disabled(**kwargs)

    async def is_checked(self, **kwargs: Any) -> bool:
        return await (await self._resolve()).is_checked(**kwargs)

    async def count(self) -> int:
        try:
            return await (await self._resolve()).count()
        except Exception:
            return 0

    async def all(self) -> list:
        return await (await self._resolve()).all()

    async def first(self) -> Locator:
        return (await self._resolve()).first

    async def last(self) -> Locator:
        return (await self._resolve()).last

    async def nth(self, index: int) -> Locator:
        return (await self._resolve()).nth(index)

    async def screenshot(self, **kwargs: Any) -> bytes:
        return await (await self._resolve()).screenshot(**kwargs)

    async def evaluate(self, expression: str, **kwargs: Any) -> Any:
        return await (await self._resolve()).evaluate(expression, **kwargs)

    async def bounding_box(self, **kwargs: Any) -> Any:
        return await (await self._resolve()).bounding_box(**kwargs)

    def __repr__(self) -> str:
        return f"HealingLocator(selector={self._selector!r}, description={self._description!r})"
