import { ChatEvent } from '../../../shared-kernel';
import {
  P2P_TYPE_SCREEN_SHARE_REQUEST,
  P2P_TYPE_SCREEN_SHARE_STOP,
  P2P_TYPE_SCREEN_STATE
} from '../realtime.constants';

interface RemoteScreenShareRequestControllerDependencies {
  getConnectedPeerIds(): string[];
  sendToPeer(peerId: string, event: ChatEvent): void;
  clearRemoteScreenShareStream(peerId: string): void;
  requestScreenShareForPeer(peerId: string): void;
  stopScreenShareForPeer(peerId: string): void;
  clearScreenShareRequest(peerId: string): void;
}

export class RemoteScreenShareRequestController {
  private remoteScreenShareRequestsEnabled = false;
  private readonly desiredRemoteScreenSharePeers = new Set<string>();
  private readonly activeRemoteScreenSharePeers = new Set<string>();
  private readonly requestRetryTimers = new Map<string, ReturnType<typeof setTimeout>>();

  constructor(
    private readonly dependencies: RemoteScreenShareRequestControllerDependencies
  ) {}

  handlePeerConnected(peerId: string): void {
    if (this.remoteScreenShareRequestsEnabled && this.desiredRemoteScreenSharePeers.has(peerId)) {
      this.requestRemoteScreenShares([peerId]);
    }
  }

  handlePeerDisconnected(peerId: string): void {
    this.activeRemoteScreenSharePeers.delete(peerId);
    this.clearRequestRetry(peerId);
    this.dependencies.clearScreenShareRequest(peerId);
  }

  handlePeerControlMessage(event: ChatEvent): void {
    if (!event.fromPeerId) {
      return;
    }

    if (event.type === P2P_TYPE_SCREEN_STATE && event.isScreenSharing === false) {
      this.dependencies.clearRemoteScreenShareStream(event.fromPeerId);
      return;
    }

    if (event.type === P2P_TYPE_SCREEN_SHARE_REQUEST) {
      this.dependencies.requestScreenShareForPeer(event.fromPeerId);
      return;
    }

    if (event.type === P2P_TYPE_SCREEN_SHARE_STOP) {
      this.dependencies.stopScreenShareForPeer(event.fromPeerId);
    }
  }

  syncRemoteScreenShareRequests(peerIds: string[], enabled: boolean): void {
    const nextDesiredPeers = new Set(
      peerIds.filter((peerId): peerId is string => !!peerId)
    );

    if (!enabled) {
      this.remoteScreenShareRequestsEnabled = false;
      this.desiredRemoteScreenSharePeers.clear();
      this.clearAllRequestRetries();
      this.stopRemoteScreenShares([...this.activeRemoteScreenSharePeers]);
      return;
    }

    this.remoteScreenShareRequestsEnabled = true;

    for (const activePeerId of [...this.activeRemoteScreenSharePeers]) {
      if (!nextDesiredPeers.has(activePeerId)) {
        this.stopRemoteScreenShares([activePeerId]);
      }
    }

    this.desiredRemoteScreenSharePeers.clear();
    nextDesiredPeers.forEach((peerId) => this.desiredRemoteScreenSharePeers.add(peerId));
    this.requestRemoteScreenShares([...nextDesiredPeers]);
  }

  clear(): void {
    this.remoteScreenShareRequestsEnabled = false;
    this.desiredRemoteScreenSharePeers.clear();
    this.activeRemoteScreenSharePeers.clear();
    this.clearAllRequestRetries();
  }

  private requestRemoteScreenShares(peerIds: string[], retryAttempt = 0): void {
    const connectedPeerIds = new Set(this.dependencies.getConnectedPeerIds());

    for (const peerId of peerIds) {
      if (!connectedPeerIds.has(peerId)) {
        continue;
      }

      this.dependencies.sendToPeer(peerId, { type: P2P_TYPE_SCREEN_SHARE_REQUEST });
      this.activeRemoteScreenSharePeers.add(peerId);
      this.scheduleRequestRetry(peerId, retryAttempt);
    }
  }

  private stopRemoteScreenShares(peerIds: string[]): void {
    const connectedPeerIds = new Set(this.dependencies.getConnectedPeerIds());

    for (const peerId of peerIds) {
      if (this.activeRemoteScreenSharePeers.has(peerId) && connectedPeerIds.has(peerId)) {
        this.dependencies.sendToPeer(peerId, { type: P2P_TYPE_SCREEN_SHARE_STOP });
      }

      this.activeRemoteScreenSharePeers.delete(peerId);
      this.clearRequestRetry(peerId);
      this.dependencies.clearRemoteScreenShareStream(peerId);
    }
  }

  private scheduleRequestRetry(peerId: string, retryAttempt: number): void {
    if (!this.remoteScreenShareRequestsEnabled || !this.desiredRemoteScreenSharePeers.has(peerId)) {
      return;
    }

    const retryDelays = [
      300,
      1_000,
      2_500
    ];
    const delay = retryDelays[retryAttempt];

    if (delay === undefined) {
      return;
    }

    this.clearRequestRetry(peerId);
    this.requestRetryTimers.set(peerId, setTimeout(() => {
      this.requestRetryTimers.delete(peerId);
      this.requestRemoteScreenShares([peerId], retryAttempt + 1);
    }, delay));
  }

  private clearRequestRetry(peerId: string): void {
    const timer = this.requestRetryTimers.get(peerId);

    if (timer) {
      clearTimeout(timer);
      this.requestRetryTimers.delete(peerId);
    }
  }

  private clearAllRequestRetries(): void {
    for (const timer of this.requestRetryTimers.values()) {
      clearTimeout(timer);
    }

    this.requestRetryTimers.clear();
  }
}
