"""Abstract base classes for device runtime control.

DeviceRuntime provides a unified interface across Android (ADB/Shizuku/A11y),
Desktop (Linux/macOS/Windows), and ChromeOS devices. Agent logic is fully
decoupled from the concrete device implementation.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import AsyncIterator


class DeviceType(str, Enum):
    ANDROID = "android"
    CHROMEOS = "chromeos"
    DESKTOP_LINUX = "desktop-linux"
    DESKTOP_WINDOWS = "desktop-windows"
    DESKTOP_MACOS = "desktop-macos"


class DeviceCapability(str, Enum):
    TOUCH = "touch"
    KEYBOARD = "keyboard"
    ACCESSIBILITY = "accessibility"
    SCREENSHOT = "screenshot"
    SCREEN_STREAM = "screen-stream"
    APP_INSTALL = "app-install"
    FILE_TRANSFER = "file-transfer"
    SHELL = "shell"


@dataclass(frozen=True, slots=True)
class DeviceInfo:
    type: DeviceType
    name: str
    os: str
    width: int
    height: int
    capabilities: list[DeviceCapability]
    os_version: str = ""
    density: int = 0
    metadata: dict[str, str] = field(default_factory=dict)

    @property
    def device_type(self) -> DeviceType:
        """Alias for ``type`` used by higher-level code."""
        return self.type


class DeviceRuntime(ABC):
    """Abstract base for controlling a device.

    Each concrete runtime (ADB, Shizuku, A11y, Desktop) implements these
    methods. The DeviceAgent interacts solely through this interface.
    """

    @abstractmethod
    async def connect(self) -> None:
        """Establish connection to the device."""

    @abstractmethod
    async def disconnect(self) -> None:
        """Release connection and resources."""

    @abstractmethod
    async def get_info(self) -> DeviceInfo:
        """Return device metadata including capabilities."""

    @abstractmethod
    async def screenshot(self) -> bytes:
        """Capture the current screen as a PNG image."""

    @abstractmethod
    async def get_ui_tree(self) -> list[dict]:
        """Return the accessibility / UI automation tree as a list of nodes."""

    @abstractmethod
    async def tap(self, x: int, y: int) -> None:
        """Simulate a tap at (x, y)."""

    @abstractmethod
    async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> None:
        """Simulate a long press at (x, y) for *duration_ms* milliseconds."""

    @abstractmethod
    async def swipe(
        self, x1: int, y1: int, x2: int, y2: int, duration_ms: int = 300
    ) -> None:
        """Swipe from (x1, y1) to (x2, y2) over *duration_ms* milliseconds."""

    @abstractmethod
    async def type_text(self, text: str) -> None:
        """Type *text* via the device input method."""

    @abstractmethod
    async def press_key(self, keycode: str) -> None:
        """Press a key by its keycode name (e.g. ``KEYCODE_BACK``)."""

    @abstractmethod
    async def open_app(self, package_or_path: str) -> None:
        """Open an application by package name or path."""

    @abstractmethod
    async def install_app(self, apk_path: str) -> None:
        """Install an application from *apk_path*."""

    @abstractmethod
    async def execute_shell(self, command: str) -> str:
        """Execute a shell command and return stdout."""

    async def start_screen_stream(self) -> AsyncIterator[bytes]:
        """Start streaming H.264 NAL units from the device screen.

        Optional — not all runtimes support direct streaming.
        Screen streaming is typically handled by ScrcpyAdapter + WebRTCPeer.
        """
        raise NotImplementedError(
            f"{type(self).__name__} does not implement start_screen_stream. "
            "Use ScrcpyAdapter for screen streaming."
        )
        yield b""  # type: ignore[misc]
