"""Three.js world adapter — bridges Kimi's AI World Builder to WorldInterface.

Studied from Kimisworld.zip (issue #870).  Kimi's world is a React +
Three.js app ("AI World Builder v1.0") that exposes a JSON state API and
accepts ``addObject`` / ``updateObject`` / ``removeObject`` commands.

This adapter is a stub: ``connect()`` and the core methods outline the
HTTP / WebSocket wiring that would be needed to talk to a running instance.
The ``observe()`` response maps Kimi's ``WorldObject`` schema to
``PerceptionOutput`` entities so that any WorldInterface consumer can
treat the Three.js canvas like any other game world.

Usage::

    registry.register("threejs", ThreeJSWorldAdapter)
    adapter = registry.get("threejs", base_url="http://localhost:5173")
    adapter.connect()
    perception = adapter.observe()
    adapter.act(CommandInput(action="add_object", parameters={"geometry": "sphere", ...}))
    adapter.speak("Hello from Timmy", target="broadcast")
"""

from __future__ import annotations

import logging

from infrastructure.world.interface import WorldInterface
from infrastructure.world.types import ActionResult, CommandInput, PerceptionOutput

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Kimi's WorldObject geometry / material vocabulary (from WorldObjects.tsx)
# ---------------------------------------------------------------------------

_VALID_GEOMETRIES = {"box", "sphere", "cylinder", "torus", "cone", "dodecahedron"}
_VALID_MATERIALS = {"standard", "wireframe", "glass", "glow"}
_VALID_TYPES = {"mesh", "light", "particle", "custom"}


def _object_to_entity_description(obj: dict) -> str:
    """Render a Kimi WorldObject dict as a human-readable entity string.

    Example output: ``sphere/glow #ff006e at (2.1, 3.0, -1.5)``
    """
    geometry = obj.get("geometry", "unknown")
    material = obj.get("material", "unknown")
    color = obj.get("color", "#ffffff")
    pos = obj.get("position", [0, 0, 0])
    obj_type = obj.get("type", "mesh")
    pos_str = "({:.1f}, {:.1f}, {:.1f})".format(*pos)
    return f"{obj_type}/{geometry}/{material} {color} at {pos_str}"


class ThreeJSWorldAdapter(WorldInterface):
    """Adapter for Kimi's Three.js AI World Builder.

    Connects to a running Three.js world that exposes:
    - ``GET  /api/world/state``    — returns current WorldObject list
    - ``POST /api/world/execute``  — accepts addObject / updateObject code
    - WebSocket ``/ws/world``      — streams state change events

    All core methods raise ``NotImplementedError`` until HTTP wiring is
    added.  Implement ``connect()`` first — it should verify that the
    Three.js app is running and optionally open a WebSocket for live events.

    Key insight from studying Kimi's world (issue #870):
    - Objects carry a geometry, material, color, position, rotation, scale,
      and an optional *animation* string executed via ``new Function()``
      each animation frame.
    - The AI agent (``AIAgent.tsx``) moves through the world with lerp()
      targeting, cycles through moods, and pulses its core during "thinking"
      states — a model for how Timmy could manifest presence in a 3D world.
    - World complexity is tracked as a simple counter (one unit per object)
      which the AI uses to decide whether to create, modify, or upgrade.
    """

    def __init__(self, *, base_url: str = "http://localhost:5173") -> None:
        self._base_url = base_url.rstrip("/")
        self._connected = False

    # -- lifecycle ---------------------------------------------------------

    def connect(self) -> None:
        raise NotImplementedError(
            "ThreeJSWorldAdapter.connect() — verify Three.js app is running at "
            f"{self._base_url} and optionally open a WebSocket to /ws/world"
        )

    def disconnect(self) -> None:
        self._connected = False
        logger.info("ThreeJSWorldAdapter disconnected")

    @property
    def is_connected(self) -> bool:
        return self._connected

    # -- core contract (stubs) ---------------------------------------------

    def observe(self) -> PerceptionOutput:
        """Return current Three.js world state as structured perception.

        Expected HTTP call::

            GET {base_url}/api/world/state
            → {"objects": [...WorldObject], "worldComplexity": int, ...}

        Each WorldObject becomes an entity description string.
        """
        raise NotImplementedError(
            "ThreeJSWorldAdapter.observe() — GET /api/world/state, "
            "map each WorldObject via _object_to_entity_description()"
        )

    def act(self, command: CommandInput) -> ActionResult:
        """Dispatch a command to the Three.js world.

        Supported actions (mirrors Kimi's CodeExecutor API):
        - ``add_object``    — parameters: WorldObject fields (geometry, material, …)
        - ``update_object`` — parameters: id + partial WorldObject fields
        - ``remove_object`` — parameters: id
        - ``clear_world``   — parameters: (none)

        Expected HTTP call::

            POST {base_url}/api/world/execute
            Content-Type: application/json
            {"action": "add_object", "parameters": {...}}
        """
        raise NotImplementedError(
            f"ThreeJSWorldAdapter.act({command.action!r}) — "
            "POST /api/world/execute with serialised CommandInput"
        )

    def speak(self, message: str, target: str | None = None) -> None:
        """Inject a text message into the Three.js world.

        Kimi's world does not have a native chat layer, so the recommended
        implementation is to create a short-lived ``Text`` entity at a
        visible position (or broadcast via the world WebSocket).

        Expected WebSocket frame::

            {"type": "timmy_speech", "text": message, "target": target}
        """
        raise NotImplementedError(
            "ThreeJSWorldAdapter.speak() — send timmy_speech frame over "
            "/ws/world WebSocket, or POST a temporary Text entity"
        )
