/**
 * Voice synthesis manager for turn-by-turn navigation
 * Uses Web Speech API for voice instructions
 */

// ─── Types ────────────────────────────────────────────────────────────────────

export interface IVoiceConfig {
  enabled: boolean;
  language: string;
  rate: number;        // 0.1 - 10, default 1
  pitch: number;       // 0 - 2, default 1
  volume: number;      // 0 - 1, default 1
  voiceName?: string;  // Specific voice to use (optional)
}

export interface IVoiceQueueItem {
  text: string;
  priority: 'normal' | 'urgent';
}

// ─── VoiceSynthesisManager ────────────────────────────────────────────────────

/**
 * Manager for voice synthesis using Web Speech API
 * Provides queue-based speech with interrupt capability
 */
export class VoiceSynthesisManager {
  private config: IVoiceConfig = {
    enabled: true,
    language: 'en-US',
    rate: 1,
    pitch: 1,
    volume: 1,
  };

  private queue: IVoiceQueueItem[] = [];
  private isSpeaking: boolean = false;
  private currentUtterance: SpeechSynthesisUtterance | null = null;
  private availableVoices: SpeechSynthesisVoice[] = [];
  private voicesLoaded: boolean = false;

  constructor(config?: Partial<IVoiceConfig>) {
    if (config) {
      this.configure(config);
    }
    this.initVoices();
  }

  // ─── Configuration ──────────────────────────────────────────────────────────

  /**
   * Configure voice settings
   */
  public configure(config: Partial<IVoiceConfig>): void {
    this.config = { ...this.config, ...config };
  }

  /**
   * Get current configuration
   */
  public getConfig(): IVoiceConfig {
    return { ...this.config };
  }

  /**
   * Check if speech synthesis is supported
   */
  public isSupported(): boolean {
    return typeof window !== 'undefined' && 'speechSynthesis' in window;
  }

  /**
   * Enable voice synthesis
   */
  public enable(): void {
    this.config.enabled = true;
  }

  /**
   * Disable voice synthesis
   */
  public disable(): void {
    this.config.enabled = false;
    this.stop();
  }

  /**
   * Toggle voice synthesis
   */
  public toggle(): void {
    if (this.config.enabled) {
      this.disable();
    } else {
      this.enable();
    }
  }

  /**
   * Check if voice is enabled
   */
  public isEnabled(): boolean {
    return this.config.enabled;
  }

  // ─── Voice Selection ────────────────────────────────────────────────────────

  /**
   * Initialize available voices
   */
  private initVoices(): void {
    if (!this.isSupported()) return;

    // Voices may load asynchronously
    const loadVoices = () => {
      this.availableVoices = window.speechSynthesis.getVoices();
      this.voicesLoaded = this.availableVoices.length > 0;
    };

    loadVoices();

    // Some browsers need to wait for voiceschanged event
    if (window.speechSynthesis.onvoiceschanged !== undefined) {
      window.speechSynthesis.onvoiceschanged = loadVoices;
    }
  }

  /**
   * Get available voices
   */
  public getAvailableVoices(): SpeechSynthesisVoice[] {
    if (!this.isSupported()) return [];
    return window.speechSynthesis.getVoices();
  }

  /**
   * Get voices for a specific language
   */
  public getVoicesForLanguage(lang: string): SpeechSynthesisVoice[] {
    return this.getAvailableVoices().filter(voice =>
      voice.lang.startsWith(lang) || voice.lang.startsWith(lang.split('-')[0])
    );
  }

  /**
   * Find the best voice for current language
   */
  private findBestVoice(): SpeechSynthesisVoice | null {
    if (!this.voicesLoaded) {
      this.availableVoices = window.speechSynthesis.getVoices();
    }

    // If a specific voice is requested, try to find it
    if (this.config.voiceName) {
      const requestedVoice = this.availableVoices.find(
        v => v.name === this.config.voiceName
      );
      if (requestedVoice) return requestedVoice;
    }

    // Find a voice matching the language
    const langVoices = this.availableVoices.filter(
      v => v.lang.startsWith(this.config.language.split('-')[0])
    );

    // Prefer native/local voices, then default voices
    const nativeVoice = langVoices.find(v => v.localService);
    if (nativeVoice) return nativeVoice;

    const defaultVoice = langVoices.find(v => v.default);
    if (defaultVoice) return defaultVoice;

    // Return any matching voice
    return langVoices[0] || null;
  }

  // ─── Speech Methods ─────────────────────────────────────────────────────────

  /**
   * Speak text with normal priority (queued)
   */
  public speak(text: string): void {
    if (!this.config.enabled || !this.isSupported()) return;

    this.queue.push({ text, priority: 'normal' });
    this.processQueue();
  }

  /**
   * Speak text with urgent priority (interrupts current speech)
   */
  public speakUrgent(text: string): void {
    if (!this.config.enabled || !this.isSupported()) return;

    // Cancel current speech and clear queue
    this.stop();

    // Add to front of queue
    this.queue.unshift({ text, priority: 'urgent' });
    this.processQueue();
  }

  /**
   * Process the speech queue
   */
  private processQueue(): void {
    if (this.isSpeaking || this.queue.length === 0) return;
    if (!this.isSupported()) return;

    const item = this.queue.shift();
    if (!item) return;

    this.isSpeaking = true;

    const utterance = new SpeechSynthesisUtterance(item.text);
    this.currentUtterance = utterance;

    // Apply configuration
    utterance.lang = this.config.language;
    utterance.rate = this.config.rate;
    utterance.pitch = this.config.pitch;
    utterance.volume = this.config.volume;

    // Set voice if available
    const voice = this.findBestVoice();
    if (voice) {
      utterance.voice = voice;
    }

    // Event handlers
    utterance.onend = () => {
      this.isSpeaking = false;
      this.currentUtterance = null;
      this.processQueue();
    };

    utterance.onerror = (event) => {
      console.warn('[VoiceSynthesisManager] Speech error:', event.error);
      this.isSpeaking = false;
      this.currentUtterance = null;
      this.processQueue();
    };

    // Speak
    window.speechSynthesis.speak(utterance);
  }

  /**
   * Stop current speech and clear queue
   */
  public stop(): void {
    if (!this.isSupported()) return;

    window.speechSynthesis.cancel();
    this.queue = [];
    this.isSpeaking = false;
    this.currentUtterance = null;
  }

  /**
   * Pause current speech
   */
  public pause(): void {
    if (!this.isSupported()) return;
    window.speechSynthesis.pause();
  }

  /**
   * Resume paused speech
   */
  public resume(): void {
    if (!this.isSupported()) return;
    window.speechSynthesis.resume();
  }

  /**
   * Check if currently speaking
   */
  public isSpeakingNow(): boolean {
    return this.isSpeaking;
  }

  /**
   * Get queue length
   */
  public getQueueLength(): number {
    return this.queue.length;
  }

  // ─── Navigation-Specific Methods ────────────────────────────────────────────

  /**
   * Speak approach maneuver instruction
   * "In [distance], [maneuver] on/onto [street]"
   */
  public speakApproach(distance: string, maneuver: string, streetName?: string): void {
    let text = `In ${distance}, ${maneuver}`;
    if (streetName && streetName !== 'unnamed road') {
      // Use "on" for continue, "onto" for turns/merges
      const preposition = maneuver.startsWith('continue') ? 'on' : 'onto';
      text += ` ${preposition} ${streetName}`;
    }
    this.speak(text);
  }

  /**
   * Speak execute maneuver instruction (urgent - immediate action)
   * "[Maneuver] now" or just "[Maneuver]"
   */
  public speakManeuver(maneuver: string, urgent: boolean = true): void {
    const text = urgent ? `${maneuver} now` : maneuver;
    if (urgent) {
      this.speakUrgent(text);
    } else {
      this.speak(text);
    }
  }

  /**
   * Speak arrival
   */
  public speakArrival(): void {
    this.speakUrgent('You have arrived at your destination');
  }

  /**
   * Speak off-route warning
   */
  public speakOffRoute(): void {
    this.speakUrgent('You are off route. Recalculating.');
  }

  /**
   * Speak route recalculated
   */
  public speakRecalculated(): void {
    this.speak('Route recalculated');
  }
}
