import {
  Injectable,
  computed,
  signal
} from '@angular/core';
import { loadVoiceSettingsFromStorage, saveVoiceSettingsToStorage } from '../../../voice-session';
import { ELECTRON_ENTIRE_SCREEN_SOURCE_NAME } from '../../domain/constants/screen-share.constants';

export type ScreenShareSourceKind = 'screen' | 'window';

export interface ScreenShareSourceOption {
  id: string;
  kind: ScreenShareSourceKind;
  name: string;
  thumbnail: string;
}

export interface ScreenShareSourceSelection {
  includeSystemAudio: boolean;
  source: ScreenShareSourceOption;
}

interface ScreenShareSourcePickerRequest {
  includeSystemAudio: boolean;
  sources: readonly ScreenShareSourceOption[];
}

@Injectable({ providedIn: 'root' })
export class ScreenShareSourcePickerService {
  readonly request = computed(() => this._request());

  private readonly _request = signal<ScreenShareSourcePickerRequest | null>(null);

  private pendingResolve: ((selection: ScreenShareSourceSelection) => void) | null = null;
  private pendingReject: ((reason?: unknown) => void) | null = null;

  open(
    sources: readonly Pick<ScreenShareSourceOption, 'id' | 'name' | 'thumbnail'>[],
    initialIncludeSystemAudio = loadVoiceSettingsFromStorage().includeSystemAudio
  ): Promise<ScreenShareSourceSelection> {
    if (sources.length === 0) {
      throw new Error('No desktop capture sources were available.');
    }

    this.cancelPendingRequest();

    const normalizedSources = sources.map((source) => {
      const kind = this.getSourceKind(source);

      return {
        ...source,
        kind,
        name: this.getSourceDisplayName(source.name, kind)
      };
    });

    this._request.set({
      includeSystemAudio: initialIncludeSystemAudio,
      sources: normalizedSources
    });

    return new Promise<ScreenShareSourceSelection>((resolve, reject) => {
      this.pendingResolve = resolve;
      this.pendingReject = reject;
    });
  }

  confirm(sourceId: string, includeSystemAudio: boolean): void {
    const activeRequest = this._request();
    const source = activeRequest?.sources.find((entry) => entry.id === sourceId);
    const resolve = this.pendingResolve;

    if (!source || !resolve) {
      return;
    }

    this.clearPendingRequest();
    saveVoiceSettingsToStorage({ includeSystemAudio });
    resolve({
      includeSystemAudio,
      source
    });
  }

  cancel(): void {
    this.cancelPendingRequest();
  }

  private cancelPendingRequest(): void {
    const reject = this.pendingReject;

    this.clearPendingRequest();

    if (reject) {
      reject(this.createAbortError());
    }
  }

  private clearPendingRequest(): void {
    this._request.set(null);
    this.pendingResolve = null;
    this.pendingReject = null;
  }

  private getSourceKind(
    source: Pick<ScreenShareSourceOption, 'id' | 'name'>
  ): ScreenShareSourceKind {
    return source.id.startsWith('screen') || source.name === ELECTRON_ENTIRE_SCREEN_SOURCE_NAME
      ? 'screen'
      : 'window';
  }

  private getSourceDisplayName(name: string, kind: ScreenShareSourceKind): string {
    const trimmedName = name.trim();

    if (trimmedName) {
      return trimmedName;
    }

    return kind === 'screen' ? 'Entire screen' : 'Window';
  }

  private createAbortError(): Error {
    if (typeof DOMException !== 'undefined') {
      return new DOMException('The user aborted a request.', 'AbortError');
    }

    const error = new Error('The user aborted a request.');

    error.name = 'AbortError';

    return error;
  }
}
