// audio_probe.rs — Validación de archivos de audio con symphonia.
//
// Usado por playlist-refill para descartar archivos cuyo contenedor está
// intacto (ffprobe les saca duración) pero cuyos paquetes de audio están
// rotos y cuelgan al pipeline GStreamer en runtime.
//
// La función `is_playable` abre el archivo, identifica el formato, decodifica
// hasta 5 paquetes del track principal y devuelve true si todo eso ocurre
// sin error. No lee el archivo entero — basta para detectar corrupción de
// header, codec no soportado o paquetes iniciales rotos.

use std::fs::File;
use std::path::Path;

use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
use symphonia::core::errors::Error as SymError;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;

const PACKETS_REQUIRED: usize = 5;
const MAX_PACKETS_SCANNED: usize = 200;

/// Devuelve true si el archivo se puede decodificar sin errores.
pub fn is_playable(path: &Path) -> bool {
    let file = match File::open(path) {
        Ok(f) => f,
        Err(_) => return false,
    };
    let mss = MediaSourceStream::new(Box::new(file), Default::default());

    let mut hint = Hint::new();
    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
        hint.with_extension(ext);
    }

    let probed = match symphonia::default::get_probe().format(
        &hint,
        mss,
        &FormatOptions::default(),
        &MetadataOptions::default(),
    ) {
        Ok(p) => p,
        Err(_) => return false,
    };
    let mut format = probed.format;

    let track = match format
        .tracks()
        .iter()
        .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
    {
        Some(t) => t,
        None => return false,
    };
    let track_id = track.id;

    let mut decoder = match symphonia::default::get_codecs()
        .make(&track.codec_params, &DecoderOptions::default())
    {
        Ok(d) => d,
        Err(_) => return false,
    };

    let mut decoded = 0usize;
    for _ in 0..MAX_PACKETS_SCANNED {
        let packet = match format.next_packet() {
            Ok(p) => p,
            Err(SymError::IoError(ref e))
                if e.kind() == std::io::ErrorKind::UnexpectedEof =>
            {
                // EOF temprano: válido sólo si ya decodificamos algo
                return decoded > 0;
            }
            Err(_) => return false,
        };
        if packet.track_id() != track_id {
            continue;
        }
        match decoder.decode(&packet) {
            Ok(_) => {
                decoded += 1;
                if decoded >= PACKETS_REQUIRED {
                    return true;
                }
            }
            // DecodeError es recuperable per-packet, pero si pasa al inicio
            // tratamos al archivo como corrupto.
            Err(SymError::DecodeError(_)) => return false,
            Err(_) => return false,
        }
    }
    decoded > 0
}
