import { Audio, AVPlaybackStatus, InterruptionModeAndroid, InterruptionModeIOS } from "expo-av";
import { logger } from "../../utils/logger";

let sound: Audio.Sound | null = null;
let playbackWaiter: {
  resolve: () => void;
  reject: (error: Error) => void;
} | null = null;

async function preparePlaybackMode(): Promise<void> {
  // Tras grabar, Android puede quedar en ruta de auricular / modo mic.
  // Forzamos altavoz y salimos del modo grabación antes de reproducir TTS.
  await Audio.setAudioModeAsync({
    allowsRecordingIOS: false,
    playsInSilentModeIOS: true,
    playThroughEarpieceAndroid: false,
    staysActiveInBackground: false,
    shouldDuckAndroid: true,
    interruptionModeIOS: InterruptionModeIOS.DuckOthers,
    interruptionModeAndroid: InterruptionModeAndroid.DuckOthers,
  });
}

function settlePlayback(error?: Error): void {
  const waiter = playbackWaiter;
  playbackWaiter = null;
  if (!waiter) return;
  if (error) waiter.reject(error);
  else waiter.resolve();
}

async function releaseSound(): Promise<void> {
  if (!sound) return;
  const current = sound;
  sound = null;
  try {
    current.setOnPlaybackStatusUpdate(null);
    const status = await current.getStatusAsync();
    if (status.isLoaded) {
      if (status.isPlaying) {
        await current.stopAsync();
      }
      await current.unloadAsync();
    }
  } catch {
    // ignore cleanup races
  }
}

export async function playAudio(
  url: string,
  onFinished?: () => void,
): Promise<void> {
  await stopAudio();
  await preparePlaybackMode();

  logger.info("Playing audio", url);

  const { sound: created } = await Audio.Sound.createAsync(
    { uri: url },
    { shouldPlay: true, volume: 1.0, isLooping: false },
  );
  sound = created;
  await sound.setVolumeAsync(1.0);

  await new Promise<void>((resolve, reject) => {
    playbackWaiter = { resolve, reject };

    sound?.setOnPlaybackStatusUpdate((status: AVPlaybackStatus) => {
      if (!status.isLoaded) {
        if ("error" in status && status.error) {
          logger.error("Playback error", status.error);
          void releaseSound().finally(() => {
            onFinished?.();
            settlePlayback(new Error(String(status.error)));
          });
        }
        return;
      }
      if (status.didJustFinish) {
        logger.info("Playback finished");
        void releaseSound().finally(() => {
          onFinished?.();
          settlePlayback();
        });
      }
    });

    void sound?.playAsync().catch((err) => {
      void releaseSound().finally(() => {
        onFinished?.();
        settlePlayback(err instanceof Error ? err : new Error(String(err)));
      });
    });
  });
}

export async function stopAudio(): Promise<void> {
  await releaseSound();
  settlePlayback();
}
