"""GenerativeUI — a Provider that adds LLM-generated UI capabilities.

Registers tools and resources from ``prefab_ui.generative`` so that an
LLM can write Prefab Python code, execute it in a sandbox, and render
the result as a streaming interactive UI.

Requires ``fastmcp[apps]`` (prefab-ui).

Usage::

    from fastmcp import FastMCP
    from fastmcp.apps.generative import GenerativeUI

    mcp = FastMCP("My Server")
    mcp.add_provider(GenerativeUI())
"""

try:
    import prefab_ui.generative as _gen
    from prefab_ui.renderer import (
        get_generative_renderer_csp,
        get_generative_renderer_html,
    )
except ImportError as _exc:
    raise ImportError(
        "GenerativeUI requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
    ) from _exc

import json
from collections.abc import AsyncIterator, Sequence
from contextlib import asynccontextmanager
from typing import Any

from fastmcp.apps.config import AppConfig, ResourceCSP, app_config_to_meta_dict
from fastmcp.server.providers.base import Provider
from fastmcp.server.providers.local_provider import LocalProvider
from fastmcp.tools.base import Tool
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mime import UI_MIME_TYPE

logger = get_logger(__name__)


def _build_csp() -> ResourceCSP:
    """Build CSP from the generative renderer's declared requirements."""
    csp = get_generative_renderer_csp()
    return ResourceCSP(
        resource_domains=csp.get("resource_domains"),
        connect_domains=csp.get("connect_domains"),
    )


class GenerativeUI(Provider):
    """A Provider that adds generative UI capabilities to a server.

    Registers:

    - A ``generate_ui`` tool that accepts Prefab Python code, executes
      it in a Pyodide sandbox, and returns the rendered PrefabApp.
      Supports streaming via ``ontoolinputpartial``.
    - A ``components`` tool that searches the Prefab component library.
    - The generative renderer resource with CSP for Pyodide CDN access.

    Example::

        from fastmcp import FastMCP
        from fastmcp.apps.generative import GenerativeUI

        mcp = FastMCP("My Server")
        mcp.add_provider(GenerativeUI())
    """

    def __init__(
        self,
        *,
        tool_name: str = "generate_prefab_ui",
        include_components_tool: bool = True,
        components_tool_name: str = "search_prefab_components",
    ) -> None:
        super().__init__()
        self._tool_name = tool_name
        self._components_tool_name = components_tool_name
        self._include_components_tool = include_components_tool
        self._local = LocalProvider(on_duplicate="error")
        self._sandbox: Any = None
        self._setup_done = False

    def __repr__(self) -> str:
        return f"GenerativeUI(tool_name={self._tool_name!r})"

    def _get_sandbox(self) -> Any:
        """Lazily create the Pyodide sandbox."""
        if self._sandbox is None:
            from prefab_ui.sandbox import Sandbox

            self._sandbox = Sandbox()
        return self._sandbox

    def _ensure_setup(self) -> None:
        """Lazily register tools and resources on first access."""
        if self._setup_done:
            return

        csp = _build_csp()
        app_config = AppConfig(resource_uri=_gen.RESOURCE_URI, csp=csp)

        # -- generate_ui tool --
        # Wraps prefab_ui.generative.execute with sandbox lifecycle management.

        from prefab_ui.app import PrefabApp

        sandbox_ref = self  # capture for closure

        async def generate_ui(
            code: str,
            data: str | dict[str, Any] | None = None,
        ) -> PrefabApp:
            parsed_data: dict[str, Any] | None
            if isinstance(data, str):
                parsed_data = json.loads(data) if data.strip() else None
            else:
                parsed_data = data
            return await _gen.execute(
                code,
                data=parsed_data,
                sandbox=sandbox_ref._get_sandbox(),
            )

        tool = Tool.from_function(
            generate_ui,
            name=self._tool_name,
            description=_gen.execute.__doc__ or "",
            meta={"ui": app_config_to_meta_dict(app_config)},
        )
        self._local._add_component(tool)

        # -- components tool --

        if self._include_components_tool:
            components_tool = Tool.from_function(
                _gen.search_components,
                name=self._components_tool_name,
                description=_gen.search_components.__doc__ or "",
            )
            self._local._add_component(components_tool)

        # -- generative renderer resource --

        from fastmcp.resources.types import TextResource

        resource_config = AppConfig(csp=csp)
        resource = TextResource(
            uri=_gen.RESOURCE_URI,  # type: ignore[arg-type]
            name="Prefab Generative Renderer",
            text=get_generative_renderer_html(),
            mime_type=UI_MIME_TYPE,
            meta={"ui": app_config_to_meta_dict(resource_config)},
        )
        self._local._add_component(resource)

        self._setup_done = True

    # ------------------------------------------------------------------
    # Provider interface
    # ------------------------------------------------------------------

    async def _list_tools(self) -> Sequence[Tool]:
        self._ensure_setup()
        return await self._local._list_tools()

    async def _get_tool(self, name: str, version: Any = None) -> Tool | None:
        self._ensure_setup()
        return await self._local._get_tool(name, version)

    async def _list_resources(self) -> Sequence[Any]:
        self._ensure_setup()
        return await self._local._list_resources()

    async def _get_resource(self, uri: str, version: Any = None) -> Any | None:
        self._ensure_setup()
        return await self._local._get_resource(uri, version)

    async def _list_resource_templates(self) -> Sequence[Any]:
        return []

    async def _get_resource_template(self, uri: str, version: Any = None) -> Any | None:
        return None

    async def _list_prompts(self) -> Sequence[Any]:
        return []

    async def _get_prompt(self, name: str, version: Any = None) -> Any | None:
        return None

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        self._ensure_setup()
        async with self._local.lifespan():
            yield
