import {
  Signal,
  computed,
  signal
} from '@angular/core';
import { v4 as uuidv4 } from 'uuid';
import type { LocalScreenShareState } from '../media/screen-share.manager';

export class WebRtcStateController {
  readonly peerId: Signal<string>;
  readonly isConnected: Signal<boolean>;
  readonly hasEverConnected: Signal<boolean>;
  readonly isVoiceConnected: Signal<boolean>;
  readonly connectedPeers: Signal<string[]>;
  readonly isMuted: Signal<boolean>;
  readonly isDeafened: Signal<boolean>;
  readonly isCameraEnabled: Signal<boolean>;
  readonly isScreenSharing: Signal<boolean>;
  readonly isNoiseReductionEnabled: Signal<boolean>;
  readonly screenStream: Signal<MediaStream | null>;
  readonly isScreenShareRemotePlaybackSuppressed: Signal<boolean>;
  readonly forceDefaultRemotePlaybackOutput: Signal<boolean>;
  readonly hasConnectionError: Signal<boolean>;
  readonly connectionErrorMessage: Signal<string | null>;
  readonly shouldShowConnectionError: Signal<boolean>;
  readonly peerLatencies: Signal<ReadonlyMap<string, number>>;

  private activeServerId: string | null = null;
  private readonly _localPeerId = signal<string>(uuidv4());
  private readonly _isSignalingConnected = signal(false);
  private readonly _isVoiceConnected = signal(false);
  private readonly _connectedPeers = signal<string[]>([]);
  private readonly _isMuted = signal(false);
  private readonly _isDeafened = signal(false);
  private readonly _isCameraEnabled = signal(false);
  private readonly _isScreenSharing = signal(false);
  private readonly _isNoiseReductionEnabled = signal(false);
  private readonly _screenStreamSignal = signal<MediaStream | null>(null);
  private readonly _isScreenShareRemotePlaybackSuppressed = signal(false);
  private readonly _forceDefaultRemotePlaybackOutput = signal(false);
  private readonly _hasConnectionError = signal(false);
  private readonly _connectionErrorMessage = signal<string | null>(null);
  private readonly _hasEverConnected = signal(false);
  private readonly _peerLatencies = signal<ReadonlyMap<string, number>>(new Map());

  constructor() {
    this.peerId = computed(() => this._localPeerId());
    this.isConnected = computed(() => this._isSignalingConnected());
    this.hasEverConnected = computed(() => this._hasEverConnected());
    this.isVoiceConnected = computed(() => this._isVoiceConnected());
    this.connectedPeers = computed(() => this._connectedPeers());
    this.isMuted = computed(() => this._isMuted());
    this.isDeafened = computed(() => this._isDeafened());
    this.isCameraEnabled = computed(() => this._isCameraEnabled());
    this.isScreenSharing = computed(() => this._isScreenSharing());
    this.isNoiseReductionEnabled = computed(() => this._isNoiseReductionEnabled());
    this.screenStream = computed(() => this._screenStreamSignal());
    this.isScreenShareRemotePlaybackSuppressed = computed(() => this._isScreenShareRemotePlaybackSuppressed());
    this.forceDefaultRemotePlaybackOutput = computed(() => this._forceDefaultRemotePlaybackOutput());
    this.hasConnectionError = computed(() => this._hasConnectionError());
    this.connectionErrorMessage = computed(() => this._connectionErrorMessage());
    this.shouldShowConnectionError = computed(() => {
      if (!this._hasConnectionError())
        return false;

      if (this._isVoiceConnected() && this._connectedPeers().length > 0)
        return false;

      return true;
    });

    this.peerLatencies = computed(() => this._peerLatencies());
  }

  get currentServerId(): string | null {
    return this.activeServerId;
  }

  getLocalPeerId(): string {
    return this._localPeerId();
  }

  isSignalingConnected(): boolean {
    return this._isSignalingConnected();
  }

  isVoiceConnectedActive(): boolean {
    return this._isVoiceConnected();
  }

  isScreenSharingActive(): boolean {
    return this._isScreenSharing();
  }

  isCameraEnabledActive(): boolean {
    return this._isCameraEnabled();
  }

  setCurrentServer(serverId: string): void {
    this.activeServerId = serverId;
  }

  setVoiceConnected(connected: boolean): void {
    this._isVoiceConnected.set(connected);
  }

  setMuted(muted: boolean): void {
    this._isMuted.set(muted);
  }

  setDeafened(deafened: boolean): void {
    this._isDeafened.set(deafened);
  }

  setCameraEnabled(enabled: boolean): void {
    this._isCameraEnabled.set(enabled);
  }

  setNoiseReductionEnabled(enabled: boolean): void {
    this._isNoiseReductionEnabled.set(enabled);
  }

  setConnectionError(message: string | null): void {
    this._hasConnectionError.set(!!message);
    this._connectionErrorMessage.set(message);
  }

  clearConnectionError(): void {
    this._hasConnectionError.set(false);
    this._connectionErrorMessage.set(null);
  }

  setConnectedPeers(peers: string[]): void {
    this._connectedPeers.set(peers);
  }

  syncPeerLatencies(latencies: ReadonlyMap<string, number>): void {
    this._peerLatencies.set(new Map(latencies));
  }

  applyLocalScreenShareState(state: LocalScreenShareState): void {
    this._isScreenSharing.set(state.active);
    this._screenStreamSignal.set(state.stream);
    this._isScreenShareRemotePlaybackSuppressed.set(state.suppressRemotePlayback);
    this._forceDefaultRemotePlaybackOutput.set(state.forceDefaultRemotePlaybackOutput);
  }

  clearPeerViewState(): void {
    this._connectedPeers.set([]);
    this._peerLatencies.set(new Map());
  }

  clearScreenShareState(): void {
    this._isScreenSharing.set(false);
    this._screenStreamSignal.set(null);
    this._isScreenShareRemotePlaybackSuppressed.set(false);
    this._forceDefaultRemotePlaybackOutput.set(false);
  }

  resetConnectionState(): void {
    this._isSignalingConnected.set(false);
    this._hasEverConnected.set(false);
    this._hasConnectionError.set(false);
    this._connectionErrorMessage.set(null);
  }

  updateSignalingConnectionStatus(anyConnected: boolean, markHasEverConnected: boolean, errorMessage?: string): void {
    if (markHasEverConnected) {
      this._hasEverConnected.set(true);
    }

    this._isSignalingConnected.set(anyConnected);
    this._hasConnectionError.set(!anyConnected);
    this._connectionErrorMessage.set(anyConnected ? null : (errorMessage ?? 'network.signaling.disconnected'));
  }
}
