import { openWebSocket } from './web-socket';
import { runnerDebug } from './debug';

const debug = runnerDebug.extend('bidi');

export interface BidiConnection extends Disposable {
  send<T>(method: string, params: any, timeout?: number): Promise<T>;
  close(): void;
}

export async function createBidiConnection(
  baseUrl: string,
): Promise<BidiConnection> {
  const url = new URL('/session', baseUrl);
  debug('Connecting to BiDi server @', url.href);

  const webSocket = await openWebSocket(url.href);
  debug('Connected');

  let requestId = 0;

  return {
    send(method, params, timeout = 10e3) {
      const id = ++requestId;
      const command = { id, method, params };
      debug('Sending command:', command);

      return new Promise((resolve, reject) => {
        const cleanup = () => {
          webSocket.removeEventListener('message', onMessage);
          webSocket.removeEventListener('error', onError);
        };

        setTimeout(() => {
          cleanup();
          reject(
            new Error(
              `Timed out after ${timeout}ms waiting for ${method} response`,
            ),
          );
        }, timeout);

        const onMessage = (event: MessageEvent) => {
          const data = JSON.parse(event.data);
          if (data.id === id) {
            debug('Received response:', data);
            cleanup();
            if (data.type === 'success') resolve(data.result);
            else reject(Error(data.message, { cause: data }));
          }
        };
        const onError = (error: any) => {
          cleanup();
          reject(new Error('Error sending request', { cause: error }));
        };

        webSocket.addEventListener('message', onMessage);
        webSocket.addEventListener('error', onError);

        webSocket.send(JSON.stringify(command));
      });
    },

    close() {
      debug('Closing connection...');
      webSocket.close();
      debug('Closed connection');
    },
    [Symbol.dispose]() {
      debug('Disposing connection...');
      webSocket.close();
      debug('Disposed connection');
    },
  };
}
