from __future__ import annotations

from dataclasses import dataclass
from typing import Collection, Dict, Mapping, Protocol, runtime_checkable

import numpy as np
import pinocchio as pin

from .teleop_config import ArmName


@dataclass(frozen=True)
class DualArmJointState:
    positions_rad: Mapping[ArmName, np.ndarray]

    def __post_init__(self) -> None:
        normalized: Dict[ArmName, np.ndarray] = {}
        if set(self.positions_rad) != {"left", "right"}:
            raise ValueError("dual-arm state must contain left and right positions")
        for arm, values in self.positions_rad.items():
            q = np.asarray(values, dtype=float).copy()
            if q.shape != (7,) or not np.all(np.isfinite(q)):
                raise ValueError(f"{arm} joint state must be finite with shape (7,)")
            q.setflags(write=False)
            normalized[arm] = q
        object.__setattr__(self, "positions_rad", normalized)


@runtime_checkable
class RobotBackend(Protocol):
    def connect(self) -> None: ...

    def read_joint_positions(self) -> DualArmJointState: ...

    def command_joint_positions(
        self, targets_rad: Mapping[ArmName, np.ndarray]
    ) -> None: ...

    def set_target_tcp_pose(self, arm: ArmName, target_tcp: pin.SE3) -> None: ...

    def reset_to_initial(self) -> None: ...

    def stop(self, arms: Collection[ArmName] = ("left", "right")) -> None: ...

    def close(self) -> None: ...
