import { ScreenShareQualityPreset, ScreenShareStartOptions } from '../../screen-share.config';
import { ELECTRON_ENTIRE_SCREEN_SOURCE_NAME } from '../../realtime.constants';
import { WebRTCLogger } from '../../logging/webrtc-logger';
import {
  DesktopSource,
  ElectronDesktopCaptureResult,
  ElectronDesktopMediaStreamConstraints,
  ElectronDesktopSourceSelection,
  ScreenShareElectronApi
} from './shared';

interface DesktopElectronScreenShareCaptureDependencies {
  getElectronApi(): ScreenShareElectronApi | null;
  getSelectDesktopSource(): ((
    sources: readonly DesktopSource[],
    options: { includeSystemAudio: boolean }
  ) => Promise<ElectronDesktopSourceSelection>) | undefined;
}

export class DesktopElectronScreenShareCapture {
  constructor(
    private readonly logger: WebRTCLogger,
    private readonly dependencies: DesktopElectronScreenShareCaptureDependencies
  ) {}

  isAvailable(): boolean {
    return !!this.dependencies.getElectronApi()?.getSources && !this.isLinuxElectron();
  }

  shouldSuppressRemotePlaybackDuringShare(
    includeSystemAudio: boolean,
    usingElectronDesktopCapture: boolean
  ): boolean {
    // Chromium display-media capture can use own-audio suppression on modern
    // builds. The Electron desktop-capturer fallback cannot, so keep the old
    // Windows mute behavior only for that fallback path.
    return includeSystemAudio && usingElectronDesktopCapture && this.isWindowsElectron();
  }

  async startCapture(
    options: ScreenShareStartOptions,
    preset: ScreenShareQualityPreset
  ): Promise<ElectronDesktopCaptureResult> {
    const electronApi = this.dependencies.getElectronApi();

    if (!electronApi?.getSources) {
      throw new Error('Electron desktop capture is unavailable.');
    }

    const sources = await electronApi.getSources();
    const selection = await this.resolveSourceSelection(sources, options.includeSystemAudio);
    // On Windows, electron-desktop loopback audio captures all system output
    // including the app's voice playback, creating echo for watchers or
    // requiring total voice muting for the sharer. The getDisplayMedia path
    // handles this correctly via restrictOwnAudio - if we fell back here,
    // share video only so voice chat stays functional.
    const effectiveIncludeSystemAudio = this.isWindowsElectron()
      ? false
      : selection.includeSystemAudio;
    const captureOptions = {
      ...options,
      includeSystemAudio: effectiveIncludeSystemAudio
    };

    if (!selection.source) {
      throw new Error('No desktop capture sources were available.');
    }

    this.logger.info('Selected Electron desktop source', {
      includeSystemAudio: effectiveIncludeSystemAudio,
      sourceId: selection.source.id,
      sourceName: selection.source.name
    });

    const constraints = this.buildConstraints(selection.source.id, captureOptions, preset);

    this.logger.info('desktopCapturer constraints', constraints);

    if (!navigator.mediaDevices?.getUserMedia) {
      throw new Error('navigator.mediaDevices.getUserMedia is not available (requires HTTPS or localhost).');
    }

    return {
      includeSystemAudio: effectiveIncludeSystemAudio,
      stream: await navigator.mediaDevices.getUserMedia(constraints)
    };
  }

  private async resolveSourceSelection(
    sources: DesktopSource[],
    includeSystemAudio: boolean
  ): Promise<ElectronDesktopSourceSelection> {
    const orderedSources = this.sortSources(sources);
    const defaultSource = orderedSources.find((source) => source.name === ELECTRON_ENTIRE_SCREEN_SOURCE_NAME)
      ?? orderedSources[0];

    if (orderedSources.length === 0) {
      throw new Error('No desktop capture sources were available.');
    }

    const selectDesktopSource = this.dependencies.getSelectDesktopSource();

    if (!this.isWindowsElectron() || orderedSources.length < 2 || !selectDesktopSource) {
      return {
        includeSystemAudio,
        source: defaultSource
      };
    }

    return await selectDesktopSource(orderedSources, { includeSystemAudio });
  }

  private sortSources(sources: DesktopSource[]): DesktopSource[] {
    return [...sources].sort((left, right) => {
      const weightDiff = this.getSourceWeight(left) - this.getSourceWeight(right);

      if (weightDiff !== 0) {
        return weightDiff;
      }

      return left.name.localeCompare(right.name);
    });
  }

  private getSourceWeight(source: DesktopSource): number {
    return source.name === ELECTRON_ENTIRE_SCREEN_SOURCE_NAME || source.id.startsWith('screen')
      ? 0
      : 1;
  }

  private buildConstraints(
    sourceId: string,
    options: ScreenShareStartOptions,
    preset: ScreenShareQualityPreset
  ): ElectronDesktopMediaStreamConstraints {
    const constraints: ElectronDesktopMediaStreamConstraints = {
      video: {
        mandatory: {
          chromeMediaSource: 'desktop',
          chromeMediaSourceId: sourceId,
          maxWidth: preset.width,
          maxHeight: preset.height,
          maxFrameRate: preset.frameRate
        }
      }
    };

    if (options.includeSystemAudio) {
      constraints.audio = {
        mandatory: {
          chromeMediaSource: 'desktop',
          chromeMediaSourceId: sourceId
        }
      };
    } else {
      constraints.audio = false;
    }

    return constraints;
  }

  private isLinuxElectron(): boolean {
    if (!this.dependencies.getElectronApi() || typeof navigator === 'undefined') {
      return false;
    }

    return /linux/i.test(`${navigator.userAgent} ${navigator.platform}`);
  }

  private isWindowsElectron(): boolean {
    if (!this.isAvailable() || typeof navigator === 'undefined') {
      return false;
    }

    return /win/i.test(`${navigator.userAgent} ${navigator.platform}`);
  }
}
