//! Native implementations of the public WebRTC compatibility surface.

use crate::{
    CloseRequest, Error, OpusFrame, ProvisionRequest, ProvisionResponse, SignalingCompleteRequest,
    VoiceEvent, VoiceSecret, VoiceSessionConfig, VoiceSignaling, WebRtcError, WebRtcVoiceSession,
    audio, audio_devices, encode_pcm_48k_mono, encode_wav, media, transport,
};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{CancellationToken, ReadWrite, Subscription};
use serde_json::{Map as JsonMap, Value as JsonValue, json};
use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::io::Read;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::path::Path;
use std::pin::Pin;
use std::sync::{
    Arc, Mutex, Weak,
    atomic::{AtomicBool, AtomicU64, Ordering},
};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};

fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    mutex
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn io_error(_: std::io::Error) -> Error {
    Error::InvalidOperation
}

fn audio_error(_: crate::WebRtcError) -> Error {
    Error::InvalidOperation
}

fn web_rtc_error(error: WebRtcError) -> Error {
    match error {
        WebRtcError::InvalidInput(_) | WebRtcError::Protocol(_) | WebRtcError::Signaling(_) => {
            Error::Argument
        }
        WebRtcError::Io(_) => Error::Socket,
        WebRtcError::Closed
        | WebRtcError::Rtc(_)
        | WebRtcError::Audio(_)
        | WebRtcError::Timeout(_) => Error::InvalidOperation,
    }
}

/// Logging boundary used by the native WebRTC implementation.
pub trait IVoiceLogger: Send + Sync {
    fn debug(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error>;

    fn error(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error>;

    fn info(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error>;

    fn log(
        &self,
        message: String,
        level: libremetaverse::logging::LogLevel,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error>;

    fn warn(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error>;
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VoiceLogRecord {
    pub level: i32,
    pub message: String,
}

#[derive(Clone, Default)]
pub struct LibreMetaverseVoiceLogger {
    records: Arc<Mutex<Vec<VoiceLogRecord>>>,
}

impl LibreMetaverseVoiceLogger {
    pub fn new() -> Result<Self, Error> {
        Ok(Self::default())
    }

    pub fn debug(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.write(1, message, client)
    }

    pub fn error(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.write(4, message, client)
    }

    pub fn info(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.write(2, message, client)
    }

    pub fn log(
        &self,
        message: String,
        level: libremetaverse::logging::LogLevel,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.write(level.0, message, client)
    }

    pub fn warn(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.write(3, message, client)
    }

    #[must_use]
    pub fn records(&self) -> Vec<VoiceLogRecord> {
        lock(&self.records).clone()
    }

    fn write(
        &self,
        level: i32,
        message: String,
        _client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        if message.len() > 1024 * 1024 || message.contains('\0') {
            return Err(Error::Argument);
        }
        lock(&self.records).push(VoiceLogRecord { level, message });
        Ok(())
    }
}

impl IVoiceLogger for LibreMetaverseVoiceLogger {
    fn debug(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        Self::debug(self, message, client)
    }

    fn error(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        Self::error(self, message, client)
    }

    fn info(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        Self::info(self, message, client)
    }

    fn log(
        &self,
        message: String,
        level: libremetaverse::logging::LogLevel,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        Self::log(self, message, level, client)
    }

    fn warn(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        Self::warn(self, message, client)
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VoiceException {
    message: String,
    definitive_rejection: bool,
}

impl VoiceException {
    pub fn new_with_string(msg: String) -> Result<Self, Error> {
        Self::new_with_string_boolean(msg, false)
    }

    pub fn new_with_string_boolean(
        msg: String,
        is_definitive_rejection: bool,
    ) -> Result<Self, Error> {
        if msg.len() > 1024 * 1024 || msg.contains('\0') {
            return Err(Error::Argument);
        }
        Ok(Self {
            message: msg,
            definitive_rejection: is_definitive_rejection,
        })
    }

    #[must_use]
    pub const fn is_definitive_rejection(&self) -> bool {
        self.definitive_rejection
    }
}

impl fmt::Display for VoiceException {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(&self.message)
    }
}

impl std::error::Error for VoiceException {}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
pub enum VoiceSessionESessionType {
    LOCAL = 0,
    MULTIAGENT = 1,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct VoiceSessionInt3 {
    pub x: i32,
    pub y: i32,
    pub z: i32,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct VoiceSessionInt4 {
    pub w: i32,
    pub x: i32,
    pub y: i32,
    pub z: i32,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VoiceSessionAvatarPosition {
    agent_id: UUID,
    listener_heading: Option<Option<VoiceSessionInt4>>,
    listener_position: Option<Option<VoiceSessionInt3>>,
    sender_heading: Option<Option<VoiceSessionInt4>>,
    sender_position: Option<Option<VoiceSessionInt3>>,
}

impl VoiceSessionAvatarPosition {
    pub fn new() -> Result<Self, Error> {
        Ok(Self::default())
    }

    pub const fn agent_id(&self) -> UUID {
        self.agent_id
    }

    pub fn set_agent_id(&mut self, value: UUID) {
        self.agent_id = value;
    }

    pub const fn listener_heading(&self) -> Option<Option<VoiceSessionInt4>> {
        self.listener_heading
    }

    pub fn set_listener_heading(&mut self, value: Option<Option<VoiceSessionInt4>>) {
        self.listener_heading = value;
    }

    pub const fn listener_position(&self) -> Option<Option<VoiceSessionInt3>> {
        self.listener_position
    }

    pub fn set_listener_position(&mut self, value: Option<Option<VoiceSessionInt3>>) {
        self.listener_position = value;
    }

    pub const fn sender_heading(&self) -> Option<Option<VoiceSessionInt4>> {
        self.sender_heading
    }

    pub fn set_sender_heading(&mut self, value: Option<Option<VoiceSessionInt4>>) {
        self.sender_heading = value;
    }

    pub const fn sender_position(&self) -> Option<Option<VoiceSessionInt3>> {
        self.sender_position
    }

    pub fn set_sender_position(&mut self, value: Option<Option<VoiceSessionInt3>>) {
        self.sender_position = value;
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VoiceSessionPeerAudioState {
    joined_primary: Option<Option<bool>>,
    left: bool,
    moderator_muted: Option<Option<bool>>,
    power: Option<Option<i32>>,
    voice_active: Option<Option<bool>>,
}

impl VoiceSessionPeerAudioState {
    pub fn new() -> Result<Self, Error> {
        Ok(Self::default())
    }

    pub const fn joined_primary(&self) -> Option<Option<bool>> {
        self.joined_primary
    }

    pub fn set_joined_primary(&mut self, value: Option<Option<bool>>) {
        self.joined_primary = value;
    }

    pub const fn left(&self) -> bool {
        self.left
    }

    pub fn set_left(&mut self, value: bool) {
        self.left = value;
    }

    pub const fn moderator_muted(&self) -> Option<Option<bool>> {
        self.moderator_muted
    }

    pub fn set_moderator_muted(&mut self, value: Option<Option<bool>>) {
        self.moderator_muted = value;
    }

    pub const fn power(&self) -> Option<Option<i32>> {
        self.power
    }

    pub fn set_power(&mut self, value: Option<Option<i32>>) {
        self.power = value;
    }

    pub const fn voice_active(&self) -> Option<Option<bool>> {
        self.voice_active
    }

    pub fn set_voice_active(&mut self, value: Option<Option<bool>>) {
        self.voice_active = value;
    }
}

type EncodedHandler = Arc<dyn Fn(u32, Vec<u8>) + Send + Sync>;
type BoolHandler = Arc<dyn Fn(bool) + Send + Sync>;

#[derive(Clone, Copy, Debug, Default)]
struct AudioProcessing {
    noise_suppression: bool,
    high_pass_filter: bool,
    automatic_gain_control: bool,
    echo_cancellation: bool,
    noise_suppression_level: i32,
    analog_gain_control: bool,
    echo_latency_ms: i32,
}

struct AudioDeviceState {
    disposed: bool,
    endpoint: Option<audio::SoundFlowAudioEndPoint>,
    source: Option<audio::SoundFlowAudioSource>,
    playback_active: bool,
    recording_active: bool,
    mic_mute: bool,
    speaker_level: f32,
    capture_gain: i32,
    playback_device: (u32, String),
    recording_device: (u32, String),
    playback_devices: HashMap<u32, String>,
    recording_devices: HashMap<u32, String>,
    playback_ids: HashMap<u32, String>,
    recording_ids: HashMap<u32, String>,
    ssrc_gain: HashMap<u32, i32>,
    ssrc_mute: HashMap<u32, bool>,
    decoded_samples: u64,
    processing: Option<AudioProcessing>,
}

struct AudioDeviceInner {
    logger: Arc<dyn IVoiceLogger>,
    state: Mutex<AudioDeviceState>,
    next_handler: AtomicU64,
    encoded_handlers: Arc<Mutex<HashMap<u64, EncodedHandler>>>,
    playback_handlers: Arc<Mutex<HashMap<u64, BoolHandler>>>,
    recording_handlers: Arc<Mutex<HashMap<u64, BoolHandler>>>,
    file_stop: AtomicBool,
    file_thread: Mutex<Option<JoinHandle<()>>>,
}

#[derive(Clone)]
pub struct AudioDevice {
    inner: Arc<AudioDeviceInner>,
}

impl AudioDevice {
    pub fn new(logger: Option<Box<dyn IVoiceLogger>>) -> Result<Self, Error> {
        let catalog = audio_devices().map_err(audio_error)?;
        let mut playback_devices = HashMap::new();
        let mut recording_devices = HashMap::new();
        let mut playback_ids = HashMap::new();
        let mut recording_ids = HashMap::new();
        let mut playback_device = (0, String::new());
        let mut recording_device = (0, String::new());
        for (index, device) in catalog.into_iter().enumerate() {
            let id = u32::try_from(index + 1).map_err(|_| Error::IndexOutOfRange)?;
            if device.input {
                if recording_device.0 == 0 || device.is_default {
                    recording_device = (id, device.name.clone());
                }
                recording_ids.insert(id, device.id);
                recording_devices.insert(id, device.name);
            } else {
                if playback_device.0 == 0 || device.is_default {
                    playback_device = (id, device.name.clone());
                }
                playback_ids.insert(id, device.id);
                playback_devices.insert(id, device.name);
            }
        }
        let logger: Arc<dyn IVoiceLogger> = match logger {
            Some(logger) => Arc::from(logger),
            None => Arc::new(LibreMetaverseVoiceLogger::default()),
        };
        Ok(Self {
            inner: Arc::new(AudioDeviceInner {
                logger,
                state: Mutex::new(AudioDeviceState {
                    disposed: false,
                    endpoint: None,
                    source: None,
                    playback_active: false,
                    recording_active: false,
                    mic_mute: false,
                    speaker_level: 1.0,
                    capture_gain: 100,
                    playback_device,
                    recording_device,
                    playback_devices,
                    recording_devices,
                    playback_ids,
                    recording_ids,
                    ssrc_gain: HashMap::new(),
                    ssrc_mute: HashMap::new(),
                    decoded_samples: 0,
                    processing: None,
                }),
                next_handler: AtomicU64::new(1),
                encoded_handlers: Arc::new(Mutex::new(HashMap::new())),
                playback_handlers: Arc::new(Mutex::new(HashMap::new())),
                recording_handlers: Arc::new(Mutex::new(HashMap::new())),
                file_stop: AtomicBool::new(false),
                file_thread: Mutex::new(None),
            }),
        })
    }

    #[must_use]
    pub fn subscribe_on_audio_source_encoded_sample(
        &self,
        handler: Option<Box<dyn Fn(u32, Vec<u8>) + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.encoded_handlers,
            handler,
        )
    }

    #[must_use]
    pub fn subscribe_on_playback_active_changed(
        &self,
        handler: Option<Box<dyn Fn(bool) + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.playback_handlers,
            handler,
        )
    }

    #[must_use]
    pub fn subscribe_on_recording_active_changed(
        &self,
        handler: Option<Box<dyn Fn(bool) + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.recording_handlers,
            handler,
        )
    }

    fn ensure_active(&self) -> Result<(), Error> {
        if lock(&self.inner.state).disposed {
            Err(Error::InvalidOperation)
        } else {
            Ok(())
        }
    }

    fn set_playback_active_value(&self, active: bool) {
        let changed = {
            let mut state = lock(&self.inner.state);
            let changed = state.playback_active != active;
            state.playback_active = active;
            changed
        };
        if changed {
            emit(&self.inner.playback_handlers, active);
        }
    }

    fn set_recording_active_value(&self, active: bool) {
        let changed = {
            let mut state = lock(&self.inner.state);
            let changed = state.recording_active != active;
            state.recording_active = active;
            changed
        };
        if changed {
            emit(&self.inner.recording_handlers, active);
        }
    }

    fn emit_frames(&self, frames: &[OpusFrame]) {
        for frame in frames {
            emit2(&self.inner.encoded_handlers, 960, frame.0.clone());
        }
    }

    pub fn audio_source_on_audio_source_encoded_sample(
        &self,
        duration_rtp_units: u32,
        sample: Vec<u8>,
    ) -> Result<(), Error> {
        self.feed_encoded_sample(duration_rtp_units, sample)
    }

    pub fn audio_source_on_audio_source_raw_sample(
        &self,
        sampling_rate: media::AudioSamplingRatesEnum,
        _duration_milliseconds: u32,
        sample: Vec<i16>,
    ) -> Result<(), Error> {
        let rate = if sampling_rate.0 > 0 {
            sampling_rate.0
        } else {
            48_000
        };
        self.feed_pcm_samples(sample, Some(1), Some(rate))
    }

    pub fn clear_ssrc(&self, ssrc: u32) -> Result<(), Error> {
        self.ensure_active()?;
        let mut state = lock(&self.inner.state);
        state.ssrc_gain.remove(&ssrc);
        state.ssrc_mute.remove(&ssrc);
        Ok(())
    }

    pub fn close_recording_source(&self) -> Result<(), Error> {
        self.stop_recording()
    }

    pub fn disable_audio_processing(&self) -> Result<(), Error> {
        self.ensure_active()?;
        lock(&self.inner.state).processing = None;
        Ok(())
    }

    pub fn dispose(&self) -> Result<(), Error> {
        self.stop_file()?;
        self.set_playback_active_value(false);
        self.set_recording_active_value(false);
        lock(&self.inner.state).disposed = true;
        lock(&self.inner.encoded_handlers).clear();
        lock(&self.inner.playback_handlers).clear();
        lock(&self.inner.recording_handlers).clear();
        Ok(())
    }

    #[allow(clippy::too_many_arguments)]
    pub fn enable_audio_processing(
        &self,
        noise_suppression: Option<bool>,
        high_pass_filter: Option<bool>,
        agc: Option<bool>,
        echo_cancellation: Option<bool>,
        ns_level: Option<audio::NoiseSuppressionLevel>,
        agc1: Option<bool>,
        aec_latency_ms: Option<i32>,
    ) -> Result<(), Error> {
        self.ensure_active()?;
        let latency = aec_latency_ms.unwrap_or_default();
        if !(0..=2_000).contains(&latency) {
            return Err(Error::Argument);
        }
        lock(&self.inner.state).processing = Some(AudioProcessing {
            noise_suppression: noise_suppression.unwrap_or(true),
            high_pass_filter: high_pass_filter.unwrap_or(true),
            automatic_gain_control: agc.unwrap_or(true),
            echo_cancellation: echo_cancellation.unwrap_or(true),
            noise_suppression_level: ns_level.unwrap_or_default().0,
            analog_gain_control: agc1.unwrap_or(false),
            echo_latency_ms: latency,
        });
        Ok(())
    }

    pub fn ensure_endpoint(&self) -> Result<bool, Error> {
        self.ensure_active()?;
        let mut state = lock(&self.inner.state);
        if state.endpoint.is_none() {
            state.endpoint = Some(audio::SoundFlowAudioEndPoint {
                id: "native-webrtc-endpoint".into(),
            });
        }
        if state.source.is_none() {
            state.source = Some(audio::SoundFlowAudioSource {
                id: "native-webrtc-source".into(),
            });
        }
        let _ = self
            .inner
            .logger
            .debug("native WebRTC audio endpoint ready".into(), None);
        Ok(true)
    }

    pub fn feed_encoded_sample(
        &self,
        duration_rtp_units: u32,
        encoded: Vec<u8>,
    ) -> Result<(), Error> {
        self.ensure_active()?;
        if duration_rtp_units == 0 || encoded.is_empty() || encoded.len() > 64 * 1024 {
            return Err(Error::Argument);
        }
        emit2(&self.inner.encoded_handlers, duration_rtp_units, encoded);
        Ok(())
    }

    pub fn feed_pcm_bytes(
        &self,
        pcm_bytes: Vec<u8>,
        channels: Option<i32>,
        sample_rate: Option<i32>,
    ) -> Result<(), Error> {
        if pcm_bytes.len() % 2 != 0 {
            return Err(Error::Argument);
        }
        let samples = pcm_bytes
            .chunks_exact(2)
            .map(|bytes| i16::from_le_bytes([bytes[0], bytes[1]]))
            .collect();
        self.feed_pcm_samples(samples, channels, sample_rate)
    }

    pub fn feed_pcm_samples(
        &self,
        pcm_interleaved: Vec<i16>,
        channels: Option<i32>,
        sample_rate: Option<i32>,
    ) -> Result<(), Error> {
        self.ensure_active()?;
        let normalized = normalize_pcm(
            &pcm_interleaved,
            channels.unwrap_or(1),
            sample_rate.unwrap_or(48_000),
        )?;
        let frames = encode_pcm_48k_mono(&normalized).map_err(audio_error)?;
        self.emit_frames(&frames);
        Ok(())
    }

    pub async fn feed_pcm_samples_paced(
        &self,
        pcm_interleaved: Vec<i16>,
        channels: Option<i32>,
        sample_rate: Option<i32>,
        ct: Option<CancellationToken>,
    ) -> Result<(), Error> {
        self.ensure_active()?;
        let normalized = normalize_pcm(
            &pcm_interleaved,
            channels.unwrap_or(1),
            sample_rate.unwrap_or(48_000),
        )?;
        let frames = encode_pcm_48k_mono(&normalized).map_err(audio_error)?;
        for frame in frames {
            if let Some(token) = &ct {
                token.throw_if_cancellation_requested()?;
            }
            emit2(&self.inner.encoded_handlers, 960, frame.0);
            tokio::time::sleep(Duration::from_millis(20)).await;
        }
        Ok(())
    }

    pub fn get_playback_devices(&self) -> Result<HashMap<u32, String>, Error> {
        self.ensure_active()?;
        Ok(lock(&self.inner.state).playback_devices.clone())
    }

    pub fn get_recording_devices(&self) -> Result<HashMap<u32, String>, Error> {
        self.ensure_active()?;
        Ok(lock(&self.inner.state).recording_devices.clone())
    }

    pub fn play_file_loop(&self, path: String) -> Result<(), Error> {
        self.start_file_playback(path, Some(true))
    }

    pub fn play_file_once(&self, path: String) -> Result<(), Error> {
        self.start_file_playback(path, Some(false))
    }

    pub async fn play_raw_pcm_stream(
        &self,
        mut pcm_stream: Box<dyn ReadWrite + Send>,
        channels: Option<i32>,
        sample_rate: Option<i32>,
        ct: Option<CancellationToken>,
    ) -> Result<(), Error> {
        let mut bytes = Vec::new();
        pcm_stream
            .by_ref()
            .take(128 * 1024 * 1024 + 1)
            .read_to_end(&mut bytes)
            .map_err(io_error)?;
        if bytes.len() > 128 * 1024 * 1024 {
            return Err(Error::Argument);
        }
        let samples = pcm_bytes(&bytes)?;
        self.feed_pcm_samples_paced(samples, channels, sample_rate, ct)
            .await
    }

    pub fn play_rtp_packet(&self, ssrc: u32, payload: Vec<u8>) -> Result<(), Error> {
        self.ensure_active()?;
        if payload.is_empty() || payload.len() > 64 * 1024 {
            return Err(Error::Argument);
        }
        let (muted, gain) = {
            let state = lock(&self.inner.state);
            (
                state.ssrc_mute.get(&ssrc).copied().unwrap_or(false),
                state.ssrc_gain.get(&ssrc).copied().unwrap_or(100),
            )
        };
        if muted || gain == 0 {
            return Ok(());
        }
        let mut decoder =
            libremetaverse_opus::Decoder::new(48_000, libremetaverse_opus::Channels::Mono)
                .map_err(|_| Error::InvalidOperation)?;
        let mut output = vec![0_i16; 5_760];
        let count = decoder
            .decode(Some(&payload), &mut output, false)
            .map_err(|_| Error::Argument)?;
        let mut state = lock(&self.inner.state);
        state.decoded_samples = state
            .decoded_samples
            .saturating_add(u64::try_from(count).unwrap_or(u64::MAX));
        Ok(())
    }

    pub fn set_capture_gain_percent(&self, percent: i32) -> Result<(), Error> {
        validate_percent(percent)?;
        self.ensure_active()?;
        lock(&self.inner.state).capture_gain = percent;
        Ok(())
    }

    pub fn set_playback_device_with_string(
        &self,
        device_name: Option<String>,
    ) -> Result<(), Error> {
        self.select_device(device_name, false)
    }

    pub fn set_playback_volume(&self, normalized: f32) -> Result<(), Error> {
        if !normalized.is_finite() || !(0.0..=1.0).contains(&normalized) {
            return Err(Error::Argument);
        }
        self.ensure_active()?;
        lock(&self.inner.state).speaker_level = normalized;
        Ok(())
    }

    pub fn set_recording_device_with_string(
        &self,
        device_name: Option<String>,
    ) -> Result<(), Error> {
        self.select_device(device_name, true)
    }

    fn select_device(&self, device_name: Option<String>, input: bool) -> Result<(), Error> {
        self.ensure_active()?;
        let mut state = lock(&self.inner.state);
        let devices = if input {
            &state.recording_devices
        } else {
            &state.playback_devices
        };
        let selected = match device_name {
            Some(name) => devices
                .iter()
                .find(|(_, candidate)| candidate.as_str() == name)
                .map(|(id, name)| (*id, name.clone()))
                .ok_or(Error::Argument)?,
            None => devices
                .iter()
                .min_by_key(|(id, _)| *id)
                .map(|(id, name)| (*id, name.clone()))
                .ok_or(Error::InvalidOperation)?,
        };
        if input {
            state.recording_device = selected;
        } else {
            state.playback_device = selected;
        }
        Ok(())
    }

    pub fn set_ssrc_gain_percent(&self, ssrc: u32, percent: i32) -> Result<(), Error> {
        validate_percent(percent)?;
        self.ensure_active()?;
        lock(&self.inner.state).ssrc_gain.insert(ssrc, percent);
        Ok(())
    }

    pub fn set_ssrc_mute(&self, ssrc: u32, muted: bool) -> Result<(), Error> {
        self.ensure_active()?;
        lock(&self.inner.state).ssrc_mute.insert(ssrc, muted);
        Ok(())
    }

    pub fn start_file_playback(&self, path: String, loop_: Option<bool>) -> Result<(), Error> {
        self.ensure_active()?;
        self.stop_file()?;
        let frames = encode_wav(Path::new(&path)).map_err(audio_error)?;
        let inner = Arc::clone(&self.inner);
        inner.file_stop.store(false, Ordering::Release);
        self.set_playback_active_value(true);
        let repeat = loop_.unwrap_or(false);
        let thread_inner = Arc::clone(&inner);
        let handle = std::thread::Builder::new()
            .name("webrtc-audio-file".into())
            .spawn(move || {
                loop {
                    for frame in &frames {
                        if thread_inner.file_stop.load(Ordering::Acquire) {
                            set_playback_inner(&thread_inner, false);
                            return;
                        }
                        emit2(&thread_inner.encoded_handlers, 960, frame.0.clone());
                        std::thread::sleep(Duration::from_millis(20));
                    }
                    if !repeat {
                        break;
                    }
                }
                set_playback_inner(&thread_inner, false);
            })
            .map_err(|_| Error::InvalidOperation)?;
        *lock(&inner.file_thread) = Some(handle);
        Ok(())
    }

    pub async fn start_playback(&self) -> Result<(), Error> {
        self.ensure_active()?;
        self.ensure_endpoint()?;
        self.set_playback_active_value(true);
        Ok(())
    }

    pub fn start_raw_pcm_file_loop(
        &self,
        path: String,
        channels: Option<i32>,
        loop_: Option<bool>,
    ) -> Result<(), Error> {
        let bytes = std::fs::read(path).map_err(io_error)?;
        let samples = pcm_bytes(&bytes)?;
        let frames = encode_pcm_48k_mono(&normalize_pcm(&samples, channels.unwrap_or(1), 48_000)?)
            .map_err(audio_error)?;
        self.start_encoded_file_frames(frames, loop_.unwrap_or(false))
    }

    fn start_encoded_file_frames(&self, frames: Vec<OpusFrame>, repeat: bool) -> Result<(), Error> {
        self.ensure_active()?;
        self.stop_file()?;
        let inner = Arc::clone(&self.inner);
        inner.file_stop.store(false, Ordering::Release);
        self.set_playback_active_value(true);
        let thread_inner = Arc::clone(&inner);
        let handle = std::thread::Builder::new()
            .name("webrtc-raw-pcm-file".into())
            .spawn(move || {
                loop {
                    for frame in &frames {
                        if thread_inner.file_stop.load(Ordering::Acquire) {
                            set_playback_inner(&thread_inner, false);
                            return;
                        }
                        emit2(&thread_inner.encoded_handlers, 960, frame.0.clone());
                        std::thread::sleep(Duration::from_millis(20));
                    }
                    if !repeat {
                        break;
                    }
                }
                set_playback_inner(&thread_inner, false);
            })
            .map_err(|_| Error::InvalidOperation)?;
        *lock(&inner.file_thread) = Some(handle);
        Ok(())
    }

    pub fn start_raw_pcm_stream(
        &self,
        mut pcm_stream: Box<dyn ReadWrite + Send>,
        channels: Option<i32>,
        sample_rate: Option<i32>,
    ) -> Result<(), Error> {
        let mut bytes = Vec::new();
        pcm_stream
            .by_ref()
            .take(128 * 1024 * 1024 + 1)
            .read_to_end(&mut bytes)
            .map_err(io_error)?;
        if bytes.len() > 128 * 1024 * 1024 {
            return Err(Error::Argument);
        }
        self.feed_pcm_bytes(bytes, channels, sample_rate)
    }

    pub fn start_recording(&self) -> Result<(), Error> {
        self.ensure_active()?;
        self.ensure_endpoint()?;
        self.set_recording_active_value(true);
        Ok(())
    }

    pub fn stop_file(&self) -> Result<(), Error> {
        self.inner.file_stop.store(true, Ordering::Release);
        if let Some(handle) = lock(&self.inner.file_thread).take() {
            if handle.thread().id() != std::thread::current().id() {
                handle.join().map_err(|_| Error::InvalidOperation)?;
            }
        }
        self.set_playback_active_value(false);
        Ok(())
    }

    pub fn stop_file_playback(&self) -> Result<(), Error> {
        self.stop_file()
    }

    pub async fn stop_playback(&self) -> Result<(), Error> {
        self.stop_file()?;
        self.set_playback_active_value(false);
        Ok(())
    }

    pub fn stop_raw_pcm_stream(&self) -> Result<(), Error> {
        self.stop_file()
    }

    pub fn stop_recording(&self) -> Result<(), Error> {
        self.ensure_active()?;
        self.set_recording_active_value(false);
        Ok(())
    }

    pub fn end_point(&self) -> Option<audio::SoundFlowAudioEndPoint> {
        lock(&self.inner.state).endpoint.clone()
    }

    pub fn set_end_point(&mut self, value: Option<audio::SoundFlowAudioEndPoint>) {
        lock(&self.inner.state).endpoint = value;
    }

    pub fn is_available(&self) -> bool {
        !lock(&self.inner.state).disposed
    }

    pub fn mic_mute(&self) -> bool {
        lock(&self.inner.state).mic_mute
    }

    pub fn set_mic_mute(&mut self, value: bool) {
        lock(&self.inner.state).mic_mute = value;
    }

    pub fn playback_active(&self) -> bool {
        lock(&self.inner.state).playback_active
    }

    pub fn set_playback_active(&mut self, value: bool) {
        self.set_playback_active_value(value);
    }

    pub fn playback_device(&self) -> (u32, String) {
        lock(&self.inner.state).playback_device.clone()
    }

    pub fn set_playback_device(&mut self, value: (u32, String)) {
        if lock(&self.inner.state).playback_devices.get(&value.0) == Some(&value.1) {
            lock(&self.inner.state).playback_device = value;
        }
    }

    pub fn recording_active(&self) -> bool {
        lock(&self.inner.state).recording_active
    }

    pub fn set_recording_active(&mut self, value: bool) {
        self.set_recording_active_value(value);
    }

    pub fn recording_device(&self) -> (u32, String) {
        lock(&self.inner.state).recording_device.clone()
    }

    pub fn set_recording_device(&mut self, value: (u32, String)) {
        if lock(&self.inner.state).recording_devices.get(&value.0) == Some(&value.1) {
            lock(&self.inner.state).recording_device = value;
        }
    }

    pub fn source(&self) -> Option<audio::SoundFlowAudioSource> {
        lock(&self.inner.state).source.clone()
    }

    pub fn set_source(&mut self, value: Option<audio::SoundFlowAudioSource>) {
        lock(&self.inner.state).source = value;
    }

    pub fn speaker_level(&self) -> f32 {
        lock(&self.inner.state).speaker_level
    }

    pub fn set_speaker_level(&mut self, value: f32) {
        if value.is_finite() {
            lock(&self.inner.state).speaker_level = value.clamp(0.0, 1.0);
        }
    }

    #[must_use]
    pub fn audio_processing_snapshot(&self) -> Option<(bool, bool, bool, bool, i32, bool, i32)> {
        lock(&self.inner.state).processing.map(|processing| {
            (
                processing.noise_suppression,
                processing.high_pass_filter,
                processing.automatic_gain_control,
                processing.echo_cancellation,
                processing.noise_suppression_level,
                processing.analog_gain_control,
                processing.echo_latency_ms,
            )
        })
    }

    #[must_use]
    pub fn decoded_samples(&self) -> u64 {
        lock(&self.inner.state).decoded_samples
    }

    fn selected_device_ids(&self) -> Result<(String, String), Error> {
        self.ensure_active()?;
        let state = lock(&self.inner.state);
        let input = state
            .recording_ids
            .get(&state.recording_device.0)
            .cloned()
            .ok_or(Error::InvalidOperation)?;
        let output = state
            .playback_ids
            .get(&state.playback_device.0)
            .cloned()
            .ok_or(Error::InvalidOperation)?;
        Ok((input, output))
    }
}

impl Drop for AudioDeviceInner {
    fn drop(&mut self) {
        self.file_stop.store(true, Ordering::Release);
        if let Some(handle) = lock(&self.file_thread).take() {
            if handle.thread().id() != std::thread::current().id() {
                let _ = handle.join();
            }
        }
    }
}

#[derive(Clone, Copy)]
struct PeerRecord {
    ssrc: Option<u32>,
    last_seen: Instant,
}

type UuidHandler = Arc<dyn Fn(UUID) + Send + Sync>;
type UuidListHandler = Arc<dyn Fn(Vec<UUID>) + Send + Sync>;
type PeerAudioHandler = Arc<dyn Fn(UUID, VoiceSessionPeerAudioState) + Send + Sync>;
type PeerPositionHandler = Arc<dyn Fn(UUID, OSDMap) + Send + Sync>;
type MuteMapHandler = Arc<dyn Fn(HashMap<UUID, bool>) + Send + Sync>;
type GainMapHandler = Arc<dyn Fn(HashMap<UUID, i32>) + Send + Sync>;
type UnitHandler = Arc<dyn Fn() + Send + Sync>;

struct PeerManagerInner {
    audio_device: AudioDevice,
    logger: Arc<dyn IVoiceLogger>,
    peers: Mutex<HashMap<UUID, PeerRecord>>,
    ssrc_peers: Mutex<HashMap<u32, UUID>>,
    next_handler: AtomicU64,
    gain_map: Arc<Mutex<HashMap<u64, GainMapHandler>>>,
    mute_map: Arc<Mutex<HashMap<u64, MuteMapHandler>>>,
    peer_audio: Arc<Mutex<HashMap<u64, PeerAudioHandler>>>,
    peer_joined: Arc<Mutex<HashMap<u64, UuidHandler>>>,
    peer_left: Arc<Mutex<HashMap<u64, UuidHandler>>>,
    peer_list: Arc<Mutex<HashMap<u64, UuidListHandler>>>,
    peer_position: Arc<Mutex<HashMap<u64, PeerPositionHandler>>>,
    pong: Arc<Mutex<HashMap<u64, UnitHandler>>>,
}

#[derive(Clone)]
pub struct PeerManager {
    inner: Arc<PeerManagerInner>,
}

impl PeerManager {
    pub fn new(
        audio_device: AudioDevice,
        _client: libremetaverse::GridClient,
        log: Box<dyn IVoiceLogger>,
    ) -> Result<Self, Error> {
        if !audio_device.is_available() {
            return Err(Error::InvalidOperation);
        }
        Ok(Self {
            inner: Arc::new(PeerManagerInner {
                audio_device,
                logger: Arc::from(log),
                peers: Mutex::new(HashMap::new()),
                ssrc_peers: Mutex::new(HashMap::new()),
                next_handler: AtomicU64::new(1),
                gain_map: Arc::new(Mutex::new(HashMap::new())),
                mute_map: Arc::new(Mutex::new(HashMap::new())),
                peer_audio: Arc::new(Mutex::new(HashMap::new())),
                peer_joined: Arc::new(Mutex::new(HashMap::new())),
                peer_left: Arc::new(Mutex::new(HashMap::new())),
                peer_list: Arc::new(Mutex::new(HashMap::new())),
                peer_position: Arc::new(Mutex::new(HashMap::new())),
                pong: Arc::new(Mutex::new(HashMap::new())),
            }),
        })
    }

    #[must_use]
    pub fn subscribe_gain_map_received(
        &self,
        handler: Option<Box<dyn Fn(HashMap<UUID, i32>) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.gain_map, handler)
    }

    #[must_use]
    pub fn subscribe_mute_map_received(
        &self,
        handler: Option<Box<dyn Fn(HashMap<UUID, bool>) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.mute_map, handler)
    }

    #[must_use]
    pub fn subscribe_peer_audio_updated(
        &self,
        handler: Option<Box<dyn Fn(UUID, VoiceSessionPeerAudioState) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.peer_audio, handler)
    }

    #[must_use]
    pub fn subscribe_peer_joined(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.peer_joined, handler)
    }

    #[must_use]
    pub fn subscribe_peer_left(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.peer_left, handler)
    }

    #[must_use]
    pub fn subscribe_peer_list_updated(
        &self,
        handler: Option<Box<dyn Fn(Vec<UUID>) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.peer_list, handler)
    }

    #[must_use]
    pub fn subscribe_peer_position_updated(
        &self,
        handler: Option<Box<dyn Fn(UUID, OSDMap) + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.peer_position, handler)
    }

    #[must_use]
    pub fn subscribe_pong_received(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.pong, handler)
    }

    pub fn clear_all_peers(&self) -> Result<(), Error> {
        let peers: Vec<_> = lock(&self.inner.peers).keys().copied().collect();
        lock(&self.inner.peers).clear();
        lock(&self.inner.ssrc_peers).clear();
        for peer in peers {
            emit(&self.inner.peer_left, peer);
        }
        self.emit_peer_list();
        Ok(())
    }

    pub fn get_known_peers(&self) -> Result<Vec<UUID>, Error> {
        let mut peers: Vec<_> = lock(&self.inner.peers).keys().copied().collect();
        peers.sort_by_key(ToString::to_string);
        Ok(peers)
    }

    pub fn process_json_element(
        &self,
        root: crate::signaling::JsonValue,
        send_string: Box<dyn Fn(String) -> bool + Send + Sync>,
        session_id: UUID,
    ) -> Result<(), Error> {
        let root = root.into_inner();
        if serde_json::to_vec(&root)
            .map_err(|_| Error::Argument)?
            .len()
            > 1024 * 1024
        {
            return Err(Error::Argument);
        }
        let object = root.as_object().ok_or(Error::Argument)?;
        if object.get("ping").and_then(JsonValue::as_bool) == Some(true)
            && !send_string(json!({"pong": true, "session_id": session_id.to_string()}).to_string())
        {
            return Err(Error::InvalidOperation);
        }
        if object.get("pong").and_then(JsonValue::as_bool) == Some(true) {
            emit0(&self.inner.pong);
        }
        if let Some(values) = object.get("m").and_then(JsonValue::as_object) {
            let values = json_bool_map(values);
            if !values.is_empty() {
                emit(&self.inner.mute_map, values);
            }
        }
        if let Some(values) = object.get("ug").and_then(JsonValue::as_object) {
            let values = json_i32_map(values);
            if !values.is_empty() {
                emit(&self.inner.gain_map, values);
            }
        }

        let all_peers = !object.is_empty() && object.keys().all(|key| uuid(key).is_some());
        let mut changed = false;
        if all_peers {
            for (key, value) in object {
                let peer = uuid(key).ok_or(Error::Argument)?;
                let Some(properties) = value.as_object() else {
                    changed |= self.remove_peer_internal(peer);
                    continue;
                };
                let left = json_bool(properties.get("l")).unwrap_or(false);
                if left {
                    changed |= self.remove_peer_internal(peer);
                    let mut state = peer_audio_state(properties);
                    state.set_left(true);
                    emit2(&self.inner.peer_audio, peer, state);
                    continue;
                }
                let ssrc = properties
                    .get("s")
                    .or_else(|| properties.get("ssrc"))
                    .and_then(JsonValue::as_u64)
                    .and_then(|value| u32::try_from(value).ok());
                changed |= self.upsert_peer(peer, ssrc);
                let state = peer_audio_state(properties);
                if let Some(ssrc) = ssrc {
                    if let Some(Some(muted)) = state.moderator_muted() {
                        self.inner.audio_device.set_ssrc_mute(ssrc, muted)?;
                    }
                }
                emit2(&self.inner.peer_audio, peer, state);
                if has_position(properties) {
                    emit2(
                        &self.inner.peer_position,
                        peer,
                        OSDMap::new_with_dictionary(json_object_to_osd(properties))?,
                    );
                }
            }
        } else if let Some(join) = object.get("j").and_then(JsonValue::as_object)
            && let Some(peer) = join.get("id").and_then(JsonValue::as_str).and_then(uuid)
        {
            changed |= self.upsert_peer(peer, None);
        }
        if changed {
            self.emit_peer_list();
        }
        Ok(())
    }

    pub fn process_osd_map(
        &self,
        map: OSDMap,
        send_string: Box<dyn Fn(String) -> bool + Send + Sync>,
        session_id: UUID,
    ) -> Result<(), Error> {
        self.process_json_element(
            crate::signaling::JsonValue::from_inner(JsonValue::Object(osd_map_to_json(
                map.snapshot(),
            ))),
            send_string,
            session_id,
        )
    }

    pub fn reap_stale_peers(&self, max_age: Duration) -> Result<(), Error> {
        let now = Instant::now();
        let stale: Vec<_> = lock(&self.inner.peers)
            .iter()
            .filter_map(|(peer, state)| {
                (now.saturating_duration_since(state.last_seen) >= max_age).then_some(*peer)
            })
            .collect();
        let mut changed = false;
        for peer in stale {
            changed |= self.remove_peer_internal(peer);
        }
        if changed {
            self.emit_peer_list();
        }
        Ok(())
    }

    pub fn remove_peer(&self, peer_id: UUID) -> Result<(), Error> {
        if self.remove_peer_internal(peer_id) {
            self.emit_peer_list();
        }
        Ok(())
    }

    pub fn try_get_peer_for_ssrc(&self, ssrc: u32, peer_id: &mut UUID) -> bool {
        let Some(peer) = lock(&self.inner.ssrc_peers).get(&ssrc).copied() else {
            return false;
        };
        *peer_id = peer;
        true
    }

    pub fn try_get_ssrc(&self, peer_id: UUID, ssrc: &mut u32) -> bool {
        let Some(value) = lock(&self.inner.peers)
            .get(&peer_id)
            .and_then(|record| record.ssrc)
        else {
            return false;
        };
        *ssrc = value;
        true
    }

    fn upsert_peer(&self, peer: UUID, ssrc: Option<u32>) -> bool {
        let mut peers = lock(&self.inner.peers);
        let joined = !peers.contains_key(&peer);
        let ssrc = ssrc.or_else(|| peers.get(&peer).and_then(|record| record.ssrc));
        if let Some(previous) = peers.insert(
            peer,
            PeerRecord {
                ssrc,
                last_seen: Instant::now(),
            },
        ) && previous.ssrc != ssrc
            && let Some(previous) = previous.ssrc
        {
            lock(&self.inner.ssrc_peers).remove(&previous);
        }
        if let Some(ssrc) = ssrc {
            lock(&self.inner.ssrc_peers).insert(ssrc, peer);
        }
        drop(peers);
        if joined {
            let _ = self
                .inner
                .logger
                .debug(format!("WebRTC peer joined: {peer}"), None);
            emit(&self.inner.peer_joined, peer);
        }
        joined
    }

    fn remove_peer_internal(&self, peer: UUID) -> bool {
        let removed = lock(&self.inner.peers).remove(&peer);
        let Some(removed) = removed else {
            return false;
        };
        if let Some(ssrc) = removed.ssrc {
            lock(&self.inner.ssrc_peers).remove(&ssrc);
            let _ = self.inner.audio_device.clear_ssrc(ssrc);
        }
        emit(&self.inner.peer_left, peer);
        true
    }

    fn emit_peer_list(&self) {
        if let Ok(peers) = self.get_known_peers() {
            emit(&self.inner.peer_list, peers);
        }
    }

    fn observe_audio(&self, peer: UUID, state: crate::PeerAudioState) -> Result<(), Error> {
        if state.left {
            if self.remove_peer_internal(peer) {
                self.emit_peer_list();
            }
        } else {
            if self.upsert_peer(peer, state.ssrc) {
                self.emit_peer_list();
            }
            if let Some(ssrc) = state.ssrc
                && let Some(muted) = state.moderator_muted
            {
                self.inner.audio_device.set_ssrc_mute(ssrc, muted)?;
            }
        }
        let mut mapped = VoiceSessionPeerAudioState::default();
        mapped.set_power(Some(state.power));
        mapped.set_voice_active(Some(state.voice_active));
        mapped.set_moderator_muted(Some(state.moderator_muted));
        mapped.set_joined_primary(Some(state.joined_primary));
        mapped.set_left(state.left);
        emit2(&self.inner.peer_audio, peer, mapped);
        Ok(())
    }

    fn observe_position(&self, peer: UUID, position: &crate::AvatarPosition) -> Result<(), Error> {
        if self.upsert_peer(peer, None) {
            self.emit_peer_list();
        }
        let map = avatar_position_json(position);
        emit2(
            &self.inner.peer_position,
            peer,
            OSDMap::new_with_dictionary(json_object_to_osd(&map))?,
        );
        Ok(())
    }

    fn observe_mute_map(&self, values: HashMap<UUID, bool>) {
        emit(&self.inner.mute_map, values);
    }

    fn observe_gain_map(&self, values: HashMap<UUID, i32>) {
        emit(&self.inner.gain_map, values);
    }
}

fn uuid(value: &str) -> Option<UUID> {
    UUID::new_with_string(value.to_owned()).ok()
}

fn json_bool(value: Option<&JsonValue>) -> Option<bool> {
    value.and_then(|value| {
        value
            .as_bool()
            .or_else(|| value.as_i64().map(|value| value != 0))
            .or_else(|| value.as_str().and_then(|value| value.parse().ok()))
    })
}

fn json_i32(value: Option<&JsonValue>) -> Option<i32> {
    value.and_then(|value| {
        value
            .as_i64()
            .and_then(|value| i32::try_from(value).ok())
            .or_else(|| value.as_str().and_then(|value| value.parse().ok()))
    })
}

fn json_bool_map(map: &JsonMap<String, JsonValue>) -> HashMap<UUID, bool> {
    map.iter()
        .filter_map(|(key, value)| Some((uuid(key)?, json_bool(Some(value))?)))
        .collect()
}

fn json_i32_map(map: &JsonMap<String, JsonValue>) -> HashMap<UUID, i32> {
    map.iter()
        .filter_map(|(key, value)| Some((uuid(key)?, json_i32(Some(value))?)))
        .collect()
}

fn peer_audio_state(map: &JsonMap<String, JsonValue>) -> VoiceSessionPeerAudioState {
    let mut state = VoiceSessionPeerAudioState::default();
    state.set_power(Some(json_i32(map.get("p"))));
    state.set_voice_active(Some(json_bool(map.get("V").or_else(|| map.get("v")))));
    state.set_moderator_muted(Some(json_bool(map.get("m"))));
    state.set_joined_primary(Some(
        map.get("j")
            .and_then(JsonValue::as_object)
            .and_then(|join| json_bool(join.get("p"))),
    ));
    state.set_left(json_bool(map.get("l")).unwrap_or(false));
    state
}

fn has_position(map: &JsonMap<String, JsonValue>) -> bool {
    ["sp", "sh", "lp", "lh"]
        .iter()
        .any(|key| map.contains_key(*key))
}

fn avatar_position_json(position: &crate::AvatarPosition) -> JsonMap<String, JsonValue> {
    let mut map = JsonMap::new();
    if let Some(value) = &position.sender_position {
        map.insert(
            "sp".into(),
            json!({"x": value.x, "y": value.y, "z": value.z}),
        );
    }
    if let Some(value) = &position.sender_heading {
        map.insert(
            "sh".into(),
            json!({"x": value.x, "y": value.y, "z": value.z, "w": value.w}),
        );
    }
    if let Some(value) = &position.listener_position {
        map.insert(
            "lp".into(),
            json!({"x": value.x, "y": value.y, "z": value.z}),
        );
    }
    if let Some(value) = &position.listener_heading {
        map.insert(
            "lh".into(),
            json!({"x": value.x, "y": value.y, "z": value.z, "w": value.w}),
        );
    }
    map
}

fn mapped_avatar_position(
    peer: UUID,
    position: crate::AvatarPosition,
) -> VoiceSessionAvatarPosition {
    let mut mapped = VoiceSessionAvatarPosition::default();
    mapped.set_agent_id(peer);
    mapped.set_sender_position(Some(position.sender_position.map(|value| {
        VoiceSessionInt3 {
            x: value.x,
            y: value.y,
            z: value.z,
        }
    })));
    mapped.set_sender_heading(Some(position.sender_heading.map(|value| {
        VoiceSessionInt4 {
            x: value.x,
            y: value.y,
            z: value.z,
            w: value.w,
        }
    })));
    mapped.set_listener_position(Some(position.listener_position.map(|value| {
        VoiceSessionInt3 {
            x: value.x,
            y: value.y,
            z: value.z,
        }
    })));
    mapped.set_listener_heading(Some(position.listener_heading.map(|value| {
        VoiceSessionInt4 {
            x: value.x,
            y: value.y,
            z: value.z,
            w: value.w,
        }
    })));
    mapped
}

fn json_to_osd(value: &JsonValue) -> OSD {
    match value {
        JsonValue::Null => OSD::Undefined,
        JsonValue::Bool(value) => OSD::Boolean(*value),
        JsonValue::Number(value) => value
            .as_i64()
            .and_then(|value| i32::try_from(value).ok())
            .map(OSD::Integer)
            .or_else(|| value.as_f64().map(OSD::Real))
            .unwrap_or(OSD::Undefined),
        JsonValue::String(value) => OSD::String(value.clone()),
        JsonValue::Array(values) => OSD::Array(values.iter().map(json_to_osd).collect()),
        JsonValue::Object(values) => OSD::Map(json_object_to_osd(values)),
    }
}

fn json_object_to_osd(values: &JsonMap<String, JsonValue>) -> HashMap<String, OSD> {
    values
        .iter()
        .map(|(key, value)| (key.clone(), json_to_osd(value)))
        .collect()
}

fn osd_to_json(value: OSD) -> JsonValue {
    match value {
        OSD::Undefined => JsonValue::Null,
        OSD::Boolean(value) => JsonValue::Bool(value),
        OSD::Integer(value) => JsonValue::from(value),
        OSD::Real(value) => JsonValue::from(value),
        OSD::String(value) | OSD::LlsdXml(value) => JsonValue::String(value),
        OSD::UUID(value) => JsonValue::String(value.to_string()),
        OSD::Uri(value) => JsonValue::String(value.0),
        OSD::Array(values) => JsonValue::Array(values.into_iter().map(osd_to_json).collect()),
        OSD::Map(values) => JsonValue::Object(osd_map_to_json(values)),
        OSD::Date(_) | OSD::Binary(_) => JsonValue::Null,
        _ => JsonValue::Null,
    }
}

fn osd_map_to_json(values: HashMap<String, OSD>) -> JsonMap<String, JsonValue> {
    values
        .into_iter()
        .map(|(key, value)| (key, osd_to_json(value)))
        .collect()
}

#[derive(Clone)]
struct GridVoiceSignaling {
    client: libremetaverse::GridClient,
}

impl GridVoiceSignaling {
    fn new(client: libremetaverse::GridClient) -> Self {
        Self { client }
    }

    fn capability(&self, name: &str) -> Result<libremetaverse_types::compat::Uri, WebRtcError> {
        let simulator = self
            .client
            .network()
            .current_sim()
            .ok_or_else(|| WebRtcError::Signaling("no connected simulator".into()))?;
        simulator
            .native_capability_uri(name)
            .map_err(|_| WebRtcError::Signaling(format!("invalid {name} capability")))?
            .ok_or_else(|| WebRtcError::Signaling(format!("missing {name} capability")))
    }

    async fn post(
        &self,
        capability: &str,
        payload: OSD,
    ) -> Result<HashMap<String, OSD>, WebRtcError> {
        let uri = self.capability(capability)?;
        let (status, bytes) = self
            .client
            .http_caps_client()
            .post_with_uri_osd_format_osd_cancellation_token_i_progress(
                uri,
                OSDFormat::Xml,
                payload,
                CancellationToken::default(),
                None,
            )
            .await
            .map_err(|_| WebRtcError::Signaling(format!("{capability} request failed")))?;
        if !status.is_success_status_code() {
            return Err(WebRtcError::Signaling(format!(
                "{capability} returned HTTP {}",
                status.status_code
            )));
        }
        match OSDParser::deserialize_with_bytes(bytes)
            .map_err(|_| WebRtcError::Signaling(format!("malformed {capability} response")))?
        {
            OSD::Map(map) => Ok(map),
            _ => Err(WebRtcError::Signaling(format!(
                "{capability} response is not an LLSD map"
            ))),
        }
    }
}

impl VoiceSignaling for GridVoiceSignaling {
    fn provision(
        &self,
        request: ProvisionRequest,
    ) -> Pin<
        Box<dyn std::future::Future<Output = Result<ProvisionResponse, WebRtcError>> + Send + '_>,
    > {
        Box::pin(async move {
            let mut jsep = HashMap::new();
            jsep.insert("type".into(), OSD::String(request.jsep.kind));
            jsep.insert("sdp".into(), OSD::String(request.jsep.sdp));
            let mut payload = HashMap::from([
                ("jsep".into(), OSD::Map(jsep)),
                ("channel_type".into(), OSD::String(request.channel_type)),
                (
                    "voice_server_type".into(),
                    OSD::String(request.voice_server_type),
                ),
            ]);
            if let Some(parcel) = request.parcel_local_id {
                payload.insert("parcel_local_id".into(), OSD::Integer(parcel));
            }
            let result = self
                .post("ProvisionVoiceAccountRequest", OSD::Map(payload))
                .await?;
            let answer_sdp = result
                .get("jsep")
                .and_then(|value| match value {
                    OSD::Map(jsep) => jsep.get("sdp"),
                    _ => None,
                })
                .and_then(|value| value.as_string().ok())
                .or_else(|| result.get("sdp").and_then(|value| value.as_string().ok()))
                .ok_or_else(|| WebRtcError::Signaling("provision response omitted SDP".into()))?;
            let viewer_session = result
                .get("viewer_session")
                .and_then(|value| {
                    value.as_uuid().ok().or_else(|| {
                        value
                            .as_string()
                            .ok()
                            .and_then(|value| UUID::new_with_string(value).ok())
                    })
                })
                .ok_or_else(|| {
                    WebRtcError::Signaling("provision response omitted viewer_session".into())
                })?;
            let channel = result
                .get("channel")
                .and_then(|value| value.as_string().ok())
                .filter(|value| !value.is_empty());
            let credentials = result
                .get("credentials")
                .and_then(|value| value.as_string().ok())
                .filter(|value| !value.is_empty())
                .map(VoiceSecret::new)
                .transpose()?;
            Ok(ProvisionResponse {
                answer_sdp,
                viewer_session,
                channel,
                credentials,
            })
        })
    }

    fn complete(
        &self,
        request: SignalingCompleteRequest,
    ) -> Pin<Box<dyn std::future::Future<Output = Result<(), WebRtcError>> + Send + '_>> {
        Box::pin(async move {
            self.post(
                "VoiceSignalingRequest",
                OSD::Map(HashMap::from([
                    (
                        "voice_server_type".into(),
                        OSD::String(request.voice_server_type),
                    ),
                    ("viewer_session".into(), OSD::String(request.viewer_session)),
                    (
                        "candidate".into(),
                        OSD::Map(HashMap::from([(
                            "completed".into(),
                            OSD::Boolean(request.candidate.completed),
                        )])),
                    ),
                ])),
            )
            .await?;
            Ok(())
        })
    }

    fn close(
        &self,
        request: CloseRequest,
    ) -> Pin<Box<dyn std::future::Future<Output = Result<(), WebRtcError>> + Send + '_>> {
        Box::pin(async move {
            let mut payload = HashMap::from([
                ("logout".into(), OSD::Boolean(request.logout)),
                (
                    "voice_server_type".into(),
                    OSD::String(request.voice_server_type),
                ),
                ("viewer_session".into(), OSD::String(request.viewer_session)),
            ]);
            if let Some(channel) = request.channel {
                payload.insert("channel".into(), OSD::String(channel));
            }
            if let Some(credentials) = request.credentials {
                payload.insert(
                    "credentials".into(),
                    OSD::String(credentials.expose().to_owned()),
                );
            }
            self.post("ProvisionVoiceAccountRequest", OSD::Map(payload))
                .await?;
            Ok(())
        })
    }
}

type TypedPositionHandler = Arc<dyn Fn(UUID, VoiceSessionAvatarPosition) + Send + Sync>;
type ExternalErrorHandler = Arc<dyn Fn(libremetaverse_types::compat::ExternalError) + Send + Sync>;

#[derive(Default)]
struct VoiceSessionState {
    channel_credentials: Option<String>,
    channel_id: Option<String>,
    connected: bool,
    data_channel_ready: bool,
    is_primary: bool,
    parcel_local_id: i32,
    sdp_local: String,
    sdp_remote: String,
    session_id: UUID,
    target_simulator: Option<libremetaverse::Simulator>,
    disposed: bool,
}

struct VoiceSessionInner {
    client: libremetaverse::GridClient,
    audio_device: AudioDevice,
    logger: Arc<dyn IVoiceLogger>,
    peer_manager: PeerManager,
    state: Mutex<VoiceSessionState>,
    session: Mutex<Option<WebRtcVoiceSession>>,
    monitor: Mutex<Option<tokio::task::JoinHandle<()>>>,
    starting: AtomicBool,
    next_handler: AtomicU64,
    data_ready: Arc<Mutex<HashMap<u64, UnitHandler>>>,
    connection_closed: Arc<Mutex<HashMap<u64, UnitHandler>>>,
    connection_ready: Arc<Mutex<HashMap<u64, UnitHandler>>>,
    typed_position: Arc<Mutex<HashMap<u64, TypedPositionHandler>>>,
    reprovision_failed: Arc<Mutex<HashMap<u64, ExternalErrorHandler>>>,
    reprovision_succeeded: Arc<Mutex<HashMap<u64, UnitHandler>>>,
}

#[derive(Clone)]
pub struct VoiceSession {
    inner: Arc<VoiceSessionInner>,
}

impl VoiceSession {
    pub const PROVISION_VOICE_ACCOUNT_CAP: &'static str = "ProvisionVoiceAccountRequest";
    pub const VOICE_SIGNALING_CAP: &'static str = "VoiceSignalingRequest";

    fn new_native(
        client: libremetaverse::GridClient,
        audio_device: AudioDevice,
        logger: Arc<dyn IVoiceLogger>,
        is_primary: bool,
        parcel_local_id: i32,
        target_simulator: Option<libremetaverse::Simulator>,
    ) -> Result<Self, Error> {
        if parcel_local_id < 0 || !audio_device.is_available() {
            return Err(Error::Argument);
        }
        let peer_manager = PeerManager::new(
            audio_device.clone(),
            client.clone(),
            Box::new(SharedVoiceLogger(Arc::clone(&logger))),
        )?;
        Ok(Self {
            inner: Arc::new(VoiceSessionInner {
                client,
                audio_device,
                logger,
                peer_manager,
                state: Mutex::new(VoiceSessionState {
                    is_primary,
                    parcel_local_id,
                    target_simulator,
                    ..VoiceSessionState::default()
                }),
                session: Mutex::new(None),
                monitor: Mutex::new(None),
                starting: AtomicBool::new(false),
                next_handler: AtomicU64::new(1),
                data_ready: Arc::new(Mutex::new(HashMap::new())),
                connection_closed: Arc::new(Mutex::new(HashMap::new())),
                connection_ready: Arc::new(Mutex::new(HashMap::new())),
                typed_position: Arc::new(Mutex::new(HashMap::new())),
                reprovision_failed: Arc::new(Mutex::new(HashMap::new())),
                reprovision_succeeded: Arc::new(Mutex::new(HashMap::new())),
            }),
        })
    }

    #[must_use]
    pub fn subscribe_on_data_channel_ready(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        subscribe(&self.inner.next_handler, &self.inner.data_ready, handler)
    }

    #[must_use]
    pub fn subscribe_on_gain_map_received(
        &self,
        handler: Option<Box<dyn Fn(HashMap<UUID, i32>) + Send + Sync>>,
    ) -> Subscription {
        self.inner.peer_manager.subscribe_gain_map_received(handler)
    }

    #[must_use]
    pub fn subscribe_on_mute_map_received(
        &self,
        handler: Option<Box<dyn Fn(HashMap<UUID, bool>) + Send + Sync>>,
    ) -> Subscription {
        self.inner.peer_manager.subscribe_mute_map_received(handler)
    }

    #[must_use]
    pub fn subscribe_on_peer_audio_updated(
        &self,
        handler: Option<Box<dyn Fn(UUID, VoiceSessionPeerAudioState) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .peer_manager
            .subscribe_peer_audio_updated(handler)
    }

    #[must_use]
    pub fn subscribe_on_peer_connection_closed(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.connection_closed,
            handler,
        )
    }

    #[must_use]
    pub fn subscribe_on_peer_connection_ready(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.connection_ready,
            handler,
        )
    }

    #[must_use]
    pub fn subscribe_on_peer_joined(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner.peer_manager.subscribe_peer_joined(handler)
    }

    #[must_use]
    pub fn subscribe_on_peer_left(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner.peer_manager.subscribe_peer_left(handler)
    }

    #[must_use]
    pub fn subscribe_on_peer_list_updated(
        &self,
        handler: Option<Box<dyn Fn(Vec<UUID>) + Send + Sync>>,
    ) -> Subscription {
        self.inner.peer_manager.subscribe_peer_list_updated(handler)
    }

    #[must_use]
    pub fn subscribe_on_peer_position_updated(
        &self,
        handler: Option<Box<dyn Fn(UUID, OSDMap) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .peer_manager
            .subscribe_peer_position_updated(handler)
    }

    #[must_use]
    pub fn subscribe_on_peer_position_updated_typed(
        &self,
        handler: Option<Box<dyn Fn(UUID, VoiceSessionAvatarPosition) + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.typed_position,
            handler,
        )
    }

    #[must_use]
    pub fn subscribe_on_reprovision_failed(
        &self,
        handler: Option<Box<dyn Fn(libremetaverse_types::compat::ExternalError) + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.reprovision_failed,
            handler,
        )
    }

    #[must_use]
    pub fn subscribe_on_reprovision_succeeded(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        subscribe(
            &self.inner.next_handler,
            &self.inner.reprovision_succeeded,
            handler,
        )
    }

    pub async fn close_session(&self) -> Result<(), Error> {
        let session = lock(&self.inner.session).take();
        if let Some(mut session) = session {
            session.shutdown().await.map_err(web_rtc_error)?;
        }
        if let Some(monitor) = lock(&self.inner.monitor).take() {
            let _ = tokio::time::timeout(Duration::from_secs(5), monitor).await;
        }
        let was_connected = {
            let mut state = lock(&self.inner.state);
            let connected = state.connected;
            state.connected = false;
            state.data_channel_ready = false;
            connected
        };
        self.inner.peer_manager.clear_all_peers()?;
        if was_connected {
            emit0(&self.inner.connection_closed);
        }
        Ok(())
    }

    pub async fn create_peer_connection(
        &self,
        ct: Option<CancellationToken>,
    ) -> Result<transport::RTCPeerConnection, Error> {
        self.ensure_started(ct).await?;
        Ok(transport::RTCPeerConnection { connected: true })
    }

    pub fn dispose(&self) -> Result<(), Error> {
        let mut state = lock(&self.inner.state);
        if state.disposed {
            return Ok(());
        }
        state.disposed = true;
        let was_connected = state.connected;
        state.connected = false;
        state.data_channel_ready = false;
        drop(state);
        lock(&self.inner.session).take();
        if let Some(monitor) = lock(&self.inner.monitor).take() {
            monitor.abort();
        }
        self.inner.peer_manager.clear_all_peers()?;
        if was_connected {
            emit0(&self.inner.connection_closed);
        }
        Ok(())
    }

    pub fn get_known_peers(&self) -> Result<Vec<UUID>, Error> {
        self.inner.peer_manager.get_known_peers()
    }

    pub async fn request_provision(&self) -> Result<bool, Error> {
        self.ensure_started(None).await?;
        Ok(true)
    }

    pub fn send_avatar_array(&self, avatars: Vec<UUID>) -> Result<bool, Error> {
        self.send_json(json!({
            "a": avatars.into_iter().map(|avatar| avatar.to_string()).collect::<Vec<_>>()
        }))
    }

    pub fn send_avatar_map(&self, avatars: Box<dyn Iterator<Item = UUID>>) -> Result<bool, Error> {
        let avatars: JsonMap<String, JsonValue> = avatars
            .map(|avatar| (avatar.to_string(), JsonValue::Object(JsonMap::new())))
            .collect();
        self.send_json(JsonValue::Object(avatars))
    }

    pub fn send_gain_map(&self, gain_map: HashMap<UUID, i32>) -> Result<bool, Error> {
        let values: JsonMap<String, JsonValue> = gain_map
            .into_iter()
            .map(|(peer, gain)| (peer.to_string(), JsonValue::from(gain.clamp(0, 220))))
            .collect();
        self.send_json(json!({"ug": values}))
    }

    pub fn send_join(&self, primary: Option<bool>) -> Result<bool, Error> {
        self.send_json(json!({"j": {"p": primary.unwrap_or(false)}}))
    }

    pub fn send_leave(&self) -> Result<bool, Error> {
        self.send_json(json!({"l": true}))
    }

    pub fn send_mute_map(&self, mute_map: HashMap<UUID, bool>) -> Result<bool, Error> {
        let values: JsonMap<String, JsonValue> = mute_map
            .into_iter()
            .map(|(peer, mute)| (peer.to_string(), JsonValue::Bool(mute)))
            .collect();
        self.send_json(json!({"m": values}))
    }

    pub fn send_position_with_vector3d_quaternion(
        &self,
        global_pos: libremetaverse_types::Vector3d,
        heading: libremetaverse_types::Quaternion,
    ) -> Result<bool, Error> {
        self.send_position_with_vector3d_quaternion_vector3d_quaternion(
            global_pos, heading, global_pos, heading,
        )
    }

    pub fn send_position_with_vector3d_quaternion_vector3d_quaternion(
        &self,
        global_pos: libremetaverse_types::Vector3d,
        heading: libremetaverse_types::Quaternion,
        camera_pos: libremetaverse_types::Vector3d,
        camera_heading: libremetaverse_types::Quaternion,
    ) -> Result<bool, Error> {
        let vector = |value: libremetaverse_types::Vector3d| {
            json!({
                "x": (value.x * 100.0).round() as i32,
                "y": (value.y * 100.0).round() as i32,
                "z": (value.z * 100.0).round() as i32
            })
        };
        let quaternion = |value: libremetaverse_types::Quaternion| {
            json!({
                "x": (value.x * 100.0).round() as i32,
                "y": (value.y * 100.0).round() as i32,
                "z": (value.z * 100.0).round() as i32,
                "w": (value.w * 100.0).round() as i32
            })
        };
        self.send_json(json!({
            "sp": vector(global_pos),
            "sh": quaternion(heading),
            "lp": vector(camera_pos),
            "lh": quaternion(camera_heading)
        }))
    }

    pub fn set_peer_gain(&self, peer_id: UUID, gain: i32) -> Result<bool, Error> {
        self.send_gain_map(HashMap::from([(peer_id, gain)]))
    }

    pub fn set_peer_mute(&self, peer_id: UUID, mute: bool) -> Result<bool, Error> {
        self.send_mute_map(HashMap::from([(peer_id, mute)]))
    }

    pub async fn start(&self, ct: Option<CancellationToken>) -> Result<(), Error> {
        self.ensure_started(ct).await
    }

    pub fn try_send_data_channel_string(&self, str: String) -> bool {
        self.send_text(str).unwrap_or(false)
    }

    pub fn channel_credentials(&self) -> Option<String> {
        lock(&self.inner.state).channel_credentials.clone()
    }

    pub fn set_channel_credentials(&mut self, value: Option<String>) {
        lock(&self.inner.state).channel_credentials = value;
    }

    pub fn channel_id(&self) -> Option<String> {
        lock(&self.inner.state).channel_id.clone()
    }

    pub fn set_channel_id(&mut self, value: Option<String>) {
        lock(&self.inner.state).channel_id = value;
    }

    pub fn connected(&self) -> bool {
        lock(&self.inner.state).connected
    }

    pub fn data_channel(&self) -> Option<transport::RTCDataChannel> {
        let state = lock(&self.inner.state);
        state.data_channel_ready.then(|| transport::RTCDataChannel {
            label: "SLData".into(),
            ready: true,
        })
    }

    pub fn is_primary(&self) -> bool {
        lock(&self.inner.state).is_primary
    }

    pub fn set_is_primary(&mut self, value: bool) {
        lock(&self.inner.state).is_primary = value;
    }

    pub fn parcel_local_id(&self) -> i32 {
        lock(&self.inner.state).parcel_local_id
    }

    pub fn set_parcel_local_id(&mut self, value: i32) {
        if value >= 0 {
            lock(&self.inner.state).parcel_local_id = value;
        }
    }

    pub fn sdp_local(&self) -> String {
        lock(&self.inner.state).sdp_local.clone()
    }

    pub fn sdp_remote(&self) -> String {
        lock(&self.inner.state).sdp_remote.clone()
    }

    pub fn session_id(&self) -> UUID {
        lock(&self.inner.state).session_id
    }

    pub fn set_session_id(&mut self, value: UUID) {
        lock(&self.inner.state).session_id = value;
    }

    pub fn target_simulator(&self) -> Option<libremetaverse::Simulator> {
        lock(&self.inner.state).target_simulator.clone()
    }

    pub fn set_target_simulator(&mut self, value: Option<libremetaverse::Simulator>) {
        lock(&self.inner.state).target_simulator = value;
    }

    fn send_json(&self, value: JsonValue) -> Result<bool, Error> {
        self.send_text(serde_json::to_string(&value).map_err(|_| Error::Argument)?)
    }

    fn send_text(&self, value: String) -> Result<bool, Error> {
        if value.len() > 64 * 1024 {
            return Err(Error::Argument);
        }
        let session = lock(&self.inner.session);
        let Some(session) = session.as_ref() else {
            return Ok(false);
        };
        session.try_send_data(value).map_err(web_rtc_error)
    }

    async fn ensure_started(&self, ct: Option<CancellationToken>) -> Result<(), Error> {
        loop {
            {
                let state = lock(&self.inner.state);
                if state.disposed {
                    return Err(Error::InvalidOperation);
                }
                if state.connected {
                    return Ok(());
                }
            }
            if self
                .inner
                .starting
                .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
                .is_ok()
            {
                break;
            }
            if let Some(token) = &ct {
                token.throw_if_cancellation_requested()?;
            }
            tokio::time::sleep(Duration::from_millis(10)).await;
        }
        let _starting = StartingGuard(&self.inner.starting);
        if let Some(token) = &ct {
            token.throw_if_cancellation_requested()?;
        }
        let (input_device, output_device) = self.inner.audio_device.selected_device_ids()?;
        let parcel_local_id = lock(&self.inner.state).parcel_local_id;
        let config = VoiceSessionConfig {
            bind_ip: discover_bind_ip(&self.inner.client),
            parcel_local_id: Some(parcel_local_id),
            timeout: Duration::from_secs(20),
            input_device,
            output_device,
        };
        let signaling: Arc<dyn VoiceSignaling> =
            Arc::new(GridVoiceSignaling::new(self.inner.client.clone()));
        let connect = WebRtcVoiceSession::connect(signaling, config);
        let result = if let Some(token) = &ct {
            tokio::select! {
                result = connect => result,
                () = token.cancelled() => return Err(Error::Cancelled),
            }
        } else {
            connect.await
        };
        let mut session = match result {
            Ok(session) => session,
            Err(error) => {
                emit(
                    &self.inner.reprovision_failed,
                    libremetaverse_types::compat::ExternalError(error.to_string()),
                );
                return Err(web_rtc_error(error));
            }
        };
        let events = session.take_events().ok_or(Error::InvalidOperation)?;
        {
            let mut state = lock(&self.inner.state);
            state.channel_credentials = session.credentials();
            state.channel_id = session.channel();
            state.connected = true;
            state.sdp_local = session.local_sdp();
            state.sdp_remote = session.remote_sdp();
            state.session_id = session.viewer_session();
        }
        *lock(&self.inner.session) = Some(session);
        let weak = Arc::downgrade(&self.inner);
        let monitor = tokio::spawn(async move {
            monitor_voice_events(weak, events).await;
        });
        *lock(&self.inner.monitor) = Some(monitor);
        emit0(&self.inner.reprovision_succeeded);
        Ok(())
    }
}

struct StartingGuard<'a>(&'a AtomicBool);

impl Drop for StartingGuard<'_> {
    fn drop(&mut self) {
        self.0.store(false, Ordering::Release);
    }
}

#[derive(Clone)]
struct SharedVoiceLogger(Arc<dyn IVoiceLogger>);

impl IVoiceLogger for SharedVoiceLogger {
    fn debug(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.0.debug(message, client)
    }

    fn error(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.0.error(message, client)
    }

    fn info(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.0.info(message, client)
    }

    fn log(
        &self,
        message: String,
        level: libremetaverse::logging::LogLevel,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.0.log(message, level, client)
    }

    fn warn(
        &self,
        message: String,
        client: Option<libremetaverse::GridClient>,
    ) -> Result<(), Error> {
        self.0.warn(message, client)
    }
}

async fn monitor_voice_events(
    inner: Weak<VoiceSessionInner>,
    mut events: tokio::sync::mpsc::Receiver<VoiceEvent>,
) {
    while let Some(event) = events.recv().await {
        let Some(inner) = inner.upgrade() else {
            return;
        };
        match event {
            VoiceEvent::Connected => {
                lock(&inner.state).connected = true;
                emit0(&inner.connection_ready);
            }
            VoiceEvent::DataChannelReady => {
                lock(&inner.state).data_channel_ready = true;
                emit0(&inner.data_ready);
            }
            VoiceEvent::PeerAudio(peer, state) => {
                let _ = inner.peer_manager.observe_audio(peer, state);
            }
            VoiceEvent::PeerPosition(peer, position) => {
                let typed = mapped_avatar_position(peer, position.clone());
                let _ = inner.peer_manager.observe_position(peer, &position);
                emit2(&inner.typed_position, peer, typed);
            }
            VoiceEvent::MuteMap(values) => inner.peer_manager.observe_mute_map(values),
            VoiceEvent::GainMap(values) => inner.peer_manager.observe_gain_map(values),
            VoiceEvent::Diagnostic(message) => {
                let _ = inner.logger.debug(message, None);
            }
            VoiceEvent::Closed => {
                let changed = {
                    let mut state = lock(&inner.state);
                    let changed = state.connected;
                    state.connected = false;
                    state.data_channel_ready = false;
                    changed
                };
                if changed {
                    emit0(&inner.connection_closed);
                }
                break;
            }
            VoiceEvent::IceState(_) | VoiceEvent::AudioFrame { .. } => {}
        }
    }
}

fn discover_bind_ip(client: &libremetaverse::GridClient) -> IpAddr {
    let remote = client
        .network()
        .current_sim()
        .map(|simulator| simulator.native_ip_end_point())
        .unwrap_or_else(|| SocketAddr::from(([1, 1, 1, 1], 53)));
    let bind = match remote {
        SocketAddr::V4(_) => SocketAddr::from(([0, 0, 0, 0], 0)),
        SocketAddr::V6(_) => SocketAddr::from(([0_u16; 8], 0)),
    };
    UdpSocket::bind(bind)
        .and_then(|socket| {
            socket.connect(remote)?;
            socket.local_addr()
        })
        .map(|address| address.ip())
        .ok()
        .filter(|address| !address.is_unspecified())
        .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST))
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum ManagerEvent {
    GainMap,
    MuteMap,
    GroupJoinFailed,
    GroupJoined,
    GroupLeft,
    P2pAccepted,
    P2pDeclined,
    P2pEnded,
    P2pFailed,
    P2pIncoming,
    P2pStarted,
    ParcelVoiceInfo,
    RegionChange,
    RegionCompleted,
    RegionFailed,
    ReprovisionFailed,
    ReprovisionSucceeded,
    VoicePermission,
    PeerAudio,
    PeerConnectionClosed,
    PeerConnectionReady,
    PeerJoined,
    PeerLeft,
    PeerList,
    PeerPosition,
    PeerPositionTyped,
}

type ErasedManagerHandler = Arc<dyn Fn(&dyn Any) + Send + Sync>;

#[derive(Default)]
struct ManagerEvents {
    next_id: AtomicU64,
    handlers: Arc<Mutex<HashMap<ManagerEvent, HashMap<u64, ErasedManagerHandler>>>>,
}

impl ManagerEvents {
    fn subscribe<T, F>(&self, event: ManagerEvent, handler: Option<F>) -> Subscription
    where
        T: Clone + Send + Sync + 'static,
        F: Fn(T) + Send + Sync + 'static,
    {
        let Some(handler) = handler else {
            return Subscription::detached();
        };
        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
        let handler: ErasedManagerHandler = Arc::new(move |value| {
            if let Some(value) = value.downcast_ref::<T>() {
                handler(value.clone());
            }
        });
        lock(&self.handlers)
            .entry(event)
            .or_default()
            .insert(id, handler);
        let handlers = Arc::downgrade(&self.handlers);
        Subscription::new(move || {
            if let Some(handlers) = handlers.upgrade()
                && let Some(event) = lock(&handlers).get_mut(&event)
            {
                event.remove(&id);
            }
        })
    }

    fn emit<T: Clone + Send + Sync + 'static>(&self, event: ManagerEvent, value: T) {
        let handlers: Vec<_> = lock(&self.handlers)
            .get(&event)
            .into_iter()
            .flat_map(|handlers| handlers.values().cloned())
            .collect();
        for handler in handlers {
            handler(&value);
        }
    }
}

#[derive(Clone)]
struct P2pCall {
    session: VoiceSession,
    incoming: bool,
    accepted: bool,
    started: std::time::SystemTime,
}

struct VoiceManagerInner {
    client: libremetaverse::GridClient,
    logger: Arc<dyn IVoiceLogger>,
    audio_device: AudioDevice,
    primary: Mutex<Option<VoiceSession>>,
    groups: Mutex<HashMap<UUID, VoiceSession>>,
    p2p: Mutex<HashMap<UUID, P2pCall>>,
    events: ManagerEvents,
    session_subscriptions: Mutex<Vec<Subscription>>,
    network_subscriptions: Mutex<Vec<Subscription>>,
    voice_allowed: AtomicBool,
    transitioning: AtomicBool,
}

pub struct VoiceManager {
    pub audio_device: AudioDevice,
    inner: Arc<VoiceManagerInner>,
}

impl VoiceManager {
    pub fn new(
        client: libremetaverse::GridClient,
        logger: Option<Box<dyn IVoiceLogger>>,
    ) -> Result<Self, Error> {
        let logger: Arc<dyn IVoiceLogger> = match logger {
            Some(logger) => Arc::from(logger),
            None => Arc::new(LibreMetaverseVoiceLogger::default()),
        };
        let audio_device =
            AudioDevice::new(Some(Box::new(SharedVoiceLogger(Arc::clone(&logger)))))?;
        let inner = Arc::new(VoiceManagerInner {
            client: client.clone(),
            logger,
            audio_device: audio_device.clone(),
            primary: Mutex::new(None),
            groups: Mutex::new(HashMap::new()),
            p2p: Mutex::new(HashMap::new()),
            events: ManagerEvents::default(),
            session_subscriptions: Mutex::new(Vec::new()),
            network_subscriptions: Mutex::new(Vec::new()),
            voice_allowed: AtomicBool::new(false),
            transitioning: AtomicBool::new(false),
        });
        let weak = Arc::downgrade(&inner);
        let changed = client
            .network()
            .native_subscribe_sim_changed(Arc::new(move |_| {
                let Some(inner) = weak.upgrade() else {
                    return;
                };
                inner.transitioning.store(true, Ordering::Release);
                inner.events.emit(ManagerEvent::RegionChange, ());
                let facade = Self {
                    audio_device: inner.audio_device.clone(),
                    inner,
                };
                let _ = facade.disconnect();
            }));
        lock(&inner.network_subscriptions).push(changed);
        Ok(Self {
            audio_device,
            inner,
        })
    }

    #[must_use]
    pub fn subscribe_gain_map_received(
        &self,
        handler: Option<Box<dyn Fn(HashMap<UUID, i32>) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(ManagerEvent::GainMap, handler)
    }

    #[must_use]
    pub fn subscribe_mute_map_received(
        &self,
        handler: Option<Box<dyn Fn(HashMap<UUID, bool>) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(ManagerEvent::MuteMap, handler)
    }

    #[must_use]
    pub fn subscribe_on_group_voice_join_failed(
        &self,
        handler: Option<
            Box<dyn Fn(UUID, libremetaverse_types::compat::ExternalError) + Send + Sync>,
        >,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::GroupJoinFailed,
            handler.map(|handler| move |(id, error)| handler(id, error)),
        )
    }

    #[must_use]
    pub fn subscribe_on_group_voice_joined(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::GroupJoined, handler)
    }

    #[must_use]
    pub fn subscribe_on_group_voice_left(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::GroupLeft, handler)
    }

    #[must_use]
    pub fn subscribe_on_p2_p_call_accepted(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::P2pAccepted, handler)
    }

    #[must_use]
    pub fn subscribe_on_p2_p_call_declined(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::P2pDeclined, handler)
    }

    #[must_use]
    pub fn subscribe_on_p2_p_call_ended(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(ManagerEvent::P2pEnded, handler)
    }

    #[must_use]
    pub fn subscribe_on_p2_p_call_failed(
        &self,
        handler: Option<
            Box<dyn Fn(UUID, libremetaverse_types::compat::ExternalError) + Send + Sync>,
        >,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::P2pFailed,
            handler.map(|handler| move |(id, error)| handler(id, error)),
        )
    }

    #[must_use]
    pub fn subscribe_on_p2_p_call_incoming(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::P2pIncoming, handler)
    }

    #[must_use]
    pub fn subscribe_on_p2_p_call_started(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::P2pStarted, handler)
    }

    #[must_use]
    pub fn subscribe_on_parcel_voice_info(
        &self,
        handler: Option<Box<dyn Fn(String, i32, String) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::ParcelVoiceInfo,
            handler.map(|handler| move |(region, parcel, uri)| handler(region, parcel, uri)),
        )
    }

    #[must_use]
    pub fn subscribe_on_region_change_detected(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::RegionChange,
            handler.map(|handler| move |()| handler()),
        )
    }

    #[must_use]
    pub fn subscribe_on_region_transition_completed(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::RegionCompleted,
            handler.map(|handler| move |()| handler()),
        )
    }

    #[must_use]
    pub fn subscribe_on_region_transition_failed(
        &self,
        handler: Option<Box<dyn Fn(libremetaverse_types::compat::ExternalError) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::RegionFailed, handler)
    }

    #[must_use]
    pub fn subscribe_on_reprovision_failed(
        &self,
        handler: Option<Box<dyn Fn(libremetaverse_types::compat::ExternalError) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::ReprovisionFailed, handler)
    }

    #[must_use]
    pub fn subscribe_on_reprovision_succeeded(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::ReprovisionSucceeded,
            handler.map(|handler| move |()| handler()),
        )
    }

    #[must_use]
    pub fn subscribe_on_voice_permission_changed(
        &self,
        handler: Option<Box<dyn Fn(bool) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::VoicePermission, handler)
    }

    #[must_use]
    pub fn subscribe_peer_audio_updated(
        &self,
        handler: Option<Box<dyn Fn(UUID, VoiceSessionPeerAudioState) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::PeerAudio,
            handler.map(|handler| move |(peer, state)| handler(peer, state)),
        )
    }

    #[must_use]
    pub fn subscribe_peer_connection_closed(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::PeerConnectionClosed,
            handler.map(|handler| move |()| handler()),
        )
    }

    #[must_use]
    pub fn subscribe_peer_connection_ready(
        &self,
        handler: Option<Box<dyn Fn() + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::PeerConnectionReady,
            handler.map(|handler| move |()| handler()),
        )
    }

    #[must_use]
    pub fn subscribe_peer_joined(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner
            .events
            .subscribe(ManagerEvent::PeerJoined, handler)
    }

    #[must_use]
    pub fn subscribe_peer_left(
        &self,
        handler: Option<Box<dyn Fn(UUID) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(ManagerEvent::PeerLeft, handler)
    }

    #[must_use]
    pub fn subscribe_peer_list_updated(
        &self,
        handler: Option<Box<dyn Fn(Vec<UUID>) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(ManagerEvent::PeerList, handler)
    }

    #[must_use]
    pub fn subscribe_peer_position_updated(
        &self,
        handler: Option<Box<dyn Fn(UUID, OSDMap) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::PeerPosition,
            handler.map(|handler| move |(peer, position)| handler(peer, position)),
        )
    }

    #[must_use]
    pub fn subscribe_peer_position_updated_typed(
        &self,
        handler: Option<Box<dyn Fn(UUID, VoiceSessionAvatarPosition) + Send + Sync>>,
    ) -> Subscription {
        self.inner.events.subscribe(
            ManagerEvent::PeerPositionTyped,
            handler.map(|handler| move |(peer, position)| handler(peer, position)),
        )
    }

    pub async fn accept_incoming_p2_p_call(&self, caller_id: UUID) -> Result<bool, Error> {
        let call = lock(&self.inner.p2p).get(&caller_id).cloned();
        let Some(call) = call else {
            return Ok(false);
        };
        if !call.incoming {
            return Ok(false);
        }
        call.session.start(None).await?;
        if let Some(call) = lock(&self.inner.p2p).get_mut(&caller_id) {
            call.accepted = true;
        }
        self.inner.events.emit(ManagerEvent::P2pAccepted, caller_id);
        Ok(true)
    }

    pub async fn accept_p2_p_call(
        &self,
        agent_id: UUID,
        channel_uri: String,
        credentials: String,
    ) -> Result<bool, Error> {
        if channel_uri.is_empty()
            || credentials.is_empty()
            || channel_uri.len() > 64 * 1024
            || credentials.len() > 64 * 1024
        {
            return Err(Error::Argument);
        }
        if lock(&self.inner.p2p).contains_key(&agent_id) {
            return self.accept_incoming_p2_p_call(agent_id).await;
        }
        let mut session = self.make_session(false, 0)?;
        session.set_channel_id(Some(channel_uri));
        session.set_channel_credentials(Some(credentials));
        self.wire_session(&session);
        self.inner.events.emit(ManagerEvent::P2pIncoming, agent_id);
        session.start(None).await?;
        lock(&self.inner.p2p).insert(
            agent_id,
            P2pCall {
                session,
                incoming: true,
                accepted: true,
                started: std::time::SystemTime::now(),
            },
        );
        self.inner.events.emit(ManagerEvent::P2pAccepted, agent_id);
        Ok(true)
    }

    pub async fn connect_primary_region(&self) -> Result<bool, Error> {
        self.inner.transitioning.store(true, Ordering::Release);
        let allowed = Self::is_voice_allowed_at(self.inner.client.clone())?;
        let previous = self.inner.voice_allowed.swap(allowed, Ordering::AcqRel);
        if previous != allowed {
            self.inner
                .events
                .emit(ManagerEvent::VoicePermission, allowed);
        }
        if !allowed {
            self.inner.transitioning.store(false, Ordering::Release);
            return Ok(false);
        }
        if lock(&self.inner.primary)
            .as_ref()
            .is_some_and(VoiceSession::connected)
        {
            self.inner.transitioning.store(false, Ordering::Release);
            return Ok(true);
        }
        let target = self.inner.client.network().current_sim();
        let session = VoiceSession::new_native(
            self.inner.client.clone(),
            self.audio_device.clone(),
            Arc::clone(&self.inner.logger),
            true,
            0,
            target,
        )?;
        self.wire_session(&session);
        if let Err(error) = session.start(None).await {
            self.inner.transitioning.store(false, Ordering::Release);
            self.inner.events.emit(
                ManagerEvent::RegionFailed,
                libremetaverse_types::compat::ExternalError(format!("{error:?}")),
            );
            return Err(error);
        }
        *lock(&self.inner.primary) = Some(session);
        self.inner.transitioning.store(false, Ordering::Release);
        self.inner.events.emit(ManagerEvent::RegionCompleted, ());
        Ok(true)
    }

    pub fn decline_incoming_p2_p_call(&self, caller_id: UUID) -> Result<(), Error> {
        self.decline_p2_p_call(caller_id)
    }

    pub fn decline_p2_p_call(&self, agent_id: UUID) -> Result<(), Error> {
        if let Some(call) = lock(&self.inner.p2p).remove(&agent_id) {
            call.session.dispose()?;
            self.inner.events.emit(ManagerEvent::P2pDeclined, agent_id);
        }
        Ok(())
    }

    pub fn disconnect(&self) -> Result<(), Error> {
        if let Some(session) = lock(&self.inner.primary).take() {
            session.dispose()?;
        }
        let groups: Vec<_> = lock(&self.inner.groups).drain().collect();
        for (group, session) in groups {
            session.dispose()?;
            self.inner.events.emit(ManagerEvent::GroupLeft, group);
        }
        let calls: Vec<_> = lock(&self.inner.p2p).drain().collect();
        for (agent, call) in calls {
            call.session.dispose()?;
            self.inner.events.emit(ManagerEvent::P2pEnded, agent);
        }
        self.inner.transitioning.store(false, Ordering::Release);
        Ok(())
    }

    pub async fn end_p2_p_call(&self, agent_id: UUID) -> Result<(), Error> {
        let call = lock(&self.inner.p2p).remove(&agent_id);
        if let Some(call) = call {
            call.session.close_session().await?;
            self.inner.events.emit(ManagerEvent::P2pEnded, agent_id);
        }
        Ok(())
    }

    pub fn get_active_group_voice_sessions(&self) -> Result<Vec<UUID>, Error> {
        let mut sessions: Vec<_> = lock(&self.inner.groups).keys().copied().collect();
        sessions.sort_by_key(ToString::to_string);
        Ok(sessions)
    }

    pub fn get_active_p2_p_calls(&self) -> Result<Vec<UUID>, Error> {
        let mut calls: Vec<_> = lock(&self.inner.p2p).keys().copied().collect();
        calls.sort_by_key(ToString::to_string);
        Ok(calls)
    }

    pub fn get_all_known_peers(&self) -> Result<Vec<UUID>, Error> {
        let mut peers = std::collections::HashSet::new();
        if let Some(primary) = lock(&self.inner.primary).as_ref() {
            peers.extend(primary.get_known_peers()?);
        }
        for session in lock(&self.inner.groups).values() {
            peers.extend(session.get_known_peers()?);
        }
        for call in lock(&self.inner.p2p).values() {
            peers.extend(call.session.get_known_peers()?);
        }
        let mut peers: Vec<_> = peers.into_iter().collect();
        peers.sort_by_key(ToString::to_string);
        Ok(peers)
    }

    pub fn get_group_voice_peers(&self, group_id: UUID) -> Result<Vec<UUID>, Error> {
        lock(&self.inner.groups)
            .get(&group_id)
            .map_or_else(|| Ok(Vec::new()), VoiceSession::get_known_peers)
    }

    pub fn get_known_peers(&self) -> Result<Vec<UUID>, Error> {
        lock(&self.inner.primary)
            .as_ref()
            .map_or_else(|| Ok(Vec::new()), VoiceSession::get_known_peers)
    }

    pub fn get_p2_p_call_info(
        &self,
        agent_id: UUID,
    ) -> Result<(bool, bool, Option<Option<std::time::SystemTime>>), Error> {
        Ok(lock(&self.inner.p2p)
            .get(&agent_id)
            .map_or((false, false, None), |call| {
                (true, call.accepted, Some(Some(call.started)))
            }))
    }

    pub fn is_in_group_voice(&self, group_id: UUID) -> Result<bool, Error> {
        Ok(lock(&self.inner.groups).contains_key(&group_id))
    }

    pub fn is_in_p2_p_call(&self, agent_id: UUID) -> Result<bool, Error> {
        Ok(lock(&self.inner.p2p).contains_key(&agent_id))
    }

    pub fn is_voice_allowed_at(client: libremetaverse::GridClient) -> Result<bool, Error> {
        if !client.network().connected() {
            return Ok(false);
        }
        let Some(simulator) = client.network().current_sim() else {
            return Ok(false);
        };
        Ok(simulator
            .native_capability_uri(VoiceSession::PROVISION_VOICE_ACCOUNT_CAP)?
            .is_some()
            && simulator
                .native_capability_uri(VoiceSession::VOICE_SIGNALING_CAP)?
                .is_some())
    }

    pub async fn join_conference_voice(&self, session_id: UUID) -> Result<bool, Error> {
        self.join_group_voice(session_id).await
    }

    pub async fn join_group_voice(&self, group_id: UUID) -> Result<bool, Error> {
        if lock(&self.inner.groups).contains_key(&group_id) {
            return Ok(true);
        }
        let mut session = self.make_session(false, 0)?;
        session.set_channel_id(Some(group_id.to_string()));
        self.wire_session(&session);
        if let Err(error) = session.start(None).await {
            self.inner.events.emit(
                ManagerEvent::GroupJoinFailed,
                (
                    group_id,
                    libremetaverse_types::compat::ExternalError(format!("{error:?}")),
                ),
            );
            return Err(error);
        }
        lock(&self.inner.groups).insert(group_id, session);
        self.inner.events.emit(ManagerEvent::GroupJoined, group_id);
        Ok(true)
    }

    pub async fn leave_conference_voice(&self, session_id: UUID) -> Result<(), Error> {
        self.leave_group_voice(session_id).await
    }

    pub async fn leave_group_voice(&self, group_id: UUID) -> Result<(), Error> {
        let session = lock(&self.inner.groups).remove(&group_id);
        if let Some(session) = session {
            session.close_session().await?;
            self.inner.events.emit(ManagerEvent::GroupLeft, group_id);
        }
        Ok(())
    }

    pub fn play_raw_file(
        &self,
        path: String,
        channels: Option<i32>,
        sample_rate: Option<i32>,
        loop_: Option<bool>,
    ) -> Result<(), Error> {
        let bytes = std::fs::read(path).map_err(io_error)?;
        let frames = encode_pcm_48k_mono(&normalize_pcm(
            &pcm_bytes(&bytes)?,
            channels.unwrap_or(1),
            sample_rate.unwrap_or(48_000),
        )?)
        .map_err(audio_error)?;
        self.audio_device
            .start_encoded_file_frames(frames, loop_.unwrap_or(false))
    }

    pub fn play_wav_as_mic(&self, path: String, loop_: Option<bool>) -> Result<(), Error> {
        self.audio_device.start_file_playback(path, loop_)
    }

    pub async fn request_parcel_voice_info(&self) -> Result<bool, Error> {
        if !self.inner.client.network().connected() {
            return Ok(false);
        }
        let signaling = GridVoiceSignaling::new(self.inner.client.clone());
        let result = match signaling
            .post("ParcelVoiceInfoRequest", OSD::Map(HashMap::new()))
            .await
        {
            Ok(result) => result,
            Err(WebRtcError::Signaling(message)) if message.contains("missing") => {
                return Ok(false);
            }
            Err(error) => return Err(web_rtc_error(error)),
        };
        let region = result
            .get("region_name")
            .and_then(|value| value.as_string().ok())
            .unwrap_or_default();
        let parcel = result
            .get("parcel_local_id")
            .and_then(|value| value.as_integer().ok())
            .unwrap_or_default();
        let uri = result
            .get("voice_credentials")
            .and_then(|value| match value {
                OSD::Map(credentials) => credentials.get("channel_uri"),
                _ => None,
            })
            .and_then(|value| value.as_string().ok())
            .unwrap_or_default();
        self.inner
            .events
            .emit(ManagerEvent::ParcelVoiceInfo, (region, parcel, uri));
        Ok(true)
    }

    pub fn send_data_channel_message(&self, message: String) -> Result<bool, Error> {
        lock(&self.inner.primary)
            .as_ref()
            .map_or(Ok(false), |session| session.send_text(message))
    }

    pub fn set_group_voice_peer_gain(
        &self,
        group_id: UUID,
        peer_id: UUID,
        gain: i32,
    ) -> Result<bool, Error> {
        lock(&self.inner.groups)
            .get(&group_id)
            .map_or(Ok(false), |session| session.set_peer_gain(peer_id, gain))
    }

    pub fn set_group_voice_peer_mute(
        &self,
        group_id: UUID,
        peer_id: UUID,
        mute: bool,
    ) -> Result<bool, Error> {
        lock(&self.inner.groups)
            .get(&group_id)
            .map_or(Ok(false), |session| session.set_peer_mute(peer_id, mute))
    }

    pub fn set_p2_p_call_gain(&self, agent_id: UUID, gain: i32) -> Result<bool, Error> {
        lock(&self.inner.p2p)
            .get(&agent_id)
            .map_or(Ok(false), |call| call.session.set_peer_gain(agent_id, gain))
    }

    pub fn set_p2_p_call_mute(&self, agent_id: UUID, mute: bool) -> Result<bool, Error> {
        lock(&self.inner.p2p)
            .get(&agent_id)
            .map_or(Ok(false), |call| call.session.set_peer_mute(agent_id, mute))
    }

    pub fn set_peer_gain(&self, peer_id: UUID, gain: i32) -> Result<(), Error> {
        if let Some(primary) = lock(&self.inner.primary).as_ref() {
            let _ = primary.set_peer_gain(peer_id, gain)?;
        }
        for session in lock(&self.inner.groups).values() {
            let _ = session.set_peer_gain(peer_id, gain)?;
        }
        for call in lock(&self.inner.p2p).values() {
            let _ = call.session.set_peer_gain(peer_id, gain)?;
        }
        Ok(())
    }

    pub fn set_peer_mute(&self, peer_id: UUID, mute: bool) -> Result<(), Error> {
        if let Some(primary) = lock(&self.inner.primary).as_ref() {
            let _ = primary.set_peer_mute(peer_id, mute)?;
        }
        for session in lock(&self.inner.groups).values() {
            let _ = session.set_peer_mute(peer_id, mute)?;
        }
        for call in lock(&self.inner.p2p).values() {
            let _ = call.session.set_peer_mute(peer_id, mute)?;
        }
        Ok(())
    }

    pub async fn start_p2_p_call(&self, agent_id: UUID) -> Result<bool, Error> {
        if lock(&self.inner.p2p).contains_key(&agent_id) {
            return Ok(true);
        }
        let session = self.make_session(false, 0)?;
        self.wire_session(&session);
        if let Err(error) = session.start(None).await {
            self.inner.events.emit(
                ManagerEvent::P2pFailed,
                (
                    agent_id,
                    libremetaverse_types::compat::ExternalError(format!("{error:?}")),
                ),
            );
            return Err(error);
        }
        lock(&self.inner.p2p).insert(
            agent_id,
            P2pCall {
                session,
                incoming: false,
                accepted: true,
                started: std::time::SystemTime::now(),
            },
        );
        self.inner.events.emit(ManagerEvent::P2pStarted, agent_id);
        Ok(true)
    }

    pub fn stop_raw_file(&self) -> Result<(), Error> {
        self.audio_device.stop_raw_pcm_stream()
    }

    pub fn stop_wav_as_mic(&self) -> Result<(), Error> {
        self.audio_device.stop_file_playback()
    }

    pub fn channel_credentials(&self) -> String {
        lock(&self.inner.primary)
            .as_ref()
            .and_then(VoiceSession::channel_credentials)
            .unwrap_or_default()
    }

    pub fn channel_id(&self) -> String {
        lock(&self.inner.primary)
            .as_ref()
            .and_then(VoiceSession::channel_id)
            .unwrap_or_default()
    }

    pub fn connected(&self) -> bool {
        lock(&self.inner.primary)
            .as_ref()
            .is_some_and(VoiceSession::connected)
    }

    pub fn is_transitioning(&self) -> bool {
        self.inner.transitioning.load(Ordering::Acquire)
    }

    pub fn sdp_local(&self) -> String {
        lock(&self.inner.primary)
            .as_ref()
            .map(VoiceSession::sdp_local)
            .unwrap_or_default()
    }

    pub fn sdp_remote(&self) -> String {
        lock(&self.inner.primary)
            .as_ref()
            .map(VoiceSession::sdp_remote)
            .unwrap_or_default()
    }

    pub fn session_id(&self) -> UUID {
        lock(&self.inner.primary)
            .as_ref()
            .map(VoiceSession::session_id)
            .unwrap_or_else(UUID::zero)
    }

    pub fn voice_allowed_here(&self) -> bool {
        self.inner.voice_allowed.load(Ordering::Acquire)
    }

    pub fn set_voice_allowed_here(&mut self, value: bool) {
        let previous = self.inner.voice_allowed.swap(value, Ordering::AcqRel);
        if previous != value {
            self.inner.events.emit(ManagerEvent::VoicePermission, value);
        }
    }

    fn make_session(&self, primary: bool, parcel: i32) -> Result<VoiceSession, Error> {
        VoiceSession::new_native(
            self.inner.client.clone(),
            self.audio_device.clone(),
            Arc::clone(&self.inner.logger),
            primary,
            parcel,
            self.inner.client.network().current_sim(),
        )
    }

    #[allow(clippy::too_many_lines)]
    fn wire_session(&self, session: &VoiceSession) {
        let mut subscriptions = lock(&self.inner.session_subscriptions);
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_gain_map_received(Some(Box::new(move |values| {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::GainMap, values);
                }
            }))),
        );
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_mute_map_received(Some(Box::new(move |values| {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::MuteMap, values);
                }
            }))),
        );
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(session.subscribe_on_peer_audio_updated(Some(Box::new(
            move |peer, state| {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::PeerAudio, (peer, state));
                }
            },
        ))));
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(session.subscribe_on_peer_connection_closed(Some(Box::new(
            move || {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::PeerConnectionClosed, ());
                }
            },
        ))));
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_peer_connection_ready(Some(Box::new(move || {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::PeerConnectionReady, ());
                }
            }))),
        );
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(session.subscribe_on_peer_joined(Some(Box::new(move |peer| {
            if let Some(inner) = weak.upgrade() {
                inner.events.emit(ManagerEvent::PeerJoined, peer);
            }
        }))));
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(session.subscribe_on_peer_left(Some(Box::new(move |peer| {
            if let Some(inner) = weak.upgrade() {
                inner.events.emit(ManagerEvent::PeerLeft, peer);
            }
        }))));
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_peer_list_updated(Some(Box::new(move |peers| {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::PeerList, peers);
                }
            }))),
        );
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(session.subscribe_on_peer_position_updated(Some(Box::new(
            move |peer, position| {
                if let Some(inner) = weak.upgrade() {
                    inner
                        .events
                        .emit(ManagerEvent::PeerPosition, (peer, position));
                }
            },
        ))));
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_peer_position_updated_typed(Some(Box::new(
                move |peer, position| {
                    if let Some(inner) = weak.upgrade() {
                        inner
                            .events
                            .emit(ManagerEvent::PeerPositionTyped, (peer, position));
                    }
                },
            ))),
        );
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_reprovision_failed(Some(Box::new(move |error| {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::ReprovisionFailed, error);
                }
            }))),
        );
        let weak = Arc::downgrade(&self.inner);
        subscriptions.push(
            session.subscribe_on_reprovision_succeeded(Some(Box::new(move || {
                if let Some(inner) = weak.upgrade() {
                    inner.events.emit(ManagerEvent::ReprovisionSucceeded, ());
                }
            }))),
        );
    }
}

impl Drop for VoiceManager {
    fn drop(&mut self) {
        if Arc::strong_count(&self.inner) == 1 {
            let _ = self.disconnect();
            let _ = self.audio_device.dispose();
        }
    }
}

fn set_playback_inner(inner: &AudioDeviceInner, active: bool) {
    let changed = {
        let mut state = lock(&inner.state);
        let changed = state.playback_active != active;
        state.playback_active = active;
        changed
    };
    if changed {
        emit(&inner.playback_handlers, active);
    }
}

fn validate_percent(percent: i32) -> Result<(), Error> {
    if (0..=100).contains(&percent) {
        Ok(())
    } else {
        Err(Error::Argument)
    }
}

fn pcm_bytes(bytes: &[u8]) -> Result<Vec<i16>, Error> {
    if bytes.len() % 2 != 0 {
        return Err(Error::Argument);
    }
    Ok(bytes
        .chunks_exact(2)
        .map(|pair| i16::from_le_bytes([pair[0], pair[1]]))
        .collect())
}

fn normalize_pcm(samples: &[i16], channels: i32, sample_rate: i32) -> Result<Vec<i16>, Error> {
    let channels = usize::try_from(channels).map_err(|_| Error::Argument)?;
    let sample_rate = usize::try_from(sample_rate).map_err(|_| Error::Argument)?;
    if !(1..=32).contains(&channels)
        || !(8_000..=192_000).contains(&sample_rate)
        || samples.len() % channels != 0
    {
        return Err(Error::Argument);
    }
    let mono: Vec<f32> = samples
        .chunks_exact(channels)
        .map(|frame| frame.iter().map(|value| f32::from(*value)).sum::<f32>() / channels as f32)
        .collect();
    if mono.is_empty() {
        return Err(Error::Argument);
    }
    let output_len = mono.len().saturating_mul(48_000).div_ceil(sample_rate);
    let mut output = Vec::with_capacity(output_len);
    for output_index in 0..output_len {
        let position = output_index as f64 * sample_rate as f64 / 48_000_f64;
        let left = position.floor() as usize;
        let fraction = (position - left as f64) as f32;
        let a = mono[left.min(mono.len() - 1)];
        let b = mono[(left + 1).min(mono.len() - 1)];
        output
            .push((a + (b - a) * fraction).clamp(f32::from(i16::MIN), f32::from(i16::MAX)) as i16);
    }
    Ok(output)
}

fn subscribe<T: ?Sized + Send + Sync + 'static>(
    next: &AtomicU64,
    handlers: &Arc<Mutex<HashMap<u64, Arc<T>>>>,
    handler: Option<Box<T>>,
) -> Subscription {
    let Some(handler) = handler else {
        return Subscription::detached();
    };
    let id = next.fetch_add(1, Ordering::Relaxed);
    lock(handlers).insert(id, Arc::from(handler));
    let handlers: Weak<Mutex<HashMap<u64, Arc<T>>>> = Arc::downgrade(handlers);
    Subscription::new(move || {
        if let Some(handlers) = handlers.upgrade() {
            lock(&handlers).remove(&id);
        }
    })
}

fn emit<T: Clone>(handlers: &Mutex<HashMap<u64, Arc<dyn Fn(T) + Send + Sync>>>, value: T) {
    let callbacks: Vec<_> = lock(handlers).values().cloned().collect();
    for callback in callbacks {
        callback(value.clone());
    }
}

fn emit2<A: Clone, B: Clone>(
    handlers: &Mutex<HashMap<u64, Arc<dyn Fn(A, B) + Send + Sync>>>,
    first: A,
    second: B,
) {
    let callbacks: Vec<_> = lock(handlers).values().cloned().collect();
    for callback in callbacks {
        callback(first.clone(), second.clone());
    }
}

fn emit0(handlers: &Mutex<HashMap<u64, Arc<dyn Fn() + Send + Sync>>>) {
    let callbacks: Vec<_> = lock(handlers).values().cloned().collect();
    for callback in callbacks {
        callback();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::mpsc;

    #[test]
    fn logger_and_exception_preserve_native_diagnostics() {
        let logger = LibreMetaverseVoiceLogger::new().unwrap();
        logger.info("voice ready".into(), None).unwrap();
        assert_eq!(
            logger.records(),
            [VoiceLogRecord {
                level: 2,
                message: "voice ready".into()
            }]
        );
        let error = VoiceException::new_with_string_boolean("declined".into(), true).unwrap();
        assert_eq!(error.to_string(), "declined");
        assert!(error.is_definitive_rejection());
    }

    #[test]
    fn virtual_audio_encodes_pcm_decodes_rtp_and_tracks_state() {
        let mut device = AudioDevice::new(None).unwrap();
        assert!(device.is_available());
        assert!(!device.get_playback_devices().unwrap().is_empty());
        assert!(!device.get_recording_devices().unwrap().is_empty());
        let (sender, receiver) = mpsc::channel();
        let _subscription = device.subscribe_on_audio_source_encoded_sample(Some(Box::new(
            move |duration, packet| sender.send((duration, packet)).unwrap(),
        )));
        device
            .feed_pcm_samples(vec![0; 960], Some(1), Some(48_000))
            .unwrap();
        let (duration, packet) = receiver.recv().unwrap();
        assert_eq!(duration, 960);
        assert!(!packet.is_empty());
        device.play_rtp_packet(7, packet).unwrap();
        assert!(device.decoded_samples() > 0);

        device
            .enable_audio_processing(
                Some(true),
                Some(true),
                Some(false),
                Some(true),
                Some(audio::NoiseSuppressionLevel(2)),
                Some(false),
                Some(40),
            )
            .unwrap();
        assert_eq!(
            device.audio_processing_snapshot(),
            Some((true, true, false, true, 2, false, 40))
        );
        device.start_recording().unwrap();
        assert!(device.recording_active());
        device.stop_recording().unwrap();
        device.set_mic_mute(true);
        assert!(device.mic_mute());
        device.dispose().unwrap();
        assert!(!device.is_available());
    }

    #[test]
    fn peer_manager_tracks_ssrc_lifecycle_maps_and_ping() {
        let audio = AudioDevice::new(None).unwrap();
        let manager = PeerManager::new(
            audio,
            libremetaverse::GridClient::new().unwrap(),
            Box::new(LibreMetaverseVoiceLogger::default()),
        )
        .unwrap();
        let peer = UUID::new_with_string("11111111-2222-4333-8444-555555555555".into()).unwrap();
        let (joined_sender, joined_receiver) = mpsc::channel();
        let _joined = manager.subscribe_peer_joined(Some(Box::new(move |peer| {
            joined_sender.send(peer).unwrap();
        })));
        let (audio_sender, audio_receiver) = mpsc::channel();
        let _audio = manager.subscribe_peer_audio_updated(Some(Box::new(move |peer, state| {
            audio_sender.send((peer, state)).unwrap();
        })));
        let sent = Arc::new(Mutex::new(Vec::new()));
        let output = Arc::clone(&sent);
        manager
            .process_json_element(
                crate::signaling::JsonValue::from_inner(json!({"ping": true})),
                Box::new(move |line| {
                    lock(&output).push(line);
                    true
                }),
                UUID::zero(),
            )
            .unwrap();
        manager
            .process_json_element(
                crate::signaling::JsonValue::from_inner(
                    json!({peer.to_string(): {"s": 77, "p": 31, "V": true, "m": false}}),
                ),
                Box::new(|_| true),
                UUID::zero(),
            )
            .unwrap();
        assert_eq!(joined_receiver.recv().unwrap(), peer);
        let (updated_peer, state) = audio_receiver.recv().unwrap();
        assert_eq!(updated_peer, peer);
        assert_eq!(state.power(), Some(Some(31)));
        assert_eq!(state.voice_active(), Some(Some(true)));
        assert_eq!(manager.get_known_peers().unwrap(), [peer]);
        let mut ssrc = 0;
        assert!(manager.try_get_ssrc(peer, &mut ssrc));
        assert_eq!(ssrc, 77);
        let mut mapped_peer = UUID::zero();
        assert!(manager.try_get_peer_for_ssrc(77, &mut mapped_peer));
        assert_eq!(mapped_peer, peer);
        assert!(lock(&sent)[0].contains("pong"));

        manager
            .process_json_element(
                crate::signaling::JsonValue::from_inner(json!({peer.to_string(): {"l": true}})),
                Box::new(|_| true),
                UUID::zero(),
            )
            .unwrap();
        assert!(manager.get_known_peers().unwrap().is_empty());
    }
}
