import { Audio } from 'expo-av';
import * as FileSystem from 'expo-file-system/legacy';
import Constants from 'expo-constants';
import { supabase } from './supabase';

/**
 * One-shot Lyra-TTS für Onboarding/Bubble-Playback.
 *
 * Vereinfachte Version des SosTtsQueue für Single-Sentence-Playback:
 *  - Kein Pre-Fetch, keine Queue, kein Streaming
 *  - Bei neuem play(): vorheriger Sound + Fetch wird abgebrochen
 *  - status-Callback für UI-Feedback (idle/loading/playing)
 *
 * Genutzt für Lyra-Bubble Accessibility (DiGA-relevant): User tappt Audio-
 * Button neben der Bubble → Text wird per /api/coach/speak gesprochen.
 *
 * Endpoint /api/coach/speak ist der Default-Provider — Backend pickt selbst
 * (Cartesia/ElevenLabs/Deepgram je nach Plan + Voice-Picker-Setting).
 */

const apiUrl = Constants.expoConfig?.extra?.apiUrl as string;

let currentSound: Audio.Sound | null = null;
let currentAbort: AbortController | null = null;

export type LyraSpeechStatus = 'idle' | 'loading' | 'playing';

export async function stopLyraSpeech(): Promise<void> {
  if (currentAbort) {
    currentAbort.abort();
    currentAbort = null;
  }
  if (currentSound) {
    try {
      await currentSound.unloadAsync();
    } catch {
      // ignore — sound might already be released
    }
    currentSound = null;
  }
}

export async function playLyraSpeech(
  text: string,
  locale: string,
  onStatus?: (status: LyraSpeechStatus) => void,
): Promise<void> {
  await stopLyraSpeech();
  onStatus?.('loading');

  const controller = new AbortController();
  currentAbort = controller;

  try {
    const session = (await supabase.auth.getSession()).data.session;
    if (!session?.access_token) {
      throw new Error('not_authenticated');
    }

    const res = await fetch(`${apiUrl}/api/coach/speak`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${session.access_token}`,
      },
      body: JSON.stringify({ text, locale, mode: 'coach' }),
      signal: controller.signal,
    });
    if (!res.ok || controller.signal.aborted) {
      onStatus?.('idle');
      return;
    }

    // Default-Provider liefert audio/mpeg als raw bytes
    const buffer = await res.arrayBuffer();
    if (controller.signal.aborted || buffer.byteLength === 0) {
      onStatus?.('idle');
      return;
    }

    // Bytes → Base64 (chunked damit String.fromCharCode nicht overflowt)
    const bytes = new Uint8Array(buffer);
    const chunks: string[] = [];
    const cs = 0x8000;
    for (let i = 0; i < bytes.length; i += cs) {
      chunks.push(
        String.fromCharCode(...bytes.subarray(i, Math.min(i + cs, bytes.length))),
      );
    }
    const base64 = btoa(chunks.join(''));

    const tmpPath = `${FileSystem.cacheDirectory}lyra-speech-${Date.now()}.mp3`;
    await FileSystem.writeAsStringAsync(tmpPath, base64, {
      encoding: FileSystem.EncodingType.Base64,
    });

    if (controller.signal.aborted) {
      onStatus?.('idle');
      return;
    }

    const { sound } = await Audio.Sound.createAsync({ uri: tmpPath });
    currentSound = sound;

    sound.setOnPlaybackStatusUpdate((status) => {
      if (!status.isLoaded) return;
      if (status.didJustFinish) {
        onStatus?.('idle');
        stopLyraSpeech().catch(() => {});
      }
    });

    await sound.playAsync();
    onStatus?.('playing');
  } catch (e) {
    if ((e as Error).name === 'AbortError') return;
    console.warn('[lyraSpeech] failed:', e);
    onStatus?.('idle');
  }
}
