"""AppSession — lifecycle management for headless Textual apps."""

from __future__ import annotations

from contextlib import contextmanager

from textual._context import active_app, active_message_pump
from textual.app import App
from textual.pilot import Pilot

from textual_mcp.error_collector import ErrorCollector
from textual_mcp.serializers.widget_tree import WidgetTreeSerializer


class AppSession:
    """Manages the lifecycle of a headless Textual app.

    Wraps App.run_test() to provide launch/stop semantics with
    error collection via message hooks.
    """

    def __init__(self, session_id: str) -> None:
        self.session_id = session_id
        self.app: App | None = None
        self.pilot: Pilot | None = None
        self.error_collector = ErrorCollector()
        self.serializer = WidgetTreeSerializer()
        self._is_running = False
        self._context_manager = None

    @property
    def is_running(self) -> bool:
        return self._is_running

    async def launch(self, app: App, size: tuple[int, int] = (120, 40)) -> None:
        """Launch an app headlessly with a Pilot.

        Args:
            app: The Textual App instance to run.
            size: Terminal dimensions as (width, height).
        """
        self._context_manager = app.run_test(
            headless=True,
            size=size,
            message_hook=self.error_collector.on_message,
        )
        self.pilot = await self._context_manager.__aenter__()
        self.app = self.pilot.app
        self._is_running = True

    @contextmanager
    def app_context(self):
        """Set Textual ContextVars for pilot operations in the current task."""
        token_app = active_app.set(self.app)
        token_pump = active_message_pump.set(self.app)
        try:
            yield
        finally:
            active_message_pump.reset(token_pump)
            active_app.reset(token_app)

    async def stop(self) -> None:
        """Stop the running app. Idempotent — safe to call multiple times."""
        if not self._is_running:
            return
        # Set ContextVars for shutdown (needed by app internals).
        # The original _context() tokens from launch can't be reset in this
        # task, so we catch the RuntimeError from that cleanup.
        active_app.set(self.app)
        active_message_pump.set(self.app)
        try:
            await self._context_manager.__aexit__(None, None, None)
        except (RuntimeError, ValueError) as e:
            if "different Context" not in str(e):
                raise
        self._is_running = False
