//! Call-audio routing abstraction.
//!
//! `CallAudio` is the common contract every device backend implements. Real
//! backends bring up the voice path when a call goes active and tear it down
//! on hangup; the mechanism (ALSA PCM bridge, UCM verb, modem AT setup) is the
//! backend's business and never leaks here. `NullAudio` is the fallback for
//! machines with no recognised telephony audio hardware (e.g. a workstation).
//!
//! Pick a backend with [`select`], which probes the hardware at startup.

use crate::types::CallId;

mod pw;
pub mod eg25;
pub mod librem5;

pub use eg25::Eg25Audio;
pub use librem5::Librem5Audio;

#[ derive( Clone, Debug ) ]
pub enum AudioError
{
	Backend( String ),
}

pub trait CallAudio
{
	/// Bring up the voice audio path for the given call. Idempotent.
	fn begin_call( &mut self, id: CallId ) -> Result<(), AudioError>;
	/// Tear it down. Idempotent.
	fn end_call( &mut self, id: CallId ) -> Result<(), AudioError>;
	fn set_mute( &mut self, id: CallId, muted: bool ) -> Result<(), AudioError>;
	fn set_speaker( &mut self, id: CallId, speaker: bool ) -> Result<(), AudioError>;
}

/// Choose the audio backend for this device. `EYDOS_AUDIO_BACKEND` forces one
/// (`librem5`, `eg25`, `null`); otherwise each backend's `detect()` is tried in
/// turn, falling back to a no-op when none matches.
pub fn select() -> Box<dyn CallAudio>
{
	match std::env::var( "EYDOS_AUDIO_BACKEND" ).ok().as_deref()
	{
		Some( "null" ) =>
		{
			eprintln!( "eydos-calls: call audio disabled (EYDOS_AUDIO_BACKEND=null)" );
			return Box::new( NullAudio );
		}
		Some( "librem5" ) =>
		{
			eprintln!( "eydos-calls: call audio via Librem 5 backend (forced)" );
			return Box::new( Librem5Audio::new() );
		}
		Some( "eg25" ) =>
		{
			eprintln!( "eydos-calls: call audio via EG25-G backend (forced)" );
			return Box::new( Eg25Audio::new() );
		}
		Some( other ) => eprintln!( "eydos-calls: unknown EYDOS_AUDIO_BACKEND={other:?}, auto-detecting" ),
		None          => {}
	}

	if let Some( a ) = Librem5Audio::detect()
	{
		eprintln!( "eydos-calls: call audio via Librem 5 backend" );
		return Box::new( a );
	}
	if let Some( a ) = Eg25Audio::detect()
	{
		eprintln!( "eydos-calls: call audio via EG25-G backend" );
		return Box::new( a );
	}

	eprintln!( "eydos-calls: no call-audio hardware recognised — audio disabled" );
	Box::new( NullAudio )
}

/// No-op audio backend: the app runs, calls connect, but no voice path is set
/// up. Used on hardware without a recognised modem↔codec route.
pub struct NullAudio;

impl CallAudio for NullAudio
{
	fn begin_call( &mut self, _id: CallId ) -> Result<(), AudioError> { Ok( () ) }
	fn end_call( &mut self, _id: CallId ) -> Result<(), AudioError> { Ok( () ) }
	fn set_mute( &mut self, _id: CallId, _muted: bool ) -> Result<(), AudioError> { Ok( () ) }
	fn set_speaker( &mut self, _id: CallId, _speaker: bool ) -> Result<(), AudioError> { Ok( () ) }
}
