//! Call-audio backend for EG25-G phones (PinePhone-class and Liberux targets).
//!
//! On these boards the EG25-G modem PCM is wired to the SoC codec **in
//! hardware**, exposed through an ALSA UCM "Voice Call" verb. So unlike the
//! Librem 5 there is NO software bridge: bringing up call audio is just
//! switching the codec card to its voice-call profile (the verb's enable
//! sequence powers the modem↔codec route), and earpiece/speaker is a second
//! profile switch. Mute is the handset mic source.
//!
//! Precondition (system, not us): the EG25-G's digital audio interface must be
//! configured once via `AT+QDAI=...` (handled by eg25-manager / the phone's
//! modem init). ModemManager owns the AT ports, so this backend does not touch
//! them.
//!
//! IMPORTANT — UNVERIFIED DEFAULTS: written without an EG25-G device on hand.
//! The card/profile/source names below are best-guess from PinePhone UCM
//! conventions and almost certainly need adjusting on the real target. Confirm
//! with `pactl list cards` on the device and override via `EYDOS_AUDIO_*` (or
//! update the defaults) before relying on it. Force-select with
//! `EYDOS_AUDIO_BACKEND=eg25`.

use std::path::Path;
use std::sync::mpsc;
use std::thread::{ self, JoinHandle };

use super::pw::{ self, env, env_bool };
use super::{ AudioError, CallAudio };
use crate::types::CallId;

#[ derive( Clone ) ]
struct Config
{
	codec_card:            String,
	hifi_profile:          String,
	voice_earpiece_profile: String,
	voice_speaker_profile:  String,
	mic_source:            String,
	start_on_speaker:      bool,
}

impl Config
{
	fn from_env() -> Self
	{
		Self
		{
			codec_card:             env( "EYDOS_AUDIO_CODEC_CARD",       "alsa_card.platform-sound" ),
			hifi_profile:           env( "EYDOS_AUDIO_HIFI_PROFILE",     "HiFi" ),
			voice_earpiece_profile: env( "EYDOS_AUDIO_EARPIECE_PROFILE", "Voice Call (Earpiece, Internal Mic)" ),
			voice_speaker_profile:  env( "EYDOS_AUDIO_SPEAKER_PROFILE",  "Voice Call (Speaker, Internal Mic)" ),
			mic_source:             env( "EYDOS_AUDIO_MIC_SOURCE",       "" ),
			start_on_speaker:       env_bool( "EYDOS_AUDIO_START_SPEAKER", false ),
		}
	}
}

#[ derive( Debug ) ]
enum Cmd
{
	Begin,
	End,
	Speaker( bool ),
	Mute( bool ),
}

pub struct Eg25Audio
{
	tx:      mpsc::Sender<Cmd>,
	_worker: JoinHandle<()>,
}

impl Eg25Audio
{
	/// Build the backend and start its worker regardless of hardware. Use via
	/// `EYDOS_AUDIO_BACKEND=eg25`; prefer [`Self::detect`].
	pub fn new() -> Self
	{
		let cfg = Config::from_env();
		let ( tx, rx ) = mpsc::channel();
		let worker = thread::Builder::new()
			.name( "eydos-audio".into() )
			.spawn( move || worker_main( cfg, rx ) )
			.expect( "failed to spawn audio worker thread" );
		Self { tx, _worker: worker }
	}

	/// `Some` on a recognised EG25-G phone. Detection is best-effort by ALSA
	/// card id; on unlisted targets force the backend with the env override.
	pub fn detect() -> Option<Self>
	{
		let known = [ "PinePhone", "PinePhonePro" ];
		let present = known.iter().any( |id| Path::new( &format!( "/proc/asound/{id}" ) ).exists() );
		present.then( Self::new )
	}

	fn send( &self, cmd: Cmd ) -> Result<(), AudioError>
	{
		self.tx
			.send( cmd )
			.map_err( |e| AudioError::Backend( format!( "audio worker: {e}" ) ) )
	}
}

impl CallAudio for Eg25Audio
{
	fn begin_call( &mut self, _id: CallId ) -> Result<(), AudioError>
	{
		self.send( Cmd::Begin )
	}

	fn end_call( &mut self, _id: CallId ) -> Result<(), AudioError>
	{
		self.send( Cmd::End )
	}

	fn set_mute( &mut self, _id: CallId, muted: bool ) -> Result<(), AudioError>
	{
		self.send( Cmd::Mute( muted ) )
	}

	fn set_speaker( &mut self, _id: CallId, speaker: bool ) -> Result<(), AudioError>
	{
		self.send( Cmd::Speaker( speaker ) )
	}
}

// ── Worker thread ────────────────────────────────────────────────

fn worker_main( cfg: Config, rx: mpsc::Receiver<Cmd> )
{
	let mut active = false;

	while let Ok( cmd ) = rx.recv()
	{
		match cmd
		{
			Cmd::Begin =>
			{
				set_voice_profile( &cfg, cfg.start_on_speaker );
				set_mute( &cfg, false );
				active = true;
			}
			Cmd::End =>
			{
				set_mute( &cfg, false );
				pw::set_card_profile( &cfg.codec_card, &cfg.hifi_profile );
				active = false;
			}
			Cmd::Speaker( s ) =>
			{
				if active
				{
					set_voice_profile( &cfg, s );
				}
			}
			Cmd::Mute( m ) =>
			{
				if active
				{
					set_mute( &cfg, m );
				}
			}
		}
	}
}

fn set_voice_profile( cfg: &Config, speaker: bool )
{
	let profile = if speaker { &cfg.voice_speaker_profile } else { &cfg.voice_earpiece_profile };
	pw::set_card_profile( &cfg.codec_card, profile );
}

fn set_mute( cfg: &Config, muted: bool )
{
	if cfg.mic_source.is_empty()
	{
		return;
	}
	pw::set_source_mute( &cfg.mic_source, muted );
}
