declare const WebSocket: {
  new (url: string): WebSocket;
  prototype: WebSocket;
  readonly CONNECTING: 0;
  readonly OPEN: 1;
  readonly CLOSING: 2;
  readonly CLOSED: 3;
};

export interface KioskConnectionOptions {
  url: string;
  kioskId: string;
  onConnectionUpdate?: (isConnected: boolean) => void;
  maxRetries?: number;
  retryDelay?: number;
  debug?: boolean;
}

type EventCallback = (...args: any[]) => void;

export class KioskConnection {
  private ws: WebSocket | null = null;
  private isConnected = false;
  private eventHandlers: Map<string, Set<EventCallback>> = new Map();
  private retryCount = 0;
  private retryTimeout: NodeJS.Timeout | null = null;
  private readonly maxRetries: number;
  private readonly initialRetryDelay: number;
  private readonly debug: boolean;
  private isShuttingDown = false;

  constructor(private options: KioskConnectionOptions) {
    this.maxRetries = options.maxRetries ?? 5;
    this.initialRetryDelay = options.retryDelay ?? 1000;
    this.debug = options.debug ?? false;
  }

  private log(...args: any[]) {
    if (this.debug && !this.isShuttingDown) {
      console.log(...args);
    }
  }

  private logError(...args: any[]) {
    if (this.debug && !this.isShuttingDown) {
      console.error(...args);
    }
  }

  public connect() {
    if (this.ws) {
      this.disconnect();
    }

    try {
      this.log('Attempting to connect to:', this.options.url);
      const wsUrl = new URL(this.options.url);
      wsUrl.searchParams.set('kioskId', this.options.kioskId);

      this.ws = new WebSocket(wsUrl.toString());

      this.ws.onopen = () => {
        this.log('WebSocket connection established');
        this.isConnected = true;
        this.retryCount = 0;
        this.options.onConnectionUpdate?.(true);
        this.emit('connected');
      };

      this.ws.onclose = (event) => {
        this.log(`WebSocket closed with code ${event.code}`);
        this.handleDisconnect('Connection closed', event);
      };

      this.ws.onerror = (error) => {
        this.logError('WebSocket error:', error);
        this.handleDisconnect('Connection error', error);
      };

      this.ws.onmessage = (event) => {
        if (event.data === 'ping') {
          if (this.ws?.readyState === WebSocket.OPEN) {
            this.ws.send('pong');
            this.emit('ping');
          }
        } else {
          try {
            const data = JSON.parse(event.data.toString());
            this.emit('message', data);
          } catch (error) {
            this.logError('Error parsing message:', error);
          }
        }
      };
    } catch (error) {
      this.logError('Error creating WebSocket:', error);
      this.handleDisconnect('Failed to create connection', error);
    }
  }

  private handleDisconnect(reason: string, error: any) {
    if (!this.isConnected && this.retryCount > 0) return;

    this.isConnected = false;
    if (!this.isShuttingDown) {
      this.options.onConnectionUpdate?.(false);
      this.emit('disconnected', { reason, error });
    }

    if (this.retryTimeout) {
      clearTimeout(this.retryTimeout);
      this.retryTimeout = null;
    }

    if (!this.isShuttingDown && this.retryCount < this.maxRetries) {
      const delay = this.initialRetryDelay * Math.pow(2, this.retryCount);
      this.log(`Attempting to reconnect in ${delay}ms (attempt ${this.retryCount + 1}/${this.maxRetries})`);

      this.retryTimeout = setTimeout(() => {
        this.retryCount++;
        this.connect();
      }, delay);
    } else if (!this.isShuttingDown) {
      this.logError('Max retry attempts reached');
      this.emit('maxRetriesExceeded');
    }
  }

  public disconnect(): Promise<void> {
    return new Promise<void>((resolve) => {
      this.isShuttingDown = true;

      // Clear any pending retry attempts
      if (this.retryTimeout) {
        clearTimeout(this.retryTimeout);
        this.retryTimeout = null;
      }

      // If there's no WebSocket or it's already closed, cleanup and resolve immediately
      if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
        this.cleanup();
        resolve();
        return;
      }

      // Set up one-time close handler for cleanup and resolution
      const handleClose = () => {
        this.cleanup();
        resolve();
      };

      const currentWs = this.ws;
      currentWs.addEventListener('close', handleClose, { once: true });

      // If the socket is open or connecting, close it
      if (currentWs.readyState === WebSocket.OPEN || currentWs.readyState === WebSocket.CONNECTING) {
        currentWs.close();
      }
    });
  }

  private cleanup() {
    if (this.ws) {
      // Remove all event listeners
      this.ws.onopen = null;
      this.ws.onclose = null;
      this.ws.onerror = null;
      this.ws.onmessage = null;
    }

    this.ws = null;
    this.isConnected = false;
    this.retryCount = 0;

    if (!this.isShuttingDown) {
      this.options.onConnectionUpdate?.(false);
      this.emit('disconnected', { reason: 'Manual disconnect' });
    }
  }

  public on(event: string, callback: EventCallback) {
    if (!this.eventHandlers.has(event)) {
      this.eventHandlers.set(event, new Set());
    }
    this.eventHandlers.get(event)?.add(callback);
    return this;
  }

  private emit(event: string, ...args: any[]) {
    const handlers = this.eventHandlers.get(event);
    if (!handlers || this.isShuttingDown) return;

    handlers.forEach(callback => {
      try {
        callback(...args);
      } catch (error) {
        this.logError('Error in event handler:', error);
      }
    });
  }

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