/**
 * OpenClaw Gateway WebSocket client.
 * Connects to the local OpenClaw gateway (ws://127.0.0.1:18789)
 * and provides a simple RPC interface.
 */
import WebSocket from 'ws';

interface RpcFrame {
  id: string;
  method: string;
  params?: unknown;
}

interface RpcResponse {
  id: string;
  result?: unknown;
  error?: { message: string };
}

export class OpenClawClient {
  private ws: WebSocket | null = null;
  private pending = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
  private readonly gatewayUrl: string;
  private readonly token: string;
  private msgCounter = 0;
  private connected = false;

  constructor(gatewayUrl: string, token: string) {
    this.gatewayUrl = gatewayUrl;
    this.token = token;
  }

  connect(): Promise<void> {
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(this.gatewayUrl, {
        headers: { Authorization: `Bearer ${this.token}` },
      });

      this.ws.once('open', () => {
        this.connected = true;
        resolve();
      });

      this.ws.once('error', (err) => {
        if (!this.connected) reject(err);
      });

      this.ws.on('message', (raw) => {
        try {
          const frame: RpcResponse = JSON.parse(raw.toString());
          const pending = this.pending.get(frame.id);
          if (!pending) return;
          this.pending.delete(frame.id);
          if (frame.error) {
            pending.reject(new Error(frame.error.message));
          } else {
            pending.resolve(frame.result);
          }
        } catch {
          // ignore malformed frames
        }
      });

      this.ws.on('close', () => {
        this.connected = false;
        // Reject all pending RPCs
        for (const [, p] of this.pending) {
          p.reject(new Error('OpenClaw gateway disconnected'));
        }
        this.pending.clear();
      });
    });
  }

  isConnected(): boolean {
    return this.connected && this.ws?.readyState === WebSocket.OPEN;
  }

  rpc(method: string, params?: unknown): Promise<unknown> {
    return new Promise((resolve, reject) => {
      if (!this.isConnected()) {
        reject(new Error('Not connected to OpenClaw gateway'));
        return;
      }
      const id = String(++this.msgCounter);
      const frame: RpcFrame = { id, method, params };
      this.pending.set(id, { resolve, reject });
      this.ws!.send(JSON.stringify(frame));

      // Timeout after 30s
      setTimeout(() => {
        if (this.pending.has(id)) {
          this.pending.delete(id);
          reject(new Error(`RPC timeout: ${method}`));
        }
      }, 30_000);
    });
  }

  close(): void {
    this.ws?.close();
  }
}
