/**
 * useRosbridge.js — React hook for ROS2 rosbridge_server WebSocket connection.
 *
 * rosbridge_server default: ws://<robot-ip>:9090
 * Provides subscribe/publish/callService helpers bound to the active connection.
 */

import { useState, useEffect, useRef, useCallback } from 'react';
import ROSLIB from 'roslib';

export function useRosbridge(url) {
  const [connected, setConnected] = useState(false);
  const [error, setError] = useState(null);
  const rosRef = useRef(null);
  const subscribersRef = useRef(new Map()); // topic -> ROSLIB.Topic

  useEffect(() => {
    if (!url) return;

    const ros = new ROSLIB.Ros({ url });
    rosRef.current = ros;

    ros.on('connection', () => {
      setConnected(true);
      setError(null);
    });

    ros.on('error', (err) => {
      setError(err?.toString() || 'Connection error');
    });

    ros.on('close', () => {
      setConnected(false);
    });

    return () => {
      subscribersRef.current.forEach((topic) => topic.unsubscribe());
      subscribersRef.current.clear();
      ros.close();
      rosRef.current = null;
    };
  }, [url]);

  /** Subscribe to a ROS2 topic. Returns an unsubscribe function. */
  const subscribe = useCallback((name, messageType, callback) => {
    if (!rosRef.current) return () => {};

    const key = `${name}::${messageType}`;
    if (subscribersRef.current.has(key)) {
      subscribersRef.current.get(key).unsubscribe();
    }

    const topic = new ROSLIB.Topic({
      ros: rosRef.current,
      name,
      messageType,
    });
    topic.subscribe(callback);
    subscribersRef.current.set(key, topic);

    return () => {
      topic.unsubscribe();
      subscribersRef.current.delete(key);
    };
  }, []);

  /** Publish a single message to a ROS2 topic. */
  const publish = useCallback((name, messageType, data) => {
    if (!rosRef.current) return;
    const topic = new ROSLIB.Topic({
      ros: rosRef.current,
      name,
      messageType,
    });
    topic.publish(new ROSLIB.Message(data));
  }, []);

  /** Call a ROS2 service. Returns a Promise resolving to the response. */
  const callService = useCallback((name, serviceType, request = {}) => {
    return new Promise((resolve, reject) => {
      if (!rosRef.current) {
        reject(new Error('Not connected'));
        return;
      }
      const svc = new ROSLIB.Service({
        ros: rosRef.current,
        name,
        serviceType,
      });
      svc.callService(new ROSLIB.ServiceRequest(request), resolve, reject);
    });
  }, []);

  /** Set ROS2 node parameters via rcl_interfaces/srv/SetParameters. */
  const setParam = useCallback((nodeName, params) => {
    // params: { name: string, type: 'bool'|'int'|'double'|'string', value: any }[]
    const TYPE_MAP = { bool: 1, integer: 2, double: 3, string: 4, int: 2 };
    const parameters = params.map(({ name, type, value }) => {
      const typeInt = TYPE_MAP[type] ?? 4;
      const valueKey = {
        1: 'bool_value',
        2: 'integer_value',
        3: 'double_value',
        4: 'string_value',
      }[typeInt];
      return { name, value: { type: typeInt, [valueKey]: value } };
    });
    return callService(
      `/${nodeName}/set_parameters`,
      'rcl_interfaces/srv/SetParameters',
      { parameters }
    );
  }, [callService]);

  return { connected, error, subscribe, publish, callService, setParam };
}
