import { HubConnection, HubConnectionBuilder, HubConnectionState } from '@microsoft/signalr';
import { interval, Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { SignalRHubOptions } from './signalr-hub-options';

export abstract class SignalrHub {
  private readonly _hubConnection: HubConnection;

  private _listenerCount = 0;

  get connected() {
    return this._hubConnection.state === HubConnectionState.Connected;
  }

  get connecting() {
    return this._hubConnection.state === HubConnectionState.Connecting;
  }

  get disconnected() {
    return this._hubConnection.state === HubConnectionState.Disconnected;
  }

  get disconnecting() {
    return this._hubConnection.state === HubConnectionState.Disconnecting;
  }

  get reconnecting() {
    return this._hubConnection.state === HubConnectionState.Reconnecting;
  }

  constructor(options: SignalRHubOptions) {
    let builder = new HubConnectionBuilder();

    builder = builder.withUrl(options.url, options.httpOptions);

    if (options?.automaticReconnect) {
      if (options.enableAutomaticReconnect) {
        builder = builder.withAutomaticReconnect(options.automaticReconnect as any);
      }
    }

    this._hubConnection = builder.build();

    interval(30000).subscribe(() => {
      this.checkConnection();
    });
  }

  async connect(): Promise<void> {
    if (this.connected || this.connecting || this.reconnecting) {
      return;
    }

    await this._hubConnection.start().catch((err) => console.error('SignalrHub.connect()', err));
  }

  disconnect(): Promise<void> {
    return this._hubConnection.stop().catch((err) => console.error('SignalrHub.stop()', err));
  }

  checkConnection() {
    if (this._listenerCount > 0 && this.disconnected) {
      this.connect();
    }
  }

  protected listen<TData>(methodName: string) {
    return new Observable<TData>((observer) => {
      const handler = (data) => {
        observer.next(data);
      };

      this._hubConnection.on(methodName, handler);
      this._listenerCount++;

      this.connect().catch((err) => console.error('SignalrHub.listen()', err));

      return () => {
        this._hubConnection.off(methodName, handler);
        this._listenerCount++;

        if (this._listenerCount <= 0) {
          this.disconnect();
        }
      };
    }).pipe(shareReplay(1));
  }
}
