import type { SidecarCommand, SidecarEvent } from './protocol';
import type { SidecarRegistration, RegistrationAck } from './registration-protocol';

type AnyCommand = SidecarCommand;
type AnyEvent = SidecarEvent;

type SidecarConnectorConfig = {
  apiUrl: string; // ws://127.0.0.1:9000/api/sidecar/register
  name: string;
  capabilities: string[];
  onCommand: (cmd: AnyCommand, reply: (msg: AnyEvent) => void) => void;
  onConnected?: () => void;
  onDisconnected?: () => void;
};

type SidecarConnection = {
  send: (msg: AnyEvent) => void;
  destroy: () => void;
  isConnected: () => boolean;
};

const RECONNECT_DELAYS = [200, 500, 1000, 2000, 4000, 8000, 15000];

export function createSidecarConnector(config: SidecarConnectorConfig): SidecarConnection {
  let ws: WebSocket | null = null;
  let connected = false;
  let reconnectAttempt = 0;
  let reconnectTimer: Timer | null = null;
  let destroyed = false;
  let sidecarId: string | null = null;

  function connect() {
    if (destroyed) return;
    if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) return;

    try {
      ws = new WebSocket(config.apiUrl);
    } catch {
      scheduleReconnect();
      return;
    }

    ws.onopen = () => {
      reconnectAttempt = 0;

      // Send registration
      const registration: SidecarRegistration = {
        type: 'register',
        name: config.name,
        capabilities: config.capabilities,
      };
      ws!.send(JSON.stringify(registration));
    };

    ws.onmessage = (event) => {
      try {
        const msg = JSON.parse(event.data as string);

        // Handle registration ack
        if (msg.type === 'registered') {
          const ack = msg as RegistrationAck;
          sidecarId = ack.id;
          connected = true;
          console.log(`[sidecar] registered with API server (id=${sidecarId})`);
          config.onConnected?.();
          return;
        }

        // Handle commands from API server
        const reply = (response: AnyEvent) => {
          if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify(response));
          }
        };
        config.onCommand(msg as AnyCommand, reply);
      } catch {
        // skip malformed messages
      }
    };

    ws.onclose = () => {
      const wasConnected = connected;
      connected = false;
      sidecarId = null;
      ws = null;
      if (wasConnected) {
        console.log('[sidecar] disconnected from API server');
        config.onDisconnected?.();
      }
      scheduleReconnect();
    };

    ws.onerror = () => {
      // onclose will fire after this
    };
  }

  function scheduleReconnect() {
    if (destroyed || reconnectTimer) return;
    const delay = RECONNECT_DELAYS[Math.min(reconnectAttempt, RECONNECT_DELAYS.length - 1)]!;
    reconnectAttempt++;
    reconnectTimer = setTimeout(() => {
      reconnectTimer = null;
      connect();
    }, delay);
  }

  function send(msg: AnyEvent): void {
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(msg));
    }
  }

  function destroy(): void {
    destroyed = true;
    if (reconnectTimer) {
      clearTimeout(reconnectTimer);
      reconnectTimer = null;
    }
    if (ws) {
      ws.close();
      ws = null;
    }
    connected = false;
  }

  // Start connecting
  connect();

  return {
    send,
    destroy,
    isConnected: () => connected,
  };
}
