"use client";

import { EventEmitter } from "events";

export interface RosMessage {
  op: string;
  topic?: string;
  type?: string;
  msg?: Record<string, unknown>;
  service?: string;
  args?: Record<string, unknown>;
  id?: string;
  result?: boolean;
  values?: Record<string, unknown>;
}

export interface ServiceRequest {
  service: string;
  args?: Record<string, unknown>;
}

export interface ServiceResponse {
  result: boolean;
  values?: Record<string, unknown>;
  error?: string;
}

export interface RobotStatus {
  connected: boolean;
  battery: number;
  position: { x: number; y: number; theta: number };
  joints: Record<string, number>;
  sensors: Record<string, unknown>;
  lastUpdate: Date;
}

export interface RobotActionExecution {
  id: string;
  actionId: string;
  pluginName: string;
  parameters: Record<string, unknown>;
  status: "pending" | "executing" | "completed" | "failed";
  startTime: Date;
  endTime?: Date;
  error?: string;
}

/**
 * A single frame in a multi-step joint animation.
 * @param joint_names  - NAO joint names to move (e.g. "LShoulderPitch")
 * @param joint_angles - Target angles in radians, one per joint_name
 * @param speed        - Movement speed as a fraction of max (0.0–1.0)
 * @param delay_after  - Milliseconds to wait after publishing this frame
 *                       before sending the next one. Defaults to 800 ms.
 */
export interface AnimationMovement {
  joint_names: string[];
  joint_angles: number[];
  speed?: number;
  delay_after?: number;
}

/**
 * Unified ROS WebSocket service for wizard interface
 * Manages connection to rosbridge and handles robot action execution
 */
export class WizardRosService extends EventEmitter {
  private ws: WebSocket | null = null;
  private url: string;
  private reconnectInterval = 3000;
  private reconnectTimer: NodeJS.Timeout | null = null;
  private messageId = 0;
  private isConnected = false;
  private connectionAttempts = 0;
  private maxReconnectAttempts = 5;
  private isConnecting = false;

  // Simulation mode
  private simulationMode: boolean;
  private simulationInterval: NodeJS.Timeout | null = null;

  // Robot state
  private robotStatus: RobotStatus = {
    connected: false,
    battery: 0,
    position: { x: 0, y: 0, theta: 0 },
    joints: {},
    sensors: {},
    lastUpdate: new Date(),
  };

  // Active action tracking
  private activeActions: Map<string, RobotActionExecution> = new Map();

  constructor(url: string = "ws://localhost:9090", simulationMode: boolean = false) {
    super();
    this.url = url;
    this.simulationMode = simulationMode || 
      (typeof window !== "undefined" && process.env.NEXT_PUBLIC_SIMULATION_MODE === "true");
  }

  /**
   * Check if running in simulation mode
   */
  isSimulationMode(): boolean {
    return this.simulationMode;
  }

  /**
   * Enable or disable simulation mode
   */
  setSimulationMode(enabled: boolean): void {
    this.simulationMode = enabled;
    if (!enabled && this.simulationInterval) {
      clearInterval(this.simulationInterval);
      this.simulationInterval = null;
    }
  }

  /**
   * Connect to ROS bridge WebSocket
   */
  async connect(): Promise<void> {
    // Simulation mode - fake connection
    if (this.simulationMode) {
      return this.connectSimulation();
    }

    return new Promise((resolve, reject) => {
      if (
        this.isConnected ||
        this.ws?.readyState === WebSocket.OPEN ||
        this.isConnecting
      ) {
        if (this.isConnected) resolve();
        return;
      }

      this.isConnecting = true;
      console.log(`[WizardROS] Connecting to ${this.url}`);
      this.ws = new WebSocket(this.url);

      const connectionTimeout = setTimeout(() => {
        if (this.ws?.readyState !== WebSocket.OPEN) {
          this.ws?.close();
          reject(new Error("Connection timeout"));
        }
      }, 10000);

      this.ws.onopen = () => {
        clearTimeout(connectionTimeout);
        console.log("[WizardROS] Connected successfully");
        this.isConnected = true;
        this.isConnecting = false;
        this.connectionAttempts = 0;
        this.clearReconnectTimer();

        // Subscribe to robot topics
        this.subscribeToRobotTopics();

        this.emit("connected");
        resolve();
      };

      this.ws.onmessage = (event) => {
        try {
          const message = JSON.parse(event.data) as RosMessage;
          this.handleMessage(message);
        } catch (error) {
          console.error("[WizardROS] Failed to parse message:", error);
        }
      };

      this.ws.onclose = (event) => {
        console.log(
          `[WizardROS] Connection closed: ${event.code} - ${event.reason}`,
        );
        this.isConnected = false;
        this.isConnecting = false;
        this.emit("disconnected");

        // Schedule reconnect if not manually closed
        if (
          event.code !== 1000 &&
          this.connectionAttempts < this.maxReconnectAttempts
        ) {
          this.scheduleReconnect();
        }
      };

      this.ws.onerror = (error) => {
        console.warn(
          "[WizardROS] WebSocket error (connection may be retried):",
          error,
        );
        clearTimeout(connectionTimeout);
        this.isConnecting = false;

        // Prevent unhandled error event if no listeners
        if (this.listenerCount("error") > 0) {
          this.emit("error", error);
        }
        reject(error);
      };
    });
  }

  /**
   * Disconnect from ROS bridge
   */
  disconnect(): void {
    this.clearReconnectTimer();

    if (this.simulationMode) {
      this.disconnectSimulation();
      return;
    }

    if (this.ws) {
      this.ws.close(1000, "Manual disconnect");
      this.ws = null;
    }

    this.isConnected = false;
    this.isConnecting = false;
    this.robotStatus.connected = false;
    this.emit("disconnected");
  }

  /**
   * Simulation mode connection - simulates robot responses
   */
  private async connectSimulation(): Promise<void> {
    console.log(`[WizardROS] SIMULATION MODE - Connecting to mock robot`);
    this.isConnected = true;
    this.isConnecting = false;
    this.connectionAttempts = 0;

    // Initialize mock robot state
    const mockStates = this.getMockJointStates();
    this.robotStatus = {
      connected: true,
      battery: 85,
      position: { x: 0, y: 0, theta: 0 },
      joints: mockStates.names.reduce((acc, name, i) => {
        acc[name] = mockStates.positions[i] ?? 0;
        return acc;
      }, {} as Record<string, number>),
      sensors: {},
      lastUpdate: new Date(),
    };

    // Start publishing simulated sensor data
    this.simulationInterval = setInterval(() => {
      this.publishSimulationData();
    }, 100);

    this.emit("connected");
    console.log(`[WizardROS] SIMULATION MODE - Connected to mock robot`);
  }

  /**
   * Simulation mode disconnection
   */
  private disconnectSimulation(): void {
    console.log(`[WizardROS] SIMULATION MODE - Disconnecting`);
    if (this.simulationInterval) {
      clearInterval(this.simulationInterval);
      this.simulationInterval = null;
    }
    this.isConnected = false;
    this.robotStatus.connected = false;
    this.emit("disconnected");
  }

  /**
   * Publish simulated sensor data
   */
  private publishSimulationData(): void {
    if (!this.simulationMode || !this.isConnected) return;

    const mockData = this.getMockJointStates();
    this.updateJointStates(mockData);
    this.robotStatus.battery = 85 + Math.random() * 2 - 1; // Slight variation
    this.robotStatus.sensors = {
      "/bumper": { left: false, right: false },
      "/hand_touch": { leftHand: false, rightHand: false },
      "/head_touch": { front: false, middle: false, rear: false },
      "/sonar/left": { range: 0.5 + Math.random() * 0.5 },
      "/sonar/right": { range: 0.5 + Math.random() * 0.5 },
    };
    this.robotStatus.lastUpdate = new Date();
    this.emit("robot_status_updated", this.robotStatus);
  }

  /**
   * Get mock joint states for simulation
   */
  private getMockJointStates(): { names: string[]; positions: number[] } {
    const names = [
      "HeadYaw", "HeadPitch",
      "LShoulderPitch", "LShoulderRoll", "LElbowYaw", "LElbowRoll", "LWristYaw", "LHand",
      "RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RElbowRoll", "RWristYaw", "RHand",
      "LHipYawPitch", "LHipRoll", "LHipPitch", "LKneePitch", "LAnklePitch", "LAnkleRoll",
      "RHipYawPitch", "RHipRoll", "RHipPitch", "RKneePitch", "RAnklePitch", "RAnkleRoll",
    ];
    const positions = names.map(() => (Math.random() - 0.5) * 0.1);
    return { names, positions };
  }

  /**
   * Execute action in simulation mode
   */
  private async executeSimulationAction(
    pluginName: string,
    actionId: string,
    parameters: Record<string, unknown>,
    actionConfig?: {
      topic: string;
      messageType: string;
      payloadMapping: {
        type: string;
        payload?: Record<string, unknown>;
        transformFn?: string;
      };
    },
  ): Promise<RobotActionExecution> {
    const executionId = `${pluginName}_${actionId}_${Date.now()}`;
    const execution: RobotActionExecution = {
      id: executionId,
      actionId,
      pluginName,
      parameters,
      status: "pending",
      startTime: new Date(),
    };

    this.activeActions.set(executionId, execution);
    this.emit("action_started", execution);

    try {
      execution.status = "executing";
      this.activeActions.set(executionId, execution);

      console.log(`[WizardROS] SIMULATION MODE - Executing ${actionId}:`, parameters);

      // If the action config carries a gesture_sequence payload, run the sim animation handler
      if (actionConfig?.payloadMapping?.payload) {
        const payload = actionConfig.payloadMapping.payload as { type?: string; movements?: AnimationMovement[] };
        if (payload.type === "gesture_sequence" && payload.movements?.length) {
          await this.executeSimulationAnimationSequence(payload.movements);
          execution.status = "completed";
          execution.endTime = new Date();
          this.emit("action_completed", execution);
          this.activeActions.set(executionId, execution);
          return execution;
        }
      }

      // Simulate action execution based on action type
      let duration = 500;

      if (actionId === "say_text" || actionId === "say_with_emotion" || actionConfig?.topic === "/speech") {
        const text = String(parameters.text || parameters.data || "Hello");
        const wordCount = text.split(/\s+/).filter(Boolean).length;
        duration = 1500 + Math.max(1000, wordCount * 300);
      } else if (actionId.includes("walk") || actionId === "stop_walking" || actionConfig?.topic === "/cmd_vel") {
        duration = 500;
        const speed = Number(parameters.speed) || 0.1;
        if (actionId === "walk_forward") {
          this.robotStatus.position.x += speed * 0.5;
        } else if (actionId === "walk_backward") {
          this.robotStatus.position.x -= speed * 0.5;
        } else if (actionId === "turn_left") {
          this.robotStatus.position.theta -= 0.5;
        } else if (actionId === "turn_right") {
          this.robotStatus.position.theta += 0.5;
        }
      } else if (actionConfig?.topic === "/joint_angles") {
        duration = 1000;
      } else if (actionId === "wake_up" || actionId === "rest" || actionId === "set_posture") {
        duration = 2000;
      }

      // Simulate async execution
      await new Promise((resolve) => setTimeout(resolve, duration));

      execution.status = "completed";
      execution.endTime = new Date();
      this.emit("action_completed", execution);
    } catch (error) {
      execution.status = "failed";
      execution.error = error instanceof Error ? error.message : String(error);
      execution.endTime = new Date();
      this.emit("action_failed", execution);
    }

    this.activeActions.set(executionId, execution);
    return execution;
  }



  /**
   * Check if connected to ROS bridge
   */
  getConnectionStatus(): boolean {
    if (this.simulationMode) {
      return this.isConnected;
    }
    return this.isConnected && this.ws?.readyState === WebSocket.OPEN;
  }

  /**
   * Get current robot status
   */
  getRobotStatus(): RobotStatus {
    return { ...this.robotStatus };
  }

  /**
   * Execute robot action using plugin configuration
   */
  async executeRobotAction(
    pluginName: string,
    actionId: string,
    parameters: Record<string, unknown>,
    actionConfig?: {
      topic: string;
      messageType: string;
      payloadMapping: {
        type: string;
        payload?: Record<string, unknown>;
        transformFn?: string;
      };
    },
  ): Promise<RobotActionExecution> {
    if (!this.isConnected) {
      throw new Error("Not connected to ROS bridge");
    }

    // Simulation mode - simulate action execution
    if (this.simulationMode) {
      return this.executeSimulationAction(pluginName, actionId, parameters, actionConfig);
    }

    const executionId = `${pluginName}_${actionId}_${Date.now()}`;
    const execution: RobotActionExecution = {
      id: executionId,
      actionId,
      pluginName,
      parameters,
      status: "pending",
      startTime: new Date(),
    };

    this.activeActions.set(executionId, execution);
    this.emit("action_started", execution);

    try {
      execution.status = "executing";
      this.activeActions.set(executionId, execution);

      // Execute based on action configuration or built-in mappings
      if (actionConfig) {
        await this.executeWithConfig(actionConfig, parameters, actionId);
      } else {
        await this.executeBuiltinAction(actionId, parameters);
      }

      execution.status = "completed";
      execution.endTime = new Date();
      this.emit("action_completed", execution);
    } catch (error) {
      execution.status = "failed";
      execution.error = error instanceof Error ? error.message : String(error);
      execution.endTime = new Date();
      this.emit("action_failed", execution);
    }

    this.activeActions.set(executionId, execution);
    return execution;
  }

  /**
   * Get list of active actions
   */
  getActiveActions(): RobotActionExecution[] {
    return Array.from(this.activeActions.values());
  }

  /**
   * Execute a multi-frame joint animation by sending each movement in sequence.
   * Each frame is published to /joint_angles then held for delay_after ms
   * (default 800 ms) before the next frame is sent.
   */
  async executeAnimationSequence(movements: AnimationMovement[]): Promise<void> {
    if (!movements.length) {
      console.warn("[WizardROS] executeAnimationSequence called with empty movements");
      return;
    }

    for (let i = 0; i < movements.length; i++) {
      const movement = movements[i]!;
      console.log(
        `[WizardROS] Animation frame ${i + 1}/${movements.length}:`,
        movement.joint_names,
        "→",
        movement.joint_angles,
      );

      this.publish(
        "/joint_angles",
        "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
        {
          joint_names: movement.joint_names,
          joint_angles: movement.joint_angles,
          speed: movement.speed ?? 0.3,
        },
      );

      // Always wait after each frame (including the last) so the caller
      // can await this method and know the animation has finished.
      const delayMs = movement.delay_after ?? 800;
      await new Promise<void>((resolve) => setTimeout(resolve, delayMs));
    }
  }

  /**
   * Execute a multi-frame joint animation in simulation mode (logs only).
   */
  private async executeSimulationAnimationSequence(
    movements: AnimationMovement[],
  ): Promise<void> {
    for (let i = 0; i < movements.length; i++) {
      const movement = movements[i]!;
      console.log(
        `[WizardROS] SIMULATION animation frame ${i + 1}/${movements.length}:`,
        movement.joint_names,
      );
      const delayMs = movement.delay_after ?? 800;
      await new Promise<void>((resolve) => setTimeout(resolve, delayMs));
    }
  }

  /**
   * Subscribe to robot sensor topics
   */
  private subscribeToRobotTopics(): void {
    console.log("[WizardROS] Setting up robot topics...");
    const topics = [
      { topic: "/joint_states", type: "sensor_msgs/JointState" },
      { topic: "/bumper", type: "naoqi_bridge_msgs/Bumper" },
      { topic: "/hand_touch", type: "naoqi_bridge_msgs/HandTouch" },
      { topic: "/head_touch", type: "naoqi_bridge_msgs/HeadTouch" },
      { topic: "/sonar/left", type: "sensor_msgs/Range" },
      { topic: "/sonar/right", type: "sensor_msgs/Range" },
    ];

    topics.forEach(({ topic, type }) => {
      this.subscribe(topic, type);
    });

    this.advertise("/speech", "std_msgs/String");
    this.advertise("/cmd_vel", "geometry_msgs/Twist");
    this.advertise("/joint_angles", "naoqi_bridge_msgs/msg/JointAnglesWithSpeed");
    this.advertise("/robot_pose", "geometry_msgs/Pose");
    this.advertise("/animation", "std_msgs/String");
  }

  /**
   * Subscribe to a ROS topic
   */
  private subscribe(topic: string, messageType: string): void {
    const message: RosMessage = {
      op: "subscribe",
      topic,
      type: messageType,
      id: `sub_${this.messageId++}`,
    };

    this.send(message);
  }

  /**
   * Advertise a ROS topic (declare the type before publishing)
   */
  private advertise(topic: string, messageType: string): void {
    console.log(`[WizardROS] Advertising topic ${topic} as ${messageType}`);
    const message: RosMessage = {
      op: "advertise",
      topic,
      type: messageType,
      id: `adv_${this.messageId++}`,
    };

    this.send(message);
  }

  /**
   * Publish message to ROS topic
   */
  private publish(
    topic: string,
    messageType: string,
    msg: Record<string, unknown>,
  ): void {
    console.log(`[WizardROS] Publishing to ${topic}:`, msg);
    const message: RosMessage = {
      op: "publish",
      topic,
      type: messageType,
      msg,
    };

    this.send(message);
  }

  /**
   * Send WebSocket message
   */
  private send(message: RosMessage): void {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message));
    } else {
      console.warn("[WizardROS] Cannot send message - not connected");
    }
  }

  /**
   * Handle incoming ROS messages
   */
  private handleMessage(message: RosMessage): void {
    if (message.topic) {
      this.handleTopicMessage(message);
    }

    this.emit("message", message);
  }

  /**
   * Handle topic-specific messages
   */
  private handleTopicMessage(message: RosMessage): void {
    if (!message.topic || !message.msg) return;

    switch (message.topic) {
      case "/joint_states":
        this.updateJointStates(message.msg);
        break;
      case "/naoqi_driver/battery":
        this.updateBatteryStatus(message.msg);
        break;
      case "/bumper":
      case "/hand_touch":
      case "/head_touch":
      case "/sonar/left":
      case "/sonar/right":
        this.updateSensorData(message.topic, message.msg);
        break;
    }

    this.robotStatus.lastUpdate = new Date();
    this.emit("robot_status_updated", this.robotStatus);
  }

  /**
   * Update joint states from ROS message
   */
  private updateJointStates(msg: Record<string, unknown>): void {
    if (
      msg.name &&
      msg.position &&
      Array.isArray(msg.name) &&
      Array.isArray(msg.position)
    ) {
      const joints: Record<string, number> = {};

      for (let i = 0; i < msg.name.length; i++) {
        const jointName = msg.name[i] as string;
        const position = msg.position[i] as number;
        if (jointName && typeof position === "number") {
          joints[jointName] = position;
        }
      }

      this.robotStatus.joints = joints;
      this.robotStatus.connected = true;
    }
  }

  /**
   * Update battery status from ROS message
   */
  private updateBatteryStatus(msg: Record<string, unknown>): void {
    if (typeof msg.percentage === "number") {
      this.robotStatus.battery = msg.percentage;
    }
  }

  /**
   * Update sensor data from ROS message
   */
  private updateSensorData(topic: string, msg: Record<string, unknown>): void {
    this.robotStatus.sensors[topic] = msg;
  }

  /**
   * Execute action with plugin configuration
   */
  private async executeWithConfig(
    config: {
      topic: string;
      messageType: string;
      payloadMapping: {
        type: string;
        payload?: Record<string, unknown>;
        transformFn?: string;
        service?: string;
        args?: Record<string, unknown>;
        sshCommand?: string;
      };
    },
    parameters: Record<string, unknown>,
    actionId?: string,
  ): Promise<void> {
    // SSH command actions
    if (config.payloadMapping.type === "ssh" && config.payloadMapping.sshCommand) {
      await this.executeSSHCommand(config.payloadMapping.sshCommand);
      return;
    }

    // Service-call actions — no topic publish involved
    if (config.payloadMapping.type === "service") {
      const service = config.payloadMapping.service ?? config.topic;
      const args = config.payloadMapping.args
        ? this.buildTemplatePayload(config.payloadMapping.args, parameters)
        : parameters;
      await this.callService(service, args);
      return;
    }

    let msg: Record<string, unknown>;

    if (
      (config.payloadMapping.type === "template" ||
        config.payloadMapping.type === "static") &&
      config.payloadMapping.payload
    ) {
      msg = this.buildTemplatePayload(config.payloadMapping.payload, parameters);
    } else if (config.payloadMapping.transformFn) {
      msg = this.applyTransformFunction(config.payloadMapping.transformFn, parameters);
    } else {
      msg = parameters;
    }

    // Delegate gesture_sequence payloads to the animation handler
    if ((msg as { type?: string }).type === "gesture_sequence") {
      const movements = (msg as { movements?: AnimationMovement[] }).movements;
      if (!movements?.length) {
        console.warn("[WizardROS] gesture_sequence payload has no movements");
        return;
      }
      console.log(`[WizardROS] Delegating to animation handler (${movements.length} frames)`);
      await this.executeAnimationSequence(movements);
      return;
    }

    // Route /animation topic through SSH instead of ROS to avoid crashes
    if (config.topic === "/animation" && actionId?.startsWith("play_animation_")) {
      await this.executeAnimationSSH(actionId);
      return;
    }

    this.publish(config.topic, config.messageType, msg);

    // Wait for action completion based on topic type
    if (config.topic === "/speech") {
      // Estimate speech duration based on text content
      const text =
        typeof msg === "object" && msg !== null && "data" in msg
          ? String((msg as any).data || "")
          : JSON.stringify(msg);
      const wordCount = text.split(/\s+/).filter(Boolean).length;
      // Emotion markup adds overhead: ~200ms per word base + emotion animation time
      const emotionOverhead = 1500; // Animation prep time
      const duration = emotionOverhead + Math.max(1000, wordCount * 300);
      console.log(
        `[WizardROS] Speech action estimated duration: ${duration}ms (${wordCount} words)`,
      );
      await new Promise((resolve) => setTimeout(resolve, duration));
    } else {
      // Short delay for non-speech actions
      await new Promise((resolve) => setTimeout(resolve, 500));
    }
  }

  /**
   * Execute built-in robot actions (robot-agnostic defaults)
   * These are generic actions that work with standard ROS topics
   */
  private async executeBuiltinAction(
    actionId: string,
    parameters: Record<string, unknown>,
  ): Promise<void> {
    switch (actionId) {
      case "say_text":
      case "say_with_emotion":
        const text = String(parameters.text || "Hello");
        this.publish("/speech", "std_msgs/String", { data: text });
        const wordCount = text.split(/\s+/).filter(Boolean).length;
        const emotion = String(parameters.emotion || "neutral");
        const emotionOverhead = 1500;
        const duration = emotionOverhead + Math.max(1000, wordCount * 300);
        console.log(
          `[WizardROS] Speech action (${actionId}) estimated: ${duration}ms`,
        );
        await new Promise((resolve) => setTimeout(resolve, duration));
        break;

      case "wave_goodbye":
        const waveText = String(parameters.text || "Goodbye");
        this.publish("/speech", "std_msgs/String", { data: waveText });
        await new Promise((resolve) => setTimeout(resolve, 3000));
        break;

      case "walk_forward":
        this.publish("/cmd_vel", "geometry_msgs/Twist", {
          linear: { x: Number(parameters.speed) || 0.1, y: 0, z: 0 },
          angular: { x: 0, y: 0, z: 0 },
        });
        await new Promise((resolve) => setTimeout(resolve, 500));
        break;

      case "walk_backward":
        this.publish("/cmd_vel", "geometry_msgs/Twist", {
          linear: { x: -(Number(parameters.speed) || 0.1), y: 0, z: 0 },
          angular: { x: 0, y: 0, z: 0 },
        });
        await new Promise((resolve) => setTimeout(resolve, 500));
        break;

      case "turn_left":
        this.publish("/cmd_vel", "geometry_msgs/Twist", {
          linear: { x: 0, y: 0, z: 0 },
          angular: { x: 0, y: 0, z: -(Number(parameters.speed) || 0.3) },
        });
        await new Promise((resolve) => setTimeout(resolve, 500));
        break;

      case "turn_right":
        this.publish("/cmd_vel", "geometry_msgs/Twist", {
          linear: { x: 0, y: 0, z: 0 },
          angular: { x: 0, y: 0, z: Number(parameters.speed) || 0.3 },
        });
        await new Promise((resolve) => setTimeout(resolve, 500));
        break;

      case "move_head":
      case "turn_head":
        this.publish(
          "/joint_angles",
          "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
          {
            joint_names: ["HeadYaw", "HeadPitch"],
            joint_angles: [
              Number(parameters.yaw) || 0,
              Number(parameters.pitch) || 0,
            ],
            speed: Number(parameters.speed) || 0.3,
          },
        );
        await new Promise((resolve) => setTimeout(resolve, 1000));
        break;

      case "move_arm": {
        const arm = String(parameters.arm || "right");
        const prefix = arm.toLowerCase() === "left" ? "L" : "R";
        this.publish(
          "/joint_angles",
          "naoqi_bridge_msgs/msg/JointAnglesWithSpeed",
          {
            joint_names: [
              `${prefix}ShoulderPitch`,
              `${prefix}ShoulderRoll`,
              `${prefix}ElbowYaw`,
              `${prefix}ElbowRoll`,
            ],
            joint_angles: [
              Number(parameters.shoulder_pitch) || 0,
              Number(parameters.shoulder_roll) || 0,
              Number(parameters.elbow_yaw) || 0,
              Number(parameters.elbow_roll) || 0,
            ],
            speed: Number(parameters.speed) || 0.3,
          },
        );
        await new Promise((resolve) => setTimeout(resolve, 1000));
        break;
      }

      case "wake_up":
        // Try known naoqi_driver2 service names in order
        for (const svc of [
          "/naoqi_driver/ALMotion/wakeUp",
          "/naoqi_driver/motion/wake_up",
          "/motion/wake_up",
        ]) {
          try {
            await this.callService(svc, {});
            console.log(`[WizardROS] wake_up succeeded via ${svc}`);
            break;
          } catch {
            // try next
          }
        }
        break;

      case "rest":
        for (const svc of [
          "/naoqi_driver/ALMotion/rest",
          "/naoqi_driver/motion/rest",
          "/motion/rest",
        ]) {
          try {
            await this.callService(svc, {});
            console.log(`[WizardROS] rest succeeded via ${svc}`);
            break;
          } catch {
            // try next
          }
        }
        break;

      case "emergency_stop":
        this.publish("/cmd_vel", "geometry_msgs/Twist", {
          linear: { x: 0, y: 0, z: 0 },
          angular: { x: 0, y: 0, z: 0 },
        });
        break;

      case "play_animation_bow":
      case "play_animation_hey":
      case "play_animation_show_floor":
      case "play_animation_enthusiastic":
      case "play_animation_yes":
      case "play_animation_no":
      case "play_animation_idontknow":
        await this.executeAnimationSSH(actionId);
        break;

      default:
        throw new Error(
          `Unknown action: ${actionId}. Define this action in your robot plugin.`,
        );
    }
  }

  /**
   * Call a ROS service
   */
  async callService(
    service: string,
    args: Record<string, unknown> = {},
  ): Promise<ServiceResponse> {
    if (!this.isConnected) {
      throw new Error("Not connected to ROS bridge");
    }

    // Simulation mode - return mock responses
    if (this.simulationMode) {
      console.log(`[WizardROS] SIMULATION MODE - Service call: ${service}`, args);

      const mockResponses: Record<string, ServiceResponse> = {
        "/naoqi_driver/get_robot_info": {
          result: true,
          values: {
            robotName: "MOCK-NAO6",
            robotVersion: "6.0",
            bodyType: "nao",
          },
        },
        "/naoqi_driver/get_joint_names": {
          result: true,
          values: {
            joint_names: [
              "HeadYaw", "HeadPitch", "LShoulderPitch", "LShoulderRoll",
              "LElbowYaw", "LElbowRoll", "LWristYaw", "LHand",
              "RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RElbowRoll",
              "RWristYaw", "RHand", "LHipYawPitch", "LHipRoll",
              "LHipPitch", "LKneePitch", "LAnklePitch", "LAnkleRoll",
              "RHipYawPitch", "RHipRoll", "RHipPitch", "RKneePitch",
              "RAnklePitch", "RAnkleRoll",
            ],
          },
        },
        "/naoqi_driver/get_position": {
          result: true,
          values: this.robotStatus.position,
        },
      };

      return mockResponses[service] || { result: true };
    }

    const id = `call_${this.messageId++}`;

    return new Promise((resolve, reject) => {
      const handleResponse = (message: RosMessage) => {
        if (message.op === "service_response" && message.id === id) {
          this.off("message", handleResponse);
          if (message.result === false) {
            resolve({
              result: false,
              error: String(message.values || "Service call failed"),
            });
          } else {
            resolve({ result: true, values: message.values });
          }
        }
      };

      this.on("message", handleResponse);

      this.send({
        op: "call_service",
        service,
        args,
        id,
      });

      setTimeout(() => {
        this.off("message", handleResponse);
        reject(new Error("Service call timed out"));
      }, 5000);
    });
  }

  /**
   * Execute animation via API route (SSH to robot)
   */
  private async executeAnimationSSH(actionId: string): Promise<void> {
    const animationMap: Record<string, string> = {
      "play_animation_bow": "animations/Stand/Gestures/BowShort_1",
      "play_animation_hey": "animations/Stand/Gestures/Hey_1",
      "play_animation_show_floor": "animations/Stand/Gestures/ShowFloor_1",
      "play_animation_enthusiastic": "animations/Stand/Gestures/Enthusiastic_4",
      "play_animation_yes": "animations/Stand/Gestures/Yes_1",
      "play_animation_no": "animations/Stand/Gestures/No_3",
      "play_animation_idontknow": "animations/Stand/Gestures/IDontKnow_1",
    };

    const animation = animationMap[actionId];
    if (!animation) {
      throw new Error(`Unknown animation: ${actionId}`);
    }

    console.log(`[WizardROS] Executing animation via API: ${animation}`);

    // Use executeSSH to run animation via qicli (bypasses studyId requirement)
    await this.executeSSHCommand(`qicli call ALAnimationPlayer.run '${animation}'`);

    console.log(`[WizardROS] Animation completed: ${animation}`);
  }

  /**
   * Execute an arbitrary SSH command via the API
   */
  private async executeSSHCommand(command: string): Promise<void> {
    console.log(`[WizardROS] Executing SSH command: ${command}`);

    const response = await fetch("/api/robots/command", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        action: "executeSSH",
        command,
      }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`SSH command failed: ${error}`);
    }

    console.log(`[WizardROS] SSH command completed: ${command}`);
  }

  /**
   * Set Autonomous Life state with fallbacks
   */
  async setAutonomousLife(enabled: boolean): Promise<boolean> {
    const desiredState = enabled ? "interactive" : "disabled";

    // List of services to try in order
    const attempts = [
      // Standard NaoQi Bridge pattern
      {
        service: "/naoqi_driver/ALAutonomousLife/setState",
        args: { state: desiredState },
      },
      {
        service: "/naoqi_driver/ALAutonomousLife/set_state",
        args: { state: desiredState },
      },
      // Direct module mapping
      {
        service: "/ALAutonomousLife/setState",
        args: { state: desiredState },
      },
      // Shortcuts/Aliases
      {
        service: "/naoqi_driver/set_autonomous_life",
        args: { state: desiredState },
      },
      {
        service: "/autonomous_life/set_state",
        args: { state: desiredState },
      },
      // Fallback: Enable/Disable topics/services
      {
        service: enabled ? "/life/enable" : "/life/disable",
        args: {},
      },
      // Last resort: Generic proxy call (if available)
      {
        service: "/naoqi_driver/function_call",
        args: {
          service: "ALAutonomousLife",
          function: "setState",
          args: [desiredState],
        },
      },
    ];

    console.log(`[WizardROS] Setting Autonomous Life to: ${desiredState}`);

    for (const attempt of attempts) {
      try {
        console.log(`[WizardROS] Trying service: ${attempt.service}`);
        const response = await this.callService(attempt.service, attempt.args);

        // If the service call didn't timeout (it resolved), check result
        if (response.result) {
          console.log(`[WizardROS] Success via ${attempt.service}`);
          return true;
        } else {
          // Resolved but failed? (e.g. internal error)
          console.warn(
            `[WizardROS] Service ${attempt.service} returned false result:`,
            response.error,
          );
        }
      } catch (error) {
        // Service call failed or timed out
        console.warn(
          `[WizardROS] Service ${attempt.service} failed/timeout:`,
          error,
        );
      }
    }

    console.error("[WizardROS] All Autonomous Life service attempts failed.");
    return false;
  }

  /**
   * Build template-based payload
   */
  private buildTemplatePayload(
    template: Record<string, unknown>,
    parameters: Record<string, unknown>,
  ): Record<string, unknown> {
    const result: Record<string, unknown> = {};

    for (const [key, value] of Object.entries(template)) {
      if (typeof value === "string" && value.includes("{{")) {
        // Template substitution
        let substituted = value;
        for (const [paramKey, paramValue] of Object.entries(parameters)) {
          const placeholder = `{{${paramKey}}}`;
          substituted = substituted.replace(
            new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
            String(paramValue ?? ""),
          );
        }
        result[key] = isNaN(Number(substituted))
          ? substituted
          : Number(substituted);
      } else if (typeof value === "object" && value !== null) {
        result[key] = this.buildTemplatePayload(
          value as Record<string, unknown>,
          parameters,
        );
      } else {
        result[key] = value;
      }
    }

    return result;
  }

  /**
   * Apply transform function for NAO6 actions
   */
  private applyTransformFunction(
    transformFn: string,
    parameters: Record<string, unknown>,
  ): Record<string, unknown> {
    switch (transformFn) {
      case "naoVelocityTransform":
        return {
          linear: {
            x: Number(parameters.linear) || 0,
            y: 0,
            z: 0,
          },
          angular: {
            x: 0,
            y: 0,
            z: Number(parameters.angular) || 0,
          },
        };

      case "naoSpeechTransform":
      case "transformToStringMessage":
        return {
          data: String(parameters.text || "Hello"),
        };

      case "naoHeadTransform":
      case "transformToHeadMovement":
        return {
          joint_names: ["HeadYaw", "HeadPitch"],
          joint_angles: [
            Number(parameters.yaw) || 0,
            Number(parameters.pitch) || 0,
          ],
          speed: Number(parameters.speed) || 0.3,
        };

      case "transformToJointAngles":
        return {
          joint_names: [String(parameters.joint_name || "HeadYaw")],
          joint_angles: [Number(parameters.angle) || 0],
          speed: Number(parameters.speed) || 0.2,
        };

      case "transformToEmotionalSpeech":
        return this.transformToEmotionalSpeech(parameters);

      case "transformToEmotionSpeech":
        return this.transformToEmotionalSpeech(parameters);

      case "transformToWaveGoodbye":
        return this.transformToWaveGoodbye(parameters);

      case "transformToAnimation":
        return this.transformToAnimation(parameters);

      case "transformToGestureSequence":
        return this.transformToGestureSequence(parameters);

      case "transformToWaveGoodbye":
        return this.transformToWaveGoodbye(parameters);

      default:
        console.warn(`Unknown transform function: ${transformFn}`);
        return parameters;
    }
  }

  /**
   * Transform parameters for emotional speech
   * NAOqi markup: \rspd=<speed>\<text>
   * Using pure speech modifiers without animations to avoid sound effects
   */
  private transformToEmotionalSpeech(parameters: Record<string, unknown>): {
    data: string;
  } {
    const text = String(parameters.text || "Hello");
    const emotion = String(parameters.emotion || "neutral");

    let markedText = text;

    switch (emotion) {
      case "happy":
        markedText = `\\rspd=120\\ ${text}`;
        break;
      case "sad":
        markedText = `\\rspd=80\\ ${text}`;
        break;
      case "neutral":
      default:
        markedText = text;
        break;
    }

    return { data: markedText };
  }

  /**
   * Transform for wave goodbye - speech without animation sound
   */
  private transformToWaveGoodbye(parameters: Record<string, unknown>): {
    data: string;
  } {
    const text = String(parameters.text || "Goodbye!");
    return { data: text };
  }

  /**
   * Transform for playing animations
   */
  private transformToAnimation(parameters: Record<string, unknown>): {
    data: string;
  } {
    const animation = String(parameters.animation || "Hey_1");
    const markedText = `^start(animations/Stand/Gestures/${animation})`;
    return { data: markedText };
  }

  /**
   * Transform for gesture sequences - sends multiple joint angle movements
   * Parameters: movements = [{joints: string[], angles: number[], duration: number}, ...]
   */
  private transformToGestureSequence(parameters: Record<string, unknown>): {
    type: string;
    movements: Array<{
      joint_names: string[];
      joint_angles: number[];
      speed: number;
    }>;
  } {
    const movements = parameters.movements as Array<{
      joints?: string[];
      angles?: number[];
      duration?: number;
      speed?: number;
    }>;

    if (!Array.isArray(movements)) {
      return { type: "gesture_sequence", movements: [] };
    }

    const parsedMovements = movements.map((m, index) => ({
      joint_names: m.joints || [],
      joint_angles: m.angles || [],
      speed: m.speed || (m.duration ? 1 / (m.duration / 1000) : 0.3),
    }));

    return { type: "gesture_sequence", movements: parsedMovements };
  }

  /**
   * Schedule reconnection attempt
   */
  private scheduleReconnect(): void {
    if (this.reconnectTimer) return;

    this.connectionAttempts++;
    console.log(
      `[WizardROS] Scheduling reconnect attempt ${this.connectionAttempts}/${this.maxReconnectAttempts}`,
    );

    this.reconnectTimer = setTimeout(async () => {
      this.reconnectTimer = null;
      try {
        await this.connect();
      } catch (error) {
        console.warn("[WizardROS] Reconnect failed:", error);
        if (this.connectionAttempts < this.maxReconnectAttempts) {
          this.scheduleReconnect();
        } else {
          this.emit("max_reconnects_reached");
        }
      }
    }, this.reconnectInterval);
  }

  /**
   * Clear reconnect timer
   */
  private clearReconnectTimer(): void {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }
}

// Global service instance
let wizardRosService: WizardRosService | null = null;
let isCreatingInstance = false;

/**
 * Get or create the global wizard ROS service (true singleton)
 */
export function getWizardRosService(simulationMode?: boolean): WizardRosService {
  // Prevent multiple instances during creation
  if (isCreatingInstance && !wizardRosService) {
    throw new Error("WizardRosService is being initialized, please wait");
  }

  if (!wizardRosService) {
    isCreatingInstance = true;
    try {
      const url = typeof window !== "undefined" 
        ? (process.env.NEXT_PUBLIC_ROS_BRIDGE_URL || "ws://localhost:9090")
        : "ws://localhost:9090";
      wizardRosService = new WizardRosService(url, simulationMode);
    } finally {
      isCreatingInstance = false;
    }
  }
  return wizardRosService;
}

/**
 * Initialize wizard ROS service with connection
 */
export async function initWizardRosService(simulationMode?: boolean): Promise<WizardRosService> {
  const service = getWizardRosService(simulationMode);

  if (simulationMode !== undefined) {
    service.setSimulationMode(simulationMode);
  }

  if (!service.getConnectionStatus()) {
    await service.connect();
  }

  return service;
}

/**
 * Reset the global wizard ROS service (useful for testing or reinitializing)
 */
export function resetWizardRosService(): void {
  if (wizardRosService) {
    wizardRosService.disconnect();
    wizardRosService = null;
  }
}
