from __future__ import annotations

import logging

from pydantic import BaseModel, Field
from typing_extensions import Self

logger = logging.getLogger(__name__)


class AppiumData(BaseModel):
    type: str
    value: str | None = None
    name: str | None = None
    label: str | None = None
    x: int
    y: int
    width: int
    height: int

    @classmethod
    def factory(cls, xml_element) -> Self:
        return cls(
            type=xml_element.tag,
            value=xml_element.attrib.get("value"),
            name=(
                xml_element.attrib["appium_name"]
                if "appium_name" in xml_element.attrib
                else xml_element.attrib.get("name")
            ),
            label=(
                xml_element.attrib["appium_label"]
                if "appium_label" in xml_element.attrib
                else xml_element.attrib.get("label")
            ),
            x=(
                int(xml_element.attrib["appium_x"])
                if "appium_x" in xml_element.attrib
                else int(xml_element.attrib.get("x"))
            ),
            y=(
                int(xml_element.attrib["appium_y"])
                if "appium_y" in xml_element.attrib
                else int(xml_element.attrib.get("y"))
            ),
            width=int(xml_element.attrib.get("width")),
            height=int(xml_element.attrib.get("height")),
        )


class ActiveElement(BaseModel):
    source: str
    appium_data: AppiumData | None = None

    type: str
    name: str | None = None
    label: str | None = None

    x: int
    y: int
    width: int
    height: int

    scrollable: str | None = None

    @classmethod
    def factory(cls, xml_element) -> Self:
        source_data = AppiumData.factory(xml_element)

        return cls(
            source=xml_element.attrib.get("source") or "appium",
            appium_data=source_data,
            type=xml_element.attrib.get("type"),
            name=xml_element.attrib.get("name"),
            label=xml_element.attrib.get("label"),
            x=int(xml_element.attrib.get("x")),
            y=int(xml_element.attrib.get("y")),
            width=int(xml_element.attrib.get("width")),
            height=int(xml_element.attrib.get("height")),
            scrollable=xml_element.attrib.get("scrollable"),
        )

    @property
    def center_x(self) -> int:
        return self.x + self.width // 2

    @property
    def center_y(self) -> int:
        return self.y + self.height // 2

    @property
    def value(self) -> str | None:
        return self.appium_data.value

    @property
    def string_description(self) -> str:
        coordinates = f"(x:{self.center_x}, y:{self.center_y})"
        return f"type='{self.type}' label='{self.label}' name='{self.name}' coordinates={coordinates}"

    @property
    def appium_xpath(self) -> str | None:
        if self.source != "appium" or not self.appium_data:
            return None

        def escape_xpath_string(value: str) -> str:
            """Produces a valid XPath string literal by using quote wrapping or concat() pattern."""
            if "'" not in value:
                # No single quotes - wrap in single quotes
                return f"'{value}'"
            elif '"' not in value:
                # No double quotes - wrap in double quotes
                return f'"{value}"'
            else:
                # Both quote types present - use concat() pattern
                parts = value.split("'")
                concat_parts = [f"'{part}'" for part in parts[:-1]]
                concat_parts.append(f"'{parts[-1]}'")
                # Intersperse with literal single quotes
                result_parts = []
                for i, part in enumerate(parts):
                    if i > 0:
                        result_parts.append("'\"'\"'")
                    result_parts.append(f"'{part}'")
                return f"concat({', '.join(result_parts)})"

        conditions: list[str] = [
            f"@x='{self.appium_data.x}'",
            f"@y='{self.appium_data.y}'",
            f"@width='{self.appium_data.width}'",
            f"@height='{self.appium_data.height}'",
        ]

        if self.appium_data.label:
            label = escape_xpath_string(self.appium_data.label)
            conditions.insert(0, f"@label={label}")

        if self.appium_data.name:
            name = escape_xpath_string(self.appium_data.name)
            conditions.insert(
                0 if self.appium_data.label is None else 1, f"@name={name}"
            )

        condition_str = " and ".join(conditions)
        return f"//{self.appium_data.type}[{condition_str}]"


class Screen(BaseModel):
    elements_tree: str | None = Field(default=None)
    screenshot_url: str | None = Field(
        default=None
    )  # Public URL for screenshot retrieval
