"""Robot control use case built on top of the OmniLink Python engine."""

from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, List

from omnilink import OmniLinkEngine, TypeRegistry, load_patterns_from_file

from omnilink.use_case_loader import UseCaseConfig
from .api import backward, forward, get_pose, stop, turn_left, turn_right

HERE = Path(__file__).resolve().parent
PATTERNS_FILE = HERE / "commands.txt"


_NUMBER_RE = re.compile(r"-?\d+(?:\.\d+)?")


def _extract_numbers(command: str) -> List[float]:
    """Return all numeric values embedded in ``command`` as floats."""

    return [float(match) for match in _NUMBER_RE.findall(command)]


def _call_motion(
    evt: Dict[str, Any],
    handler: Callable[[float, float], Dict[str, Any]],
    *,
    expected: int = 2,
) -> Dict[str, Any]:
    """Run ``handler`` with ``expected`` numeric arguments parsed from the event."""

    numbers = _extract_numbers(evt.get("command", ""))
    if len(numbers) < expected:
        print(
            "[robot-example] Not enough numeric arguments for template",
            evt.get("template"),
        )
        return {"ack": False}

    args: Iterable[float] = numbers[:expected]
    try:
        result = handler(*args)
    except Exception as exc:  # pragma: no cover - defensive logging
        print(f"[robot-example] Robot command failed: {exc}")
        return {"ack": False, "error": str(exc)}

    return {"ack": True, "result": result}


def _handle_stop(_evt: Dict[str, Any]) -> Dict[str, Any]:
    try:
        result = stop()
    except Exception as exc:  # pragma: no cover - defensive logging
        print(f"[robot-example] Failed to stop robot: {exc}")
        return {"ack": False, "error": str(exc)}
    return {"ack": True, "result": result}


_DISPATCH: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {
    "move_forward_at_[number]_m/s_for_[number]_seconds": lambda evt: _call_motion(
        evt, forward
    ),
    "move_backward_at_[number]_m/s_for_[number]_seconds": lambda evt: _call_motion(
        evt, backward
    ),
    "turn_right_at_[number]_rad/s_for_[number]_seconds": lambda evt: _call_motion(
        evt, turn_right
    ),
    "turn_left_at_[number]_rad/s_for_[number]_seconds": lambda evt: _call_motion(
        evt, turn_left
    ),
    "stop": _handle_stop,
}


def _handle_any(evt: Dict[str, Any]) -> Dict[str, Any]:
    template = evt.get("template")
    if not template:
        print("[robot-example] Event did not match a known template")
        return {"ack": False}

    handler = _DISPATCH.get(template)
    if handler is None:
        print(f"[robot-example] No handler for template: {template}")
        return {"ack": False}

    return handler(evt)


def _configure(engine: OmniLinkEngine) -> None:
    engine.on(lambda _evt: True, _handle_any)


def _build_context_payload() -> str:
    """Build a compact JSON payload describing the latest robot pose."""

    try:
        pose = get_pose()
    except Exception as exc:  # pragma: no cover - telemetry helper
        return json.dumps({"pose_error": str(exc)})

    if not isinstance(pose, dict):
        return json.dumps({"pose": pose})
    return json.dumps({"pose": pose})


def build_use_case() -> UseCaseConfig:
    types = TypeRegistry()
    templates = load_patterns_from_file(PATTERNS_FILE, types)
    return UseCaseConfig(
        types=types,
        templates=templates,
        configure=_configure,
        context_provider=_build_context_payload,
    )


USE_CASE = build_use_case()


__all__ = [
    "PATTERNS_FILE",
    "USE_CASE",
    "build_use_case",
]
