"""
zmq_api.py — ZMQ velocity + command interface to Holosoma

Previously the PUB socket was bound at module import time. That made the
module unsafe to re-import from any multiprocessing child (e.g. the LiDAR
SLAM_worker spawn), because the child would try to rebind the same port
and crash with `Address already in use`.

The bind now lives in init_zmq() — call it once from the brain entrypoint.
Child processes can import this module without any network side effects.
"""
import json
import os
import time
import zmq
from Core.config_loader import load_config
from Core.logger import log

_cfg = load_config("ZMQ")

ZMQ_HOST        = _cfg["zmq_host"]
ZMQ_PORT        = _cfg["zmq_port"]
STOP_ITERATIONS = _cfg["stop_iterations"]
STOP_DELAY      = _cfg["stop_delay"]
STEP_PAUSE      = _cfg["step_pause"]

# Shared state. These stay None until init_zmq() is called.
ctx: zmq.Context = None
sock: zmq.Socket = None
_INIT_SETTLE = 0.5       # seconds to let PUB tell subscribers it's alive


def init_zmq() -> zmq.Socket:
    """
    Bind the PUB socket. Idempotent — safe to call more than once.
    Call from the main (parent) process only. Do NOT call from multiprocessing
    children — they inherit nothing useful from the bound socket anyway.
    """
    global ctx, sock
    if sock is not None:
        return sock
    ctx  = zmq.Context()
    sock = ctx.socket(zmq.PUB)
    sock.bind(f"tcp://{ZMQ_HOST}:{ZMQ_PORT}")
    time.sleep(_INIT_SETTLE)
    log(f"ZMQ PUB bound on tcp://{ZMQ_HOST}:{ZMQ_PORT} (pid={os.getpid()})",
        "info", "zmq")
    return sock


def _ensure_sock() -> zmq.Socket:
    if sock is None:
        raise RuntimeError(
            "zmq_api not initialized — call init_zmq() from the brain "
            "entrypoint before using send_vel/send_cmd/gradual_stop"
        )
    return sock


def get_socket():
    """Return the shared ZMQ PUB socket (for odometry to reuse)."""
    return _ensure_sock()


def send_vel(vx: float = 0.0, vy: float = 0.0, vyaw: float = 0.0):
    """Send velocity to Holosoma. vx m/s | vy m/s | vyaw rad/s"""
    _ensure_sock().send_string(json.dumps({"vel": {"vx": vx, "vy": vy, "vyaw": vyaw}}))


def gradual_stop():
    """Smooth deceleration to zero over ~1 second."""
    s = _ensure_sock()
    for _ in range(STOP_ITERATIONS):
        s.send_string(json.dumps({"vel": {"vx": 0.0, "vy": 0.0, "vyaw": 0.0}}))
        time.sleep(STOP_DELAY)


def send_cmd(cmd: str):
    """Send Holosoma state command: start | walk | stand | stop"""
    _ensure_sock().send_string(json.dumps({"cmd": cmd}))


# Load MOVE_MAP from navigation config (pure data, safe at import time)
_nav = load_config("Navigation")
MOVE_MAP = {k: tuple(v) for k, v in _nav["move_map"].items()}
