//! Native agent movement, camera, teleport, and region-crossing behavior.

#![allow(clippy::cast_possible_truncation)] // C# camera math narrows doubles to singles.
#![allow(clippy::cast_precision_loss)] // C# compares region dimensions in single precision.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API.
#![allow(clippy::needless_pass_by_value)] // Owned parameters mirror mapped C# signatures.
#![allow(clippy::too_many_arguments)] // AgentUpdate is a protocol-shaped method.

use crate::agent_manager::{AgentManager, AgentManagerInner, EventRegistry};
use crate::interfaces::IMessage;
use crate::packet_catalog::GeneratedPacket;
use crate::packets::{
    AgentFOVPacket, AgentMovementCompletePacket, AgentRequestSitPacket, AgentSitPacket,
    AgentUpdatePacket, CameraConstraintPacket, CrossedRegionPacket, GenericMessagePacket,
    GenericMessagePacketParamListBlock, PacketType, SetAlwaysRunPacket, StartLurePacket,
    StartLurePacketTargetDataBlock, TeleportCancelPacket, TeleportFailedPacket,
    TeleportFinishPacket, TeleportLandmarkRequestPacket, TeleportLocalPacket,
    TeleportLocationRequestPacket, TeleportLureRequestPacket, TeleportProgressPacket,
    TeleportStartPacket,
};
use crate::{
    AgentFlags, AgentManagerControlFlags, AgentManagerCrossingFailureReason,
    AgentManagerCrossingState, AgentState, BorderCrossingDirection, Error, GridClient,
    GridLayerType, InstantMessageDialog, InstantMessageOnline, NetworkManager, Simulator,
    TeleportFlags, TeleportStatus,
};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use libremetaverse_types::{Quaternion, UUID, Vector3, Vector3d, Vector4};
use std::collections::HashMap;
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex, RwLock, Weak};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant, SystemTime};

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

fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
    value
        .read()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
    value
        .write()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn wire_string(mut bytes: Vec<u8>) -> String {
    while bytes.last() == Some(&0) {
        bytes.pop();
    }
    String::from_utf8_lossy(&bytes).into_owned()
}

fn decode_packet<T: GeneratedPacket>(data: &[u8]) -> Result<T, Error> {
    let mut packet = T::new_generated();
    let mut position = 0_i32;
    let mut packet_end = i32::try_from(data.len()).map_err(|_| Error::Argument)? - 1;
    let mut zero_buffer = vec![0_u8; crate::UdpTransportConfig::default().max_decoded_packet_size];
    packet.decode_from_bytes(
        data,
        &mut position,
        &mut packet_end,
        Some(zero_buffer.as_mut_slice()),
    )?;
    Ok(packet)
}

fn send_encoded(
    simulator: &Simulator,
    packet_type: PacketType,
    mut data: Vec<u8>,
    reliable: Option<bool>,
) -> Result<(), Error> {
    if let Some(reliable) = reliable {
        let flags = data.first_mut().ok_or(Error::Argument)?;
        if reliable {
            *flags |= crate::Helpers::MSG_RELIABLE;
        } else {
            *flags &= !crate::Helpers::MSG_RELIABLE;
        }
    }
    let length = i32::try_from(data.len()).map_err(|_| Error::Argument)?;
    let zerocoded = data
        .first()
        .is_some_and(|flags| flags & crate::Helpers::MSG_ZEROCODED != 0);
    simulator.native_send_packet_data(data, length, packet_type, zerocoded)
}

fn normalize(value: Vector3) -> Vector3 {
    let length_squared = value.x * value.x + value.y * value.y + value.z * value.z;
    if length_squared > 0.000_000_1 {
        let inverse = 1.0 / length_squared.sqrt();
        Vector3 {
            x: value.x * inverse,
            y: value.y * inverse,
            z: value.z * inverse,
        }
    } else {
        Vector3::zero()
    }
}

fn cross(lhs: Vector3, rhs: Vector3) -> Vector3 {
    Vector3 {
        x: lhs.y * rhs.z - lhs.z * rhs.y,
        y: lhs.z * rhs.x - lhs.x * rhs.z,
        z: lhs.x * rhs.y - lhs.y * rhs.x,
    }
}

fn dot(lhs: Vector3, rhs: Vector3) -> f32 {
    lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z
}

fn subtract(lhs: Vector3, rhs: Vector3) -> Vector3 {
    Vector3 {
        x: lhs.x - rhs.x,
        y: lhs.y - rhs.y,
        z: lhs.z - rhs.z,
    }
}

fn add(lhs: Vector3, rhs: Vector3) -> Vector3 {
    Vector3 {
        x: lhs.x + rhs.x,
        y: lhs.y + rhs.y,
        z: lhs.z + rhs.z,
    }
}

fn scale(value: Vector3, factor: f32) -> Vector3 {
    Vector3 {
        x: value.x * factor,
        y: value.y * factor,
        z: value.z * factor,
    }
}

fn rotate_axis(value: Vector3, axis: Vector3, angle: f32) -> Vector3 {
    let axis = normalize(axis);
    let cosine = angle.cos();
    let sine = angle.sin();
    add(
        add(scale(value, cosine), scale(cross(axis, value), sine)),
        scale(axis, dot(axis, value) * (1.0 - cosine)),
    )
}

#[derive(Clone, Debug, PartialEq)]
pub struct TeleportEventArgs {
    message: String,
    status: TeleportStatus,
    flags: TeleportFlags,
}

impl TeleportEventArgs {
    pub fn new(
        message: String,
        status: TeleportStatus,
        flags: TeleportFlags,
    ) -> Result<Self, Error> {
        Ok(Self {
            message,
            status,
            flags,
        })
    }

    #[must_use]
    pub const fn flags(&self) -> TeleportFlags {
        self.flags
    }

    #[must_use]
    pub fn message(&self) -> String {
        self.message.clone()
    }

    #[must_use]
    pub const fn status(&self) -> TeleportStatus {
        self.status
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CameraConstraintEventArgs {
    collide_plane: Vector4,
}

impl CameraConstraintEventArgs {
    pub const fn new(collide_plane: Vector4) -> Result<Self, Error> {
        Ok(Self { collide_plane })
    }

    #[must_use]
    pub const fn collide_plane(&self) -> Vector4 {
        self.collide_plane
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AvatarSitResponseEventArgs {
    object_id: UUID,
    auto_pilot: bool,
    camera_at_offset: Vector3,
    camera_eye_offset: Vector3,
    force_mouselook: bool,
    sit_position: Vector3,
    sit_rotation: Quaternion,
}

#[derive(Clone, Debug)]
pub struct RegionCrossedEventArgs {
    old_simulator: Option<Simulator>,
    new_simulator: Option<Simulator>,
}

#[derive(Clone, Debug)]
pub struct RegionCrossingPredictionEventArgs {
    current_simulator: Simulator,
    direction: BorderCrossingDirection,
    time_until_crossing: f32,
}

impl RegionCrossingPredictionEventArgs {
    pub const fn new(
        current_simulator: Simulator,
        direction: BorderCrossingDirection,
        time_until_crossing: f32,
    ) -> Result<Self, Error> {
        Ok(Self {
            current_simulator,
            direction,
            time_until_crossing,
        })
    }

    #[must_use]
    pub fn current_simulator(&self) -> Simulator {
        self.current_simulator.clone()
    }

    #[must_use]
    pub const fn direction(&self) -> BorderCrossingDirection {
        self.direction
    }

    #[must_use]
    pub const fn time_until_crossing(&self) -> f32 {
        self.time_until_crossing
    }
}

impl RegionCrossedEventArgs {
    pub const fn new(
        old_simulator: Option<Simulator>,
        new_simulator: Option<Simulator>,
    ) -> Result<Self, Error> {
        Ok(Self {
            old_simulator,
            new_simulator,
        })
    }

    #[must_use]
    pub fn new_simulator(&self) -> Option<Simulator> {
        self.new_simulator.clone()
    }

    #[must_use]
    pub fn old_simulator(&self) -> Option<Simulator> {
        self.old_simulator.clone()
    }
}

impl AvatarSitResponseEventArgs {
    pub const fn new(
        object_id: UUID,
        auto_pilot: bool,
        camera_at_offset: Vector3,
        camera_eye_offset: Vector3,
        force_mouselook: bool,
        sit_position: Vector3,
        sit_rotation: Quaternion,
    ) -> Result<Self, Error> {
        Ok(Self {
            object_id,
            auto_pilot,
            camera_at_offset,
            camera_eye_offset,
            force_mouselook,
            sit_position,
            sit_rotation,
        })
    }

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

    #[must_use]
    pub const fn camera_at_offset(&self) -> Vector3 {
        self.camera_at_offset
    }

    #[must_use]
    pub const fn camera_eye_offset(&self) -> Vector3 {
        self.camera_eye_offset
    }

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

    #[must_use]
    pub const fn object_id(&self) -> UUID {
        self.object_id
    }

    #[must_use]
    pub const fn sit_position(&self) -> Vector3 {
        self.sit_position
    }

    #[must_use]
    pub const fn sit_rotation(&self) -> Quaternion {
        self.sit_rotation
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct CameraFrame {
    origin: Vector3,
    x_axis: Vector3,
    y_axis: Vector3,
    z_axis: Vector3,
}

impl Default for CameraFrame {
    fn default() -> Self {
        Self {
            origin: Vector3 {
                x: 128.0,
                y: 128.0,
                z: 20.0,
            },
            x_axis: Vector3::unit_x(),
            y_axis: Vector3::unit_y(),
            z_axis: Vector3::unit_z(),
        }
    }
}

impl CameraFrame {
    fn finite(value: Vector3) -> bool {
        value.x.is_finite() && value.y.is_finite() && value.z.is_finite()
    }

    fn orthonormalize(&mut self) {
        self.x_axis = normalize(self.x_axis);
        self.y_axis = normalize(subtract(
            self.y_axis,
            scale(self.x_axis, dot(self.x_axis, self.y_axis)),
        ));
        self.z_axis = cross(self.x_axis, self.y_axis);
    }

    fn rotate(&mut self, angle: f32, axis: Vector3) {
        self.x_axis = rotate_axis(self.x_axis, axis, angle);
        self.y_axis = rotate_axis(self.y_axis, axis, angle);
        self.orthonormalize();
    }

    fn look_direction(&mut self, mut at: Vector3, up: Vector3) {
        let mut left = cross(up, at);
        if left == Vector3::zero() {
            at.x += 0.01;
            at = normalize(at);
            left = cross(up, at);
        }
        self.x_axis = at;
        self.y_axis = normalize(left);
        self.z_axis = cross(at, self.y_axis);
    }
}

/// Shared-reference camera wrapper with the same axis mapping as the C# frame.
#[derive(Clone)]
pub struct AgentManagerAgentMovementAgentCamera {
    pub far: f32,
    frame: Arc<Mutex<CameraFrame>>,
    vertical_fov_radians: Arc<AtomicU32>,
}

impl fmt::Debug for AgentManagerAgentMovementAgentCamera {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("AgentCamera")
            .field("far", &self.far)
            .field("vertical_fov_radians", &self.vertical_fov_angle())
            .field("frame", &*mutex(&self.frame))
            .finish()
    }
}

impl AgentManagerAgentMovementAgentCamera {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            far: 128.0,
            frame: Arc::new(Mutex::new(CameraFrame::default())),
            vertical_fov_radians: Arc::new(AtomicU32::new(60.0_f32.to_radians().to_bits())),
        })
    }

    fn from_frame(
        frame: Arc<Mutex<CameraFrame>>,
        vertical_fov_radians: Arc<AtomicU32>,
        far: f32,
    ) -> Self {
        Self {
            far,
            frame,
            vertical_fov_radians,
        }
    }

    pub fn look_at_with_vector3_vector3(
        &self,
        position: Vector3,
        target: Vector3,
    ) -> Result<(), Error> {
        self.look_at_with_vector3_vector3_vector3(position, target, Vector3::unit_z())
    }

    pub fn look_at_with_vector3_vector3_vector3(
        &self,
        position: Vector3,
        target: Vector3,
        up_direction: Vector3,
    ) -> Result<(), Error> {
        if !CameraFrame::finite(position)
            || !CameraFrame::finite(target)
            || !CameraFrame::finite(up_direction)
        {
            return Err(Error::Argument);
        }
        let mut frame = mutex(&self.frame);
        frame.origin = position;
        frame.look_direction(normalize(subtract(target, position)), up_direction);
        Ok(())
    }

    pub fn look_direction_with_vector3(&self, target: Vector3) -> Result<(), Error> {
        self.look_direction_with_vector3_vector3(target, Vector3::unit_z())
    }

    pub fn look_direction_with_vector3_vector3(
        &self,
        target: Vector3,
        up_direction: Vector3,
    ) -> Result<(), Error> {
        if !CameraFrame::finite(target) || !CameraFrame::finite(up_direction) {
            return Err(Error::Argument);
        }
        mutex(&self.frame).look_direction(target, up_direction);
        Ok(())
    }

    pub fn look_direction_with_double(&self, heading: f64) -> Result<(), Error> {
        if !heading.is_finite() {
            return Err(Error::Argument);
        }
        let mut frame = mutex(&self.frame);
        frame.y_axis = Vector3 {
            x: heading.cos() as f32,
            y: heading.sin() as f32,
            z: frame.y_axis.z,
        };
        frame.x_axis = Vector3 {
            x: -heading.sin() as f32,
            y: heading.cos() as f32,
            z: frame.x_axis.z,
        };
        Ok(())
    }

    pub fn pitch(&self, angle: f32) -> Result<(), Error> {
        if !angle.is_finite() {
            return Err(Error::Argument);
        }
        let mut frame = mutex(&self.frame);
        let axis = frame.y_axis;
        frame.rotate(angle, axis);
        Ok(())
    }

    pub fn roll(&self, angle: f32) -> Result<(), Error> {
        if !angle.is_finite() {
            return Err(Error::Argument);
        }
        let mut frame = mutex(&self.frame);
        let axis = frame.x_axis;
        frame.rotate(angle, axis);
        Ok(())
    }

    pub fn set_position_orientation(
        &self,
        position: Vector3,
        roll: f32,
        pitch: f32,
        yaw: f32,
    ) -> Result<(), Error> {
        if !CameraFrame::finite(position)
            || !roll.is_finite()
            || !pitch.is_finite()
            || !yaw.is_finite()
        {
            return Err(Error::Argument);
        }
        let mut frame = mutex(&self.frame);
        frame.origin = position;
        frame.x_axis = Vector3::unit_x();
        frame.y_axis = Vector3::unit_y();
        frame.z_axis = Vector3::unit_z();
        let axis = frame.x_axis;
        frame.rotate(roll, axis);
        let axis = frame.y_axis;
        frame.rotate(pitch, axis);
        let axis = frame.z_axis;
        frame.rotate(yaw, axis);
        Ok(())
    }

    pub fn yaw(&self, angle: f32) -> Result<(), Error> {
        if !angle.is_finite() {
            return Err(Error::Argument);
        }
        let mut frame = mutex(&self.frame);
        let axis = frame.z_axis;
        frame.rotate(angle, axis);
        Ok(())
    }

    #[must_use]
    pub fn at_axis(&self) -> Vector3 {
        mutex(&self.frame).y_axis
    }

    pub fn set_at_axis(&mut self, value: Vector3) {
        if CameraFrame::finite(value) {
            mutex(&self.frame).y_axis = value;
        }
    }

    #[must_use]
    pub fn left_axis(&self) -> Vector3 {
        mutex(&self.frame).x_axis
    }

    pub fn set_left_axis(&mut self, value: Vector3) {
        if CameraFrame::finite(value) {
            mutex(&self.frame).x_axis = value;
        }
    }

    #[must_use]
    pub fn position(&self) -> Vector3 {
        mutex(&self.frame).origin
    }

    pub fn set_position(&mut self, value: Vector3) {
        if CameraFrame::finite(value) {
            mutex(&self.frame).origin = value;
        }
    }

    #[must_use]
    pub fn up_axis(&self) -> Vector3 {
        mutex(&self.frame).z_axis
    }

    pub fn set_up_axis(&mut self, value: Vector3) {
        if CameraFrame::finite(value) {
            mutex(&self.frame).z_axis = value;
        }
    }

    #[must_use]
    pub fn vertical_fov_angle(&self) -> f32 {
        f32::from_bits(self.vertical_fov_radians.load(Ordering::Acquire))
    }

    pub fn set_vertical_fov_angle(&self, value: f32) -> Result<(), Error> {
        if !value.is_finite() || !(10.0_f32.to_radians()..=140.0_f32.to_radians()).contains(&value)
        {
            return Err(Error::Argument);
        }
        self.vertical_fov_radians
            .store(value.to_bits(), Ordering::Release);
        Ok(())
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct WireState {
    body_rotation: Quaternion,
    head_rotation: Quaternion,
    camera_center: Vector3,
    camera_x_axis: Vector3,
    camera_y_axis: Vector3,
    camera_z_axis: Vector3,
    far: f32,
    flags: AgentFlags,
    state: AgentState,
}

impl Default for WireState {
    fn default() -> Self {
        Self {
            body_rotation: Quaternion::identity(),
            head_rotation: Quaternion::identity(),
            camera_center: CameraFrame::default().origin,
            camera_x_axis: Vector3::unit_x(),
            camera_y_axis: Vector3::unit_y(),
            camera_z_axis: Vector3::unit_z(),
            far: 128.0,
            flags: AgentFlags::NONE,
            state: AgentState::NONE,
        }
    }
}

#[derive(Clone, Copy, Default)]
struct LastUpdate {
    wire: WireState,
    duplicate_count: u8,
}

#[derive(Clone, Copy, Debug)]
struct Kinematics {
    relative_position: Vector3,
    relative_rotation: Quaternion,
    velocity: Vector3,
    acceleration: Vector3,
    angular_velocity: Vector3,
    sitting_on: u32,
    last_position_update: SystemTime,
    home_position: Vector3,
}

impl Default for Kinematics {
    fn default() -> Self {
        Self {
            relative_position: Vector3::zero(),
            relative_rotation: Quaternion::identity(),
            velocity: Vector3::zero(),
            acceleration: Vector3::zero(),
            angular_velocity: Vector3::zero(),
            sitting_on: 0,
            last_position_update: SystemTime::UNIX_EPOCH,
            home_position: Vector3::zero(),
        }
    }
}

struct TeleportState {
    status: TeleportStatus,
    message: String,
    generation: u64,
    waiter: Option<(u64, tokio::sync::oneshot::Sender<bool>)>,
}

impl Default for TeleportState {
    fn default() -> Self {
        Self {
            status: TeleportStatus::None,
            message: String::new(),
            generation: 0,
            waiter: None,
        }
    }
}

#[derive(Default)]
struct TimerState {
    connected: bool,
    started: bool,
    shutdown: bool,
    revision: u64,
}

#[derive(Default)]
struct TimerControl {
    state: Mutex<TimerState>,
    wake: Condvar,
}

struct CrossingInfo {
    state: AgentManagerCrossingState,
    started: Instant,
    old_simulator: Option<Simulator>,
    new_simulator: Option<Simulator>,
    region_handle: u64,
    endpoint: SocketAddr,
    seed: Uri,
    position: Vector3,
    look_at: Vector3,
    size_x: u32,
    size_y: u32,
    retry_count: u8,
    failure_reason: AgentManagerCrossingFailureReason,
    failure_message: String,
    generation: u64,
    cancel_requested: bool,
    restored_old_simulator: bool,
}

#[allow(dead_code)] // Mirrors the reference's per-simulator cache for later packet updates.
#[derive(Clone, Copy)]
struct SimulatorAgentState {
    position: Vector3,
    rotation: Quaternion,
    local_id: u32,
    is_present: bool,
    last_update: SystemTime,
}

#[allow(dead_code)] // Tracks proactive child establishment exactly as the reference does.
#[derive(Clone, Copy)]
struct ChildAgentStatus {
    request_time: SystemTime,
    established: bool,
    direction: BorderCrossingDirection,
}

#[derive(Default)]
struct CrossingControl {
    current: Mutex<Option<CrossingInfo>>,
    wake: Condvar,
}

pub(crate) struct AgentMovementRuntime {
    client: Arc<GridClient>,
    network: NetworkManager,
    owner: Weak<AgentManagerInner>,
    controls: AtomicU32,
    always_run: AtomicBool,
    auto_reset_controls: AtomicBool,
    update_interval: AtomicI32,
    camera_frame: Arc<Mutex<CameraFrame>>,
    vertical_fov_radians: Arc<AtomicU32>,
    last_update: Mutex<LastUpdate>,
    kinematics: RwLock<Kinematics>,
    teleport: Mutex<TeleportState>,
    teleport_progress: EventRegistry<TeleportEventArgs>,
    camera_constraint: EventRegistry<CameraConstraintEventArgs>,
    avatar_sit_response: EventRegistry<AvatarSitResponseEventArgs>,
    region_crossed: EventRegistry<RegionCrossedEventArgs>,
    region_crossing_predicted: EventRegistry<RegionCrossingPredictionEventArgs>,
    timer: Arc<TimerControl>,
    timer_thread: Mutex<Option<JoinHandle<()>>>,
    lifecycle_subscriptions: Mutex<Vec<Subscription>>,
    crossing_generation: AtomicU64,
    crossing: Arc<CrossingControl>,
    crossing_thread: Mutex<Option<JoinHandle<()>>>,
    simulator_states: RwLock<HashMap<SocketAddr, SimulatorAgentState>>,
    child_agent_status: RwLock<HashMap<u64, ChildAgentStatus>>,
    object_simulators: RwLock<HashMap<UUID, Vec<Simulator>>>,
}

impl fmt::Debug for AgentMovementRuntime {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("AgentMovementRuntime")
            .field("controls", &self.controls.load(Ordering::Acquire))
            .field(
                "update_interval",
                &self.update_interval.load(Ordering::Acquire),
            )
            .field("kinematics", &*read(&self.kinematics))
            .finish_non_exhaustive()
    }
}

impl Drop for AgentMovementRuntime {
    fn drop(&mut self) {
        {
            let mut state = mutex(&self.timer.state);
            state.shutdown = true;
            state.revision = state.revision.wrapping_add(1);
        }
        self.timer.wake.notify_all();
        self.crossing_generation.fetch_add(1, Ordering::AcqRel);
        self.crossing.wake.notify_all();
        if let Some(handle) = mutex(&self.timer_thread).take()
            && handle.thread().id() != thread::current().id()
        {
            let _ = handle.join();
        }
        if let Some(handle) = mutex(&self.crossing_thread).take()
            && handle.thread().id() != thread::current().id()
        {
            let _ = handle.join();
        }
    }
}

impl AgentMovementRuntime {
    pub(crate) fn new(
        client: Arc<GridClient>,
        network: NetworkManager,
        owner: Weak<AgentManagerInner>,
    ) -> Arc<Self> {
        let interval = client.settings_ref().timing.agent_update_interval;
        let runtime = Arc::new(Self {
            client,
            network,
            owner,
            controls: AtomicU32::new(0),
            always_run: AtomicBool::new(false),
            auto_reset_controls: AtomicBool::new(false),
            update_interval: AtomicI32::new(interval),
            camera_frame: Arc::new(Mutex::new(CameraFrame::default())),
            vertical_fov_radians: Arc::new(AtomicU32::new(60.0_f32.to_radians().to_bits())),
            last_update: Mutex::new(LastUpdate::default()),
            kinematics: RwLock::new(Kinematics::default()),
            teleport: Mutex::new(TeleportState::default()),
            teleport_progress: EventRegistry::default(),
            camera_constraint: EventRegistry::default(),
            avatar_sit_response: EventRegistry::default(),
            region_crossed: EventRegistry::default(),
            region_crossing_predicted: EventRegistry::default(),
            timer: Arc::new(TimerControl {
                state: Mutex::new(TimerState {
                    connected: false,
                    started: false,
                    shutdown: false,
                    revision: 0,
                }),
                wake: Condvar::new(),
            }),
            timer_thread: Mutex::new(None),
            lifecycle_subscriptions: Mutex::new(Vec::new()),
            crossing_generation: AtomicU64::new(0),
            crossing: Arc::new(CrossingControl::default()),
            crossing_thread: Mutex::new(None),
            simulator_states: RwLock::new(HashMap::new()),
            child_agent_status: RwLock::new(HashMap::new()),
            object_simulators: RwLock::new(HashMap::new()),
        });
        runtime.install_lifecycle();
        runtime.start_timer_worker();
        runtime
    }

    fn install_lifecycle(self: &Arc<Self>) {
        let weak = Arc::downgrade(self);
        let login = self
            .network
            .native_subscribe_login_progress(Arc::new(move |event| {
                if let Some(runtime) = weak.upgrade()
                    && event.status() == crate::LoginStatus::Success
                {
                    runtime.set_timer_connection(true, true);
                }
            }));
        let weak = Arc::downgrade(self);
        let disconnected = self
            .network
            .native_subscribe_disconnected(Arc::new(move |_| {
                if let Some(runtime) = weak.upgrade() {
                    runtime.set_timer_connection(false, false);
                }
            }));
        mutex(&self.lifecycle_subscriptions).extend([login, disconnected]);
    }

    fn set_timer_connection(&self, connected: bool, login_succeeded: bool) {
        let mut state = mutex(&self.timer.state);
        state.connected = connected;
        if login_succeeded {
            state.started = self.client.settings_ref().agent.send_updates_regularly;
        }
        state.revision = state.revision.wrapping_add(1);
        drop(state);
        self.timer.wake.notify_all();
    }

    fn start_timer_worker(self: &Arc<Self>) {
        let weak = Arc::downgrade(self);
        let timer = Arc::clone(&self.timer);
        let handle = thread::Builder::new()
            .name("libremetaverse-agent-update".into())
            .spawn(move || {
                loop {
                    let Some(runtime) = weak.upgrade() else {
                        break;
                    };
                    let interval = runtime.update_interval.load(Ordering::Acquire);
                    drop(runtime);

                    let state = mutex(&timer.state);
                    if state.shutdown {
                        break;
                    }
                    if !state.connected || !state.started || interval <= 0 {
                        drop(
                            timer
                                .wake
                                .wait(state)
                                .unwrap_or_else(std::sync::PoisonError::into_inner),
                        );
                        continue;
                    }
                    let revision = state.revision;
                    let (next_state, timed_out) = timer
                        .wake
                        .wait_timeout(
                            state,
                            Duration::from_millis(u64::try_from(interval).unwrap_or(1)),
                        )
                        .unwrap_or_else(std::sync::PoisonError::into_inner);
                    let should_send = timed_out.timed_out()
                        && !next_state.shutdown
                        && next_state.connected
                        && next_state.started
                        && next_state.revision == revision;
                    drop(next_state);
                    if should_send
                        && let Some(runtime) = weak.upgrade()
                        && runtime.client.settings_ref().agent.send_updates
                        && runtime.network.native_connected()
                    {
                        let _ = runtime.send_last_update(false, None);
                    }
                }
            });
        if let Ok(handle) = handle {
            *mutex(&self.timer_thread) = Some(handle);
        }
    }

    pub(crate) fn stop(&self) {
        {
            let mut state = mutex(&self.timer.state);
            state.connected = false;
            state.started = false;
            state.shutdown = true;
            state.revision = state.revision.wrapping_add(1);
        }
        self.timer.wake.notify_all();
        if let Some(handle) = mutex(&self.timer_thread).take()
            && handle.thread().id() != thread::current().id()
        {
            let _ = handle.join();
        }
        let mut teleport = mutex(&self.teleport);
        if let Some((_, waiter)) = teleport.waiter.take() {
            let _ = waiter.send(false);
        }
        drop(teleport);
        self.crossing_generation.fetch_add(1, Ordering::AcqRel);
        if let Some(crossing) = mutex(&self.crossing.current).as_mut() {
            crossing.cancel_requested = true;
        }
        self.crossing.wake.notify_all();
        if let Some(handle) = mutex(&self.crossing_thread).take()
            && handle.thread().id() != thread::current().id()
        {
            let _ = handle.join();
        }
    }

    fn ids(&self) -> (UUID, UUID) {
        self.owner.upgrade().map_or_else(
            || {
                (
                    self.network.native_agent_id(),
                    self.network.native_session_id(),
                )
            },
            |owner| owner.native_ids(&self.network),
        )
    }

    fn current_sim(&self) -> Result<Simulator, Error> {
        self.network
            .native_current_sim()
            .ok_or(Error::InvalidOperation)
    }

    fn send_current(
        &self,
        packet_type: PacketType,
        data: Vec<u8>,
        reliable: Option<bool>,
    ) -> Result<(), Error> {
        send_encoded(&self.current_sim()?, packet_type, data, reliable)
    }

    fn snapshot(&self) -> WireState {
        mutex(&self.last_update).wire
    }

    fn camera(&self, far: f32) -> AgentManagerAgentMovementAgentCamera {
        AgentManagerAgentMovementAgentCamera::from_frame(
            Arc::clone(&self.camera_frame),
            Arc::clone(&self.vertical_fov_radians),
            far,
        )
    }

    fn send_update(
        &self,
        wire: WireState,
        reliable: bool,
        simulator: Option<Simulator>,
    ) -> Result<(), Error> {
        let simulator = simulator.map_or_else(|| self.current_sim(), Ok)?;
        if !simulator.native_agent_movement_complete() {
            return Ok(());
        }
        let controls = self.controls.load(Ordering::Acquire);
        let should_send = {
            let mut last = mutex(&self.last_update);
            if controls == 0 && wire == last.wire {
                last.duplicate_count = last.duplicate_count.saturating_add(1);
            } else {
                last.duplicate_count = 0;
            }
            let should_send = self
                .client
                .settings_ref()
                .agent
                .disable_update_duplicate_check
                || last.duplicate_count < 10;
            if should_send {
                last.wire = wire;
            }
            should_send
        };
        if !should_send {
            return Ok(());
        }

        let (agent_id, session_id) = self.ids();
        let mut packet = AgentUpdatePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.head_rotation = wire.head_rotation;
        packet.agent_data.body_rotation = wire.body_rotation;
        // Preserve the reference's deliberate Camera wrapper/frame swap.
        packet.agent_data.camera_at_axis = wire.camera_x_axis;
        packet.agent_data.camera_center = wire.camera_center;
        packet.agent_data.camera_left_axis = wire.camera_y_axis;
        packet.agent_data.camera_up_axis = wire.camera_z_axis;
        packet.agent_data.far = wire.far;
        packet.agent_data.state = wire.state.0;
        packet.agent_data.control_flags = controls;
        packet.agent_data.flags = wire.flags.0;
        send_encoded(
            &simulator,
            PacketType::AgentUpdate,
            packet.to_bytes_with_method()?,
            Some(reliable),
        )?;
        if self.auto_reset_controls.load(Ordering::Acquire) {
            self.reset_control_flags();
        }
        Ok(())
    }

    fn send_last_update(&self, reliable: bool, simulator: Option<Simulator>) -> Result<(), Error> {
        self.send_update(self.snapshot(), reliable, simulator)
    }

    fn reset_control_flags(&self) {
        const PERSISTENT: u32 = (1 << 27) | (1 << 13) | (1 << 18) | (1 << 5);
        self.controls.fetch_and(PERSISTENT, Ordering::AcqRel);
    }

    fn set_flag(&self, flag: u32, value: bool) {
        if value {
            self.controls.fetch_or(flag, Ordering::AcqRel);
        } else {
            self.controls.fetch_and(!flag, Ordering::AcqRel);
        }
    }

    fn flag(&self, flag: u32) -> bool {
        self.controls.load(Ordering::Acquire) & flag != 0
    }

    fn set_update_interval(&self, value: i32) {
        self.update_interval.store(value.max(0), Ordering::Release);
        let mut state = mutex(&self.timer.state);
        state.revision = state.revision.wrapping_add(1);
        drop(state);
        self.timer.wake.notify_all();
    }

    fn update_position(&self, position: Vector3, look_at: Vector3) -> Result<(), Error> {
        {
            let mut kinematics = write(&self.kinematics);
            kinematics.relative_position = position;
            kinematics.last_position_update = SystemTime::now();
        }
        self.camera(128.0).look_direction_with_vector3(look_at)
    }

    fn teleport_event(&self, message: String, status: TeleportStatus, flags: TeleportFlags) {
        let terminal = matches!(
            status,
            TeleportStatus::Failed | TeleportStatus::Finished | TeleportStatus::Cancelled
        );
        let waiter = {
            let mut state = mutex(&self.teleport);
            state.message.clone_from(&message);
            state.status = status;
            if terminal {
                state.waiter.take().map(|(_, waiter)| waiter)
            } else {
                None
            }
        };
        self.teleport_progress.emit(TeleportEventArgs {
            message,
            status,
            flags,
        });
        if let Some(waiter) = waiter {
            let _ = waiter.send(status == TeleportStatus::Finished);
        }
    }

    fn begin_teleport(&self) -> (u64, tokio::sync::oneshot::Receiver<bool>) {
        let (sender, receiver) = tokio::sync::oneshot::channel();
        let old_waiter = {
            let mut state = mutex(&self.teleport);
            state.generation = state.generation.wrapping_add(1);
            state.status = TeleportStatus::None;
            state.message.clear();
            let generation = state.generation;
            let old = state.waiter.replace((generation, sender));
            (generation, old)
        };
        if let Some((_, old)) = old_waiter.1 {
            let _ = old.send(false);
        }
        (old_waiter.0, receiver)
    }

    async fn wait_for_teleport(
        &self,
        generation: u64,
        receiver: tokio::sync::oneshot::Receiver<bool>,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        let cancellation = cancellation_token.unwrap_or_default();
        if let Err(error) = cancellation.throw_if_cancellation_requested() {
            let mut state = mutex(&self.teleport);
            if state
                .waiter
                .as_ref()
                .is_some_and(|(candidate, _)| *candidate == generation)
            {
                state.waiter.take();
            }
            return Err(error);
        }
        let timeout = Duration::from_millis(
            u64::try_from(self.client.settings_ref().timing.teleport_timeout.max(1)).unwrap_or(1),
        );
        tokio::select! {
            result = receiver => Ok(result.unwrap_or(false)),
            () = cancellation.cancelled() => {
                let mut state = mutex(&self.teleport);
                if state.waiter.as_ref().is_some_and(|(candidate, _)| *candidate == generation) {
                    state.waiter.take();
                }
                Err(Error::Cancelled)
            }
            () = tokio::time::sleep(timeout) => {
                let mut state = mutex(&self.teleport);
                if state.waiter.as_ref().is_some_and(|(candidate, _)| *candidate == generation) {
                    state.waiter.take();
                    state.message = "Teleport timed out.".into();
                    state.status = TeleportStatus::Failed;
                }
                Ok(false)
            }
        }
    }

    async fn wait_for_event_queue(
        &self,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<(), Error> {
        let simulator = self.current_sim()?;
        if simulator.native_is_event_queue_running(None)? {
            return Ok(());
        }
        simulator.native_start_event_queue();
        let cancellation = cancellation_token.unwrap_or_default();
        cancellation.throw_if_cancellation_requested()?;
        let (sender, receiver) = tokio::sync::oneshot::channel();
        let sender = Mutex::new(Some(sender));
        let expected = simulator.clone();
        let subscription =
            self.network
                .native_subscribe_event_queue_running(Arc::new(move |event| {
                    if event.simulator() == expected
                        && let Some(sender) = mutex(&sender).take()
                    {
                        let _ = sender.send(());
                    }
                }));
        tokio::select! {
            _ = receiver => {}
            () = cancellation.cancelled() => {
                drop(subscription);
                return Err(Error::Cancelled);
            }
            () = tokio::time::sleep(Duration::from_secs(10)) => {}
        }
        drop(subscription);
        Ok(())
    }

    fn request_teleport_landmark(&self, landmark: UUID) -> Result<(), Error> {
        let (agent_id, session_id) = self.ids();
        let mut packet = TeleportLandmarkRequestPacket::new_with_constructor()?;
        packet.info.agent_id = agent_id;
        packet.info.session_id = session_id;
        packet.info.landmark_id = landmark;
        self.send_current(
            PacketType::TeleportLandmarkRequest,
            packet.to_bytes_with_method()?,
            None,
        )
    }

    fn request_teleport_location(
        &self,
        region_handle: u64,
        position: Vector3,
        look_at: Vector3,
        ignore_caps_status: bool,
    ) -> Result<(), Error> {
        let sim = self.current_sim()?;
        if !ignore_caps_status && !sim.native_is_event_queue_running(None)? {
            self.teleport_event(
                "CAPS event queue is not running".into(),
                TeleportStatus::Failed,
                TeleportFlags::DEFAULT,
            );
            return Ok(());
        }
        let (agent_id, session_id) = self.ids();
        let mut packet = TeleportLocationRequestPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.info.look_at = look_at;
        packet.info.position = position;
        packet.info.region_handle = region_handle;
        send_encoded(
            &sim,
            PacketType::TeleportLocationRequest,
            packet.to_bytes_with_method()?,
            None,
        )
    }

    fn complete_agent_movement(&self, simulator: Simulator) -> Result<(), Error> {
        let (agent_id, session_id) = self.ids();
        let mut packet = crate::packets::CompleteAgentMovementPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.circuit_code = self.network.native_circuit_code();
        // The reference packet constructor marks this final region transition
        // reliable; OpenSim may never publish the scene if this datagram is lost.
        send_encoded(
            &simulator,
            PacketType::CompleteAgentMovement,
            packet.to_bytes_with_method()?,
            Some(true),
        )
    }

    fn connect_destination(
        &self,
        endpoint: SocketAddr,
        region_handle: u64,
        seed: Option<Uri>,
        size_x: u32,
        size_y: u32,
    ) -> bool {
        if let Some(old) = self.network.native_current_sim() {
            old.native_set_agent_movement_complete(false);
        }
        self.network
            .native_connect(endpoint, region_handle, true, seed, size_x, size_y)
            .ok()
            .flatten()
            .is_some()
    }

    #[allow(clippy::too_many_lines)] // Packet dispatch mirrors the protocol state machine.
    pub(crate) fn handle_raw_packet(
        self: &Arc<Self>,
        packet_type: PacketType,
        data: &[u8],
        simulator: Simulator,
    ) -> Result<bool, Error> {
        match packet_type {
            PacketType::AgentMovementComplete => {
                let packet = decode_packet::<AgentMovementCompletePacket>(data)?;
                self.update_position(packet.data.position, packet.data.look_at)?;
                simulator.native_set_agent_movement_complete(true);
                self.notify_movement_complete(&simulator);
                self.update_multi_simulator_state(&simulator);
                Ok(true)
            }
            PacketType::CameraConstraint => {
                let packet = decode_packet::<CameraConstraintPacket>(data)?;
                self.camera_constraint.emit(CameraConstraintEventArgs {
                    collide_plane: packet.camera_collide_plane.plane,
                });
                Ok(true)
            }
            PacketType::AvatarSitResponse => {
                let packet = decode_packet::<crate::packets::AvatarSitResponsePacket>(data)?;
                let transform = packet.sit_transform;
                self.avatar_sit_response.emit(AvatarSitResponseEventArgs {
                    object_id: packet.sit_object.id,
                    auto_pilot: transform.auto_pilot,
                    camera_at_offset: transform.camera_at_offset,
                    camera_eye_offset: transform.camera_eye_offset,
                    force_mouselook: transform.force_mouselook,
                    sit_position: transform.sit_position,
                    sit_rotation: transform.sit_rotation,
                });
                Ok(true)
            }
            PacketType::TeleportStart => {
                let packet = decode_packet::<TeleportStartPacket>(data)?;
                self.teleport_event(
                    "Teleport started".into(),
                    TeleportStatus::Start,
                    TeleportFlags(packet.info.teleport_flags),
                );
                Ok(true)
            }
            PacketType::TeleportProgress => {
                let packet = decode_packet::<TeleportProgressPacket>(data)?;
                self.teleport_event(
                    wire_string(packet.info.message),
                    TeleportStatus::Progress,
                    TeleportFlags(packet.info.teleport_flags),
                );
                Ok(true)
            }
            PacketType::TeleportFailed => {
                let packet = decode_packet::<TeleportFailedPacket>(data)?;
                let status = mutex(&self.teleport).status;
                if matches!(status, TeleportStatus::Finished | TeleportStatus::None) {
                    return Ok(true);
                }
                self.teleport_event(
                    wire_string(packet.info.reason),
                    TeleportStatus::Failed,
                    TeleportFlags::DEFAULT,
                );
                Ok(true)
            }
            PacketType::TeleportCancel => {
                let _ = decode_packet::<TeleportCancelPacket>(data)?;
                self.teleport_event(
                    "Cancelled".into(),
                    TeleportStatus::Cancelled,
                    TeleportFlags::DEFAULT,
                );
                Ok(true)
            }
            PacketType::TeleportLocal => {
                let packet = decode_packet::<TeleportLocalPacket>(data)?;
                self.update_position(packet.info.position, packet.info.look_at)?;
                self.teleport_event(
                    "Teleport finished".into(),
                    TeleportStatus::Finished,
                    TeleportFlags(packet.info.teleport_flags),
                );
                Ok(true)
            }
            PacketType::TeleportFinish => {
                let packet = decode_packet::<TeleportFinishPacket>(data)?;
                let endpoint = SocketAddr::new(
                    IpAddr::V4(Ipv4Addr::from(packet.info.sim_ip.to_le_bytes())),
                    packet.info.sim_port,
                );
                let seed = Uri(wire_string(packet.info.seed_capability));
                let success = self.connect_destination(
                    endpoint,
                    packet.info.region_handle,
                    Some(seed),
                    256,
                    256,
                );
                self.teleport_event(
                    if success {
                        "Teleport finished".into()
                    } else {
                        "Failed to connect to simulator after teleport".into()
                    },
                    if success {
                        TeleportStatus::Finished
                    } else {
                        TeleportStatus::Failed
                    },
                    TeleportFlags(packet.info.teleport_flags),
                );
                Ok(true)
            }
            PacketType::CrossedRegion => {
                let packet = decode_packet::<CrossedRegionPacket>(data)?;
                self.begin_crossing(
                    simulator,
                    SocketAddr::new(
                        IpAddr::V4(Ipv4Addr::from(packet.region_data.sim_ip.to_le_bytes())),
                        packet.region_data.sim_port,
                    ),
                    packet.region_data.region_handle,
                    Uri(wire_string(packet.region_data.seed_capability)),
                    packet.info.position,
                    packet.info.look_at,
                    256,
                    256,
                );
                Ok(true)
            }
            _ => Ok(false),
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn begin_crossing(
        self: &Arc<Self>,
        old_simulator: Simulator,
        endpoint: SocketAddr,
        region_handle: u64,
        seed: Uri,
        position: Vector3,
        look_at: Vector3,
        size_x: u32,
        size_y: u32,
    ) -> bool {
        {
            let current = mutex(&self.crossing.current);
            if current.as_ref().is_some_and(|crossing| {
                !matches!(
                    crossing.state,
                    AgentManagerCrossingState::Idle
                        | AgentManagerCrossingState::Completed
                        | AgentManagerCrossingState::Failed
                )
            }) {
                return false;
            }
        }
        let generation = self.crossing_generation.fetch_add(1, Ordering::AcqRel) + 1;
        *mutex(&self.crossing.current) = Some(CrossingInfo {
            state: AgentManagerCrossingState::PreparingCross,
            started: Instant::now(),
            old_simulator: Some(old_simulator),
            new_simulator: None,
            region_handle,
            endpoint,
            seed,
            position,
            look_at,
            size_x,
            size_y,
            retry_count: 0,
            failure_reason: AgentManagerCrossingFailureReason::Unknown,
            failure_message: String::new(),
            generation,
            cancel_requested: false,
            restored_old_simulator: false,
        });
        let weak = Arc::downgrade(self);
        let crossing = Arc::clone(&self.crossing);
        let spawned = thread::Builder::new()
            .name("libremetaverse-region-crossing".into())
            .spawn(move || Self::run_crossing(weak, crossing, generation));
        let Ok(handle) = spawned else {
            if let Some(info) = mutex(&self.crossing.current).as_mut() {
                info.state = AgentManagerCrossingState::Failed;
                info.failure_reason = AgentManagerCrossingFailureReason::NetworkError;
                info.failure_message = "Unable to start region crossing worker".into();
            }
            return false;
        };
        if let Some(previous) = mutex(&self.crossing_thread).replace(handle)
            && previous.thread().id() != thread::current().id()
        {
            let _ = previous.join();
        }
        true
    }

    #[allow(clippy::too_many_lines)] // Keeping transitions together makes the state audit explicit.
    fn run_crossing(weak: Weak<Self>, crossing: Arc<CrossingControl>, generation: u64) {
        loop {
            let (endpoint, region_handle, seed, size_x, size_y) = {
                let mut current = mutex(&crossing.current);
                let Some(info) = current
                    .as_mut()
                    .filter(|info| info.generation == generation)
                else {
                    return;
                };
                if info.cancel_requested {
                    drop(current);
                    Self::recover_crossing(&weak, &crossing, generation);
                    return;
                }
                info.state = AgentManagerCrossingState::Connecting;
                (
                    info.endpoint,
                    info.region_handle,
                    info.seed.clone(),
                    info.size_x,
                    info.size_y,
                )
            };

            let Some(runtime) = weak.upgrade() else {
                return;
            };
            if runtime.crossing_generation.load(Ordering::Acquire) != generation {
                return;
            }
            let connection = runtime.network.native_connect(
                endpoint,
                region_handle,
                true,
                Some(seed),
                size_x,
                size_y,
            );
            drop(runtime);

            match connection {
                Ok(Some(new_simulator)) => {
                    let (old_simulator, position, look_at) = {
                        let mut current = mutex(&crossing.current);
                        let Some(info) = current
                            .as_mut()
                            .filter(|info| info.generation == generation)
                        else {
                            return;
                        };
                        if info.cancel_requested {
                            info.new_simulator = Some(new_simulator);
                            drop(current);
                            Self::recover_crossing(&weak, &crossing, generation);
                            return;
                        }
                        info.new_simulator = Some(new_simulator.clone());
                        info.state = AgentManagerCrossingState::WaitingForComplete;
                        (info.old_simulator.clone(), info.position, info.look_at)
                    };
                    crossing.wake.notify_all();
                    if let Some(old) = &old_simulator
                        && *old != new_simulator
                    {
                        old.native_set_agent_movement_complete(false);
                    }
                    if let Some(runtime) = weak.upgrade() {
                        let _ = runtime.update_position(position, look_at);
                    }

                    let current = mutex(&crossing.current);
                    let (mut current, timeout) = crossing
                        .wake
                        .wait_timeout_while(current, Duration::from_secs(30), |current| {
                            current.as_ref().is_some_and(|info| {
                                info.generation == generation
                                    && info.state == AgentManagerCrossingState::WaitingForComplete
                                    && !info.cancel_requested
                            })
                        })
                        .unwrap_or_else(std::sync::PoisonError::into_inner);
                    let Some(info) = current
                        .as_mut()
                        .filter(|info| info.generation == generation)
                    else {
                        return;
                    };
                    if info.state == AgentManagerCrossingState::Completed {
                        let event = RegionCrossedEventArgs {
                            old_simulator: info.old_simulator.clone(),
                            new_simulator: info.new_simulator.clone(),
                        };
                        info.state = AgentManagerCrossingState::Idle;
                        drop(current);
                        if let Some(runtime) = weak.upgrade() {
                            runtime.region_crossed.emit(event);
                        }
                        return;
                    }
                    if timeout.timed_out() {
                        info.failure_reason = AgentManagerCrossingFailureReason::Timeout;
                        info.failure_message = "Timeout in state WaitingForComplete".into();
                    }
                    drop(current);
                    Self::recover_crossing(&weak, &crossing, generation);
                    return;
                }
                Ok(None) => {
                    let mut current = mutex(&crossing.current);
                    let Some(info) = current
                        .as_mut()
                        .filter(|info| info.generation == generation)
                    else {
                        return;
                    };
                    info.failure_reason = AgentManagerCrossingFailureReason::ConnectionFailed;
                    info.failure_message = format!("Failed to connect to {}", info.endpoint);
                    if info.retry_count >= 3 {
                        info.failure_reason = AgentManagerCrossingFailureReason::MaxRetriesExceeded;
                        info.failure_message.push_str(" after 3 retries");
                        drop(current);
                        Self::recover_crossing(&weak, &crossing, generation);
                        return;
                    }
                    info.retry_count += 1;
                    let delay = Duration::from_secs(u64::from(info.retry_count));
                    let (current, _) = crossing
                        .wake
                        .wait_timeout(current, delay)
                        .unwrap_or_else(std::sync::PoisonError::into_inner);
                    if current
                        .as_ref()
                        .is_none_or(|info| info.generation != generation || info.cancel_requested)
                    {
                        drop(current);
                        Self::recover_crossing(&weak, &crossing, generation);
                        return;
                    }
                }
                Err(error) => {
                    let mut current = mutex(&crossing.current);
                    if let Some(info) = current
                        .as_mut()
                        .filter(|info| info.generation == generation)
                    {
                        info.failure_reason = AgentManagerCrossingFailureReason::NetworkError;
                        info.failure_message = format!("Exception during connection: {error}");
                    }
                    drop(current);
                    Self::recover_crossing(&weak, &crossing, generation);
                    return;
                }
            }
        }
    }

    fn recover_crossing(weak: &Weak<Self>, crossing: &CrossingControl, generation: u64) {
        let (old_simulator, new_simulator) = {
            let mut current = mutex(&crossing.current);
            let Some(info) = current
                .as_mut()
                .filter(|info| info.generation == generation)
            else {
                return;
            };
            info.state = AgentManagerCrossingState::Recovering;
            info.restored_old_simulator = info.old_simulator.is_some();
            (info.old_simulator.clone(), info.new_simulator.clone())
        };
        if let Some(runtime) = weak.upgrade() {
            if runtime.crossing_generation.load(Ordering::Acquire) != generation {
                return;
            }
            if let Some(old) = &old_simulator {
                old.native_set_agent_movement_complete(true);
                runtime.network.native_set_current_sim(Some(old.clone()));
                let _ = runtime.complete_agent_movement(old.clone());
            }
            if let Some(new_simulator) = new_simulator {
                let _ = runtime.network.native_disconnect_sim(new_simulator, false);
            }
            let event = RegionCrossedEventArgs {
                old_simulator,
                new_simulator: None,
            };
            if let Some(info) = mutex(&crossing.current)
                .as_mut()
                .filter(|info| info.generation == generation)
            {
                info.state = AgentManagerCrossingState::Failed;
                info.state = AgentManagerCrossingState::Idle;
            }
            runtime.region_crossed.emit(event);
        }
    }

    fn notify_movement_complete(&self, simulator: &Simulator) {
        let mut current = mutex(&self.crossing.current);
        let Some(info) = current.as_mut() else {
            return;
        };
        if info.state != AgentManagerCrossingState::WaitingForComplete {
            return;
        }
        if info.new_simulator.as_ref() == Some(simulator) {
            info.state = AgentManagerCrossingState::Completed;
        } else if info.old_simulator.as_ref() == Some(simulator) {
            info.failure_reason = AgentManagerCrossingFailureReason::SimulatorRejected;
            info.failure_message =
                "Received MovementComplete from old simulator instead of new one".into();
            info.cancel_requested = true;
        } else {
            return;
        }
        drop(current);
        self.crossing.wake.notify_all();
    }

    fn update_multi_simulator_state(&self, simulator: &Simulator) {
        let state = *read(&self.kinematics);
        write(&self.simulator_states).insert(
            simulator.native_ip_end_point(),
            SimulatorAgentState {
                position: state.relative_position,
                rotation: state.relative_rotation,
                local_id: 0,
                is_present: simulator.native_agent_movement_complete(),
                last_update: SystemTime::now(),
            },
        );
        if !self.client.settings_ref().agent.multiple_sims {
            return;
        }
        self.predict_crossing(simulator, state.relative_position, state.velocity);
        self.establish_neighbor_children(simulator, state.relative_position, state.velocity);
        self.cleanup_object_tracking();
        let now = SystemTime::now();
        write(&self.child_agent_status).retain(|_, status| {
            now.duration_since(status.request_time).unwrap_or_default() <= Duration::from_mins(2)
        });
    }

    #[allow(clippy::float_cmp)] // Golden code selects the direction by exact minimum equality.
    fn predict_crossing(&self, simulator: &Simulator, position: Vector3, velocity: Vector3) {
        if velocity == Vector3::zero() {
            return;
        }
        let (region_size_x, region_size_y) = simulator.region_size();
        let west = if velocity.x < -0.1 {
            -position.x / velocity.x
        } else {
            f32::MAX
        };
        let east = if velocity.x > 0.1 {
            (region_size_x as f32 - position.x) / velocity.x
        } else {
            f32::MAX
        };
        let south = if velocity.y < -0.1 {
            -position.y / velocity.y
        } else {
            f32::MAX
        };
        let north = if velocity.y > 0.1 {
            (region_size_y as f32 - position.y) / velocity.y
        } else {
            f32::MAX
        };
        let time = west.min(east).min(south).min(north);
        if !(0.0 < time && time < 3.0) {
            return;
        }
        let direction = if time == west {
            BorderCrossingDirection::West
        } else if time == east {
            BorderCrossingDirection::East
        } else if time == south {
            BorderCrossingDirection::South
        } else if time == north {
            BorderCrossingDirection::North
        } else {
            BorderCrossingDirection::Unknown
        };
        self.region_crossing_predicted
            .emit(RegionCrossingPredictionEventArgs {
                current_simulator: simulator.clone(),
                direction,
                time_until_crossing: time,
            });
    }

    fn establish_neighbor_children(
        &self,
        simulator: &Simulator,
        position: Vector3,
        velocity: Vector3,
    ) {
        if !simulator.native_agent_movement_complete() {
            return;
        }
        let x = u32::try_from(simulator.handle >> 32).unwrap_or_default();
        let y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default();
        let (size_x, size_y) = simulator.region_size();
        let size_x = size_x as f32;
        let size_y = size_y as f32;
        if velocity.x < -0.5 && position.x < 128.0 {
            self.establish_child(x.wrapping_sub(256), y, BorderCrossingDirection::West);
        } else if velocity.x > 0.5 && position.x > size_x - 128.0 {
            self.establish_child(x.wrapping_add(256), y, BorderCrossingDirection::East);
        }
        if velocity.y < -0.5 && position.y < 128.0 {
            self.establish_child(x, y.wrapping_sub(256), BorderCrossingDirection::South);
        } else if velocity.y > 0.5 && position.y > size_y - 128.0 {
            self.establish_child(x, y.wrapping_add(256), BorderCrossingDirection::North);
        }
        if Self::is_near_corner(position, size_x, size_y, 64.0) {
            for (child_x, child_y, direction) in [
                (x.wrapping_sub(256), y, BorderCrossingDirection::West),
                (x.wrapping_add(256), y, BorderCrossingDirection::East),
                (x, y.wrapping_sub(256), BorderCrossingDirection::South),
                (x, y.wrapping_add(256), BorderCrossingDirection::North),
            ] {
                self.establish_child(child_x, child_y, direction);
            }
        }
    }

    fn establish_child(&self, x: u32, y: u32, direction: BorderCrossingDirection) {
        let handle = (u64::from(x) << 32) | u64::from(y);
        let now = SystemTime::now();
        let mut children = write(&self.child_agent_status);
        if children.get(&handle).is_some_and(|status| {
            now.duration_since(status.request_time).unwrap_or_default() < Duration::from_secs(30)
        }) {
            return;
        }
        let established = self
            .network
            .simulators
            .snapshot()
            .iter()
            .any(|simulator| simulator.handle == handle);
        children.insert(
            handle,
            ChildAgentStatus {
                request_time: now,
                established,
                direction,
            },
        );
    }

    const fn is_near_corner(position: Vector3, size_x: f32, size_y: f32, threshold: f32) -> bool {
        (position.x < threshold || position.x > size_x - threshold)
            && (position.y < threshold || position.y > size_y - threshold)
    }

    fn cleanup_object_tracking(&self) {
        write(&self.object_simulators).retain(|_, simulators| {
            simulators.retain(Simulator::native_is_connected);
            !simulators.is_empty()
        });
    }

    fn track_object(&self, object_id: UUID, simulator: Simulator) {
        if object_id == UUID::zero() {
            return;
        }
        let mut objects = write(&self.object_simulators);
        let simulators = objects.entry(object_id).or_default();
        if !simulators.contains(&simulator) {
            simulators.push(simulator);
        }
    }

    fn untrack_object(&self, object_id: UUID, simulator: &Simulator) {
        let mut objects = write(&self.object_simulators);
        if let Some(simulators) = objects.get_mut(&object_id) {
            simulators.retain(|candidate| candidate != simulator);
            if simulators.is_empty() {
                objects.remove(&object_id);
            }
        }
    }

    fn object_simulators(&self, object_id: UUID) -> Vec<Simulator> {
        read(&self.object_simulators)
            .get(&object_id)
            .cloned()
            .unwrap_or_default()
    }

    pub(crate) fn handle_caps_event(
        self: &Arc<Self>,
        name: &str,
        message: &dyn IMessage,
        simulator: Simulator,
    ) -> Result<bool, Error> {
        match name {
            "TeleportFinish" => {
                let mut finish = crate::messages::linden::TeleportFinishMessage::new()?;
                finish.deserialize(message.serialize()?)?;
                let port = u16::try_from(finish.port).map_err(|_| Error::Argument)?;
                let success = self.connect_destination(
                    SocketAddr::new(finish.ip, port),
                    finish.region_handle,
                    Some(finish.seed_capability),
                    finish.region_size_x,
                    finish.region_size_y,
                );
                self.teleport_event(
                    if success {
                        "Teleport finished".into()
                    } else {
                        "Failed to connect to simulator after teleport".into()
                    },
                    if success {
                        TeleportStatus::Finished
                    } else {
                        TeleportStatus::Failed
                    },
                    finish.flags,
                );
                Ok(true)
            }
            "TeleportFailed" => {
                let mut failed = crate::messages::linden::TeleportFailedMessage::new()?;
                failed.deserialize(message.serialize()?)?;
                let status = mutex(&self.teleport).status;
                if !matches!(status, TeleportStatus::Finished | TeleportStatus::None) {
                    self.teleport_event(
                        failed.reason,
                        TeleportStatus::Failed,
                        TeleportFlags::DEFAULT,
                    );
                }
                Ok(true)
            }
            "CrossedRegion" => {
                let mut crossed = crate::messages::linden::CrossedRegionMessage::new()?;
                crossed.deserialize(message.serialize()?)?;
                let port = u16::try_from(crossed.port).map_err(|_| Error::Argument)?;
                self.begin_crossing(
                    simulator,
                    SocketAddr::new(crossed.ip, port),
                    crossed.region_handle,
                    crossed.seed_capability,
                    crossed.position,
                    crossed.look_at,
                    crossed.region_size_x,
                    crossed.region_size_y,
                );
                Ok(true)
            }
            _ => Ok(false),
        }
    }

    pub(crate) fn sim_position(&self) -> Vector3 {
        let state = *read(&self.kinematics);
        if state.sitting_on == 0 {
            return state.relative_position;
        }
        let Some(simulator) = self.network.native_current_sim() else {
            return state.relative_position;
        };
        let primitives = read(&simulator.objects_primitives);
        let Some(parent) = primitives.get(&state.sitting_on) else {
            return state.relative_position;
        };
        add(
            parent.position,
            Vector3::mul_with_vector3_quaternion(state.relative_position, parent.rotation),
        )
    }

    pub(crate) fn sim_rotation(&self) -> Quaternion {
        let state = *read(&self.kinematics);
        if state.sitting_on == 0 {
            return state.relative_rotation;
        }
        self.network
            .native_current_sim()
            .and_then(|simulator| {
                read(&simulator.objects_primitives)
                    .get(&state.sitting_on)
                    .map(|parent| {
                        Quaternion::mul_with_quaternion_quaternion(
                            state.relative_rotation,
                            parent.rotation,
                        )
                    })
            })
            .unwrap_or(state.relative_rotation)
    }
}

/// Public movement handle. Control and camera state are shared with packet callbacks.
pub struct AgentManagerAgentMovement {
    pub body_rotation: Quaternion,
    pub camera: AgentManagerAgentMovementAgentCamera,
    pub flags: AgentFlags,
    pub head_rotation: Quaternion,
    pub state: AgentState,
    pub(crate) runtime: Arc<AgentMovementRuntime>,
}

impl fmt::Debug for AgentManagerAgentMovement {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("AgentMovement")
            .field("body_rotation", &self.body_rotation)
            .field("camera", &self.camera)
            .field("flags", &self.flags)
            .field("head_rotation", &self.head_rotation)
            .field("state", &self.state)
            .field("controls", &self.agent_controls())
            .finish_non_exhaustive()
    }
}

impl AgentManagerAgentMovement {
    pub(crate) fn from_runtime(runtime: Arc<AgentMovementRuntime>) -> Self {
        let wire = runtime.snapshot();
        Self {
            body_rotation: wire.body_rotation,
            camera: runtime.camera(wire.far),
            flags: wire.flags,
            head_rotation: wire.head_rotation,
            state: wire.state,
            runtime,
        }
    }

    pub fn new(client: GridClient) -> Result<Self, Error> {
        Ok(AgentManager::native_new(Some(Arc::new(client)))?.movement)
    }

    fn wire(&self) -> WireState {
        WireState {
            body_rotation: self.body_rotation,
            head_rotation: self.head_rotation,
            camera_center: self.camera.position(),
            camera_x_axis: self.camera.left_axis(),
            camera_y_axis: self.camera.at_axis(),
            camera_z_axis: self.camera.up_axis(),
            far: self.camera.far,
            flags: self.flags,
            state: self.state,
        }
    }

    pub fn reset_control_flags(&self) -> Result<(), Error> {
        self.runtime.reset_control_flags();
        Ok(())
    }

    pub fn send_manual_update(
        &self,
        control_flags: AgentManagerControlFlags,
        position: Vector3,
        forward_axis: Vector3,
        left_axis: Vector3,
        up_axis: Vector3,
        body_rotation: Quaternion,
        head_rotation: Quaternion,
        far_clip: f32,
        flags: AgentFlags,
        state: AgentState,
        reliable: bool,
    ) -> Result<(), Error> {
        let simulator = self.runtime.current_sim()?;
        if !simulator.native_handshake_complete() {
            return Ok(());
        }
        let (agent_id, session_id) = self.runtime.ids();
        let mut packet = AgentUpdatePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.body_rotation = body_rotation;
        packet.agent_data.head_rotation = head_rotation;
        packet.agent_data.camera_center = position;
        packet.agent_data.camera_at_axis = forward_axis;
        packet.agent_data.camera_left_axis = left_axis;
        packet.agent_data.camera_up_axis = up_axis;
        packet.agent_data.far = far_clip;
        packet.agent_data.control_flags = control_flags.0.cast_unsigned();
        packet.agent_data.flags = flags.0;
        packet.agent_data.state = state.0;
        send_encoded(
            &simulator,
            PacketType::AgentUpdate,
            packet.to_bytes_with_method()?,
            Some(reliable),
        )
    }

    pub fn send_update_with_boolean(&self, reliable: Option<bool>) -> Result<(), Error> {
        self.runtime
            .send_update(self.wire(), reliable.unwrap_or(false), None)
    }

    pub fn send_update_with_boolean_simulator(
        &self,
        reliable: bool,
        simulator: Simulator,
    ) -> Result<(), Error> {
        self.runtime
            .send_update(self.wire(), reliable, Some(simulator))
    }

    pub fn set_fov_vertical_angle(&self, angle: f32) -> Result<(), Error> {
        self.camera.set_vertical_fov_angle(angle)?;
        let (agent_id, session_id) = self.runtime.ids();
        let mut packet = AgentFOVPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.circuit_code = self.runtime.network.native_circuit_code();
        packet.fov_block.gen_counter = 0;
        packet.fov_block.vertical_angle = angle;
        self.runtime
            .send_current(PacketType::AgentFOV, packet.to_bytes_with_method()?, None)
    }

    pub fn turn_toward(&self, target: Vector3, send_update: Option<bool>) -> Result<bool, Error> {
        if !self.runtime.client.settings_ref().agent.send_updates {
            return Ok(false);
        }
        let position = self.runtime.sim_position();
        let direction = normalize(subtract(target, position));
        if direction == Vector3::zero() {
            return Ok(false);
        }
        let rotation = Vector3::rotation_between(Vector3::unit_x(), direction)?;
        self.camera.look_at_with_vector3_vector3(position, target)?;
        let mut wire = self.wire();
        wire.body_rotation = rotation;
        wire.head_rotation = rotation;
        if send_update.unwrap_or(true) {
            self.runtime.send_update(wire, false, None)?;
        } else {
            mutex(&self.runtime.last_update).wire = wire;
        }
        Ok(true)
    }

    pub fn update_from_heading(&self, heading: f64, reliable: bool) -> Result<(), Error> {
        let position = self.runtime.sim_position();
        {
            let mut camera = self.camera.clone();
            camera.set_position(position);
            camera.look_direction_with_double(heading)?;
        }
        let rotation = Quaternion {
            x: self.body_rotation.x,
            y: self.body_rotation.y,
            z: (heading / 2.0).sin() as f32,
            w: (heading / 2.0).cos() as f32,
        };
        let mut wire = self.wire();
        wire.body_rotation = rotation;
        wire.head_rotation = rotation;
        self.runtime.send_update(wire, reliable, None)
    }

    #[must_use]
    pub fn agent_controls(&self) -> u32 {
        self.runtime.controls.load(Ordering::Acquire)
    }

    pub fn set_agent_controls(&mut self, value: u32) {
        self.runtime.controls.store(value, Ordering::Release);
    }

    #[must_use]
    pub fn always_run(&self) -> bool {
        self.runtime.always_run.load(Ordering::Acquire)
    }

    pub fn set_always_run(&mut self, value: bool) {
        self.runtime.always_run.store(value, Ordering::Release);
        let result = (|| {
            let (agent_id, session_id) = self.runtime.ids();
            let mut packet = SetAlwaysRunPacket::new_with_constructor()?;
            packet.agent_data.agent_id = agent_id;
            packet.agent_data.session_id = session_id;
            packet.agent_data.always_run = value;
            self.runtime.send_current(
                PacketType::SetAlwaysRun,
                packet.to_bytes_with_method()?,
                None,
            )
        })();
        let _: Result<(), Error> = result;
    }

    #[must_use]
    pub fn auto_reset_controls(&self) -> bool {
        self.runtime.auto_reset_controls.load(Ordering::Acquire)
    }

    pub fn set_auto_reset_controls(&mut self, value: bool) {
        self.runtime
            .auto_reset_controls
            .store(value, Ordering::Release);
    }

    #[must_use]
    pub fn update_enabled(&self) -> bool {
        self.update_interval() != 0
    }

    #[must_use]
    pub fn update_interval(&self) -> i32 {
        self.runtime.update_interval.load(Ordering::Acquire)
    }

    pub fn set_update_interval(&mut self, value: i32) {
        self.runtime.set_update_interval(value);
    }
}

macro_rules! control_accessors {
    ($(($getter:ident, $setter:ident, $flag:expr)),+ $(,)?) => {
        impl AgentManagerAgentMovement {
            $(
                #[must_use]
                pub fn $getter(&self) -> bool {
                    self.runtime.flag($flag)
                }

                pub fn $setter(&mut self, value: bool) {
                    self.runtime.set_flag($flag, value);
                }
            )+
        }
    };
}

control_accessors!(
    (at_pos, set_at_pos, 1 << 0),
    (at_neg, set_at_neg, 1 << 1),
    (left_pos, set_left_pos, 1 << 2),
    (left_neg, set_left_neg, 1 << 3),
    (up_pos, set_up_pos, 1 << 4),
    (up_neg, set_up_neg, 1 << 5),
    (pitch_pos, set_pitch_pos, 1 << 6),
    (pitch_neg, set_pitch_neg, 1 << 7),
    (yaw_pos, set_yaw_pos, 1 << 8),
    (yaw_neg, set_yaw_neg, 1 << 9),
    (fast_at, set_fast_at, 1 << 10),
    (fast_left, set_fast_left, 1 << 11),
    (fast_up, set_fast_up, 1 << 12),
    (fly, set_fly, 1 << 13),
    (stop, set_stop, 1 << 14),
    (finish_anim, set_finish_anim, 1 << 15),
    (stand_up, set_stand_up, 1 << 16),
    (sit_on_ground, set_sit_on_ground, 1 << 17),
    (mouselook, set_mouselook, 1 << 18),
    (nudge_at_pos, set_nudge_at_pos, 1 << 19),
    (nudge_at_neg, set_nudge_at_neg, 1 << 20),
    (nudge_left_pos, set_nudge_left_pos, 1 << 21),
    (nudge_left_neg, set_nudge_left_neg, 1 << 22),
    (nudge_up_pos, set_nudge_up_pos, 1 << 23),
    (nudge_up_neg, set_nudge_up_neg, 1 << 24),
    (turn_left, set_turn_left, 1 << 25),
    (turn_right, set_turn_right, 1 << 26),
    (away, set_away, 1 << 27),
    (l_button_down, set_l_button_down, 1 << 28),
    (l_button_up, set_l_button_up, 1 << 29),
    (ml_button_down, set_ml_button_down, 1 << 30),
    (ml_button_up, set_ml_button_up, 1 << 31),
);

impl AgentMovementRuntime {
    pub(crate) fn subscribe_teleport_progress(
        &self,
        handler: EventHandler<TeleportEventArgs>,
    ) -> Subscription {
        self.teleport_progress.subscribe(handler)
    }

    pub(crate) fn subscribe_camera_constraint(
        &self,
        handler: EventHandler<CameraConstraintEventArgs>,
    ) -> Subscription {
        self.camera_constraint.subscribe(handler)
    }

    pub(crate) fn subscribe_avatar_sit_response(
        &self,
        handler: EventHandler<AvatarSitResponseEventArgs>,
    ) -> Subscription {
        self.avatar_sit_response.subscribe(handler)
    }

    pub(crate) fn subscribe_region_crossed(
        &self,
        handler: EventHandler<RegionCrossedEventArgs>,
    ) -> Subscription {
        self.region_crossed.subscribe(handler)
    }

    pub(crate) fn subscribe_region_crossing_predicted(
        &self,
        handler: EventHandler<RegionCrossingPredictionEventArgs>,
    ) -> Subscription {
        self.region_crossing_predicted.subscribe(handler)
    }

    pub(crate) fn crossing_state(&self) -> AgentManagerCrossingState {
        mutex(&self.crossing.current)
            .as_ref()
            .map_or(AgentManagerCrossingState::Idle, |crossing| crossing.state)
    }

    pub(crate) fn crossing_failure_reason(&self) -> AgentManagerCrossingFailureReason {
        mutex(&self.crossing.current)
            .as_ref()
            .map_or(AgentManagerCrossingFailureReason::Unknown, |crossing| {
                crossing.failure_reason
            })
    }

    pub(crate) fn crossing_details(&self) -> String {
        let current = mutex(&self.crossing.current);
        let Some(crossing) = current.as_ref() else {
            return "No active crossing".into();
        };
        format!(
            "State: {:?}, Duration: {:.2}s, Retries: {}/3, Old Sim: {}, New Sim: {}, Target: {}, Failure: {:?} - {}",
            crossing.state,
            crossing.started.elapsed().as_secs_f64(),
            crossing.retry_count,
            crossing
                .old_simulator
                .as_ref()
                .map_or("null", |simulator| simulator.name.as_str()),
            crossing
                .new_simulator
                .as_ref()
                .map_or("null", |simulator| simulator.name.as_str()),
            crossing.endpoint,
            crossing.failure_reason,
            crossing.failure_message,
        )
    }

    pub(crate) fn is_crossing(&self) -> bool {
        !matches!(
            self.crossing_state(),
            AgentManagerCrossingState::Idle
                | AgentManagerCrossingState::Completed
                | AgentManagerCrossingState::Failed
        )
    }

    pub(crate) fn cancel_crossing(&self) {
        let mut current = mutex(&self.crossing.current);
        let Some(crossing) = current.as_mut() else {
            return;
        };
        if matches!(
            crossing.state,
            AgentManagerCrossingState::Idle
                | AgentManagerCrossingState::Completed
                | AgentManagerCrossingState::Failed
        ) {
            return;
        }
        crossing.failure_message = "Crossing was manually cancelled".into();
        crossing.cancel_requested = true;
        drop(current);
        self.crossing.wake.notify_all();
    }

    pub(crate) fn acceleration(&self) -> Vector3 {
        read(&self.kinematics).acceleration
    }

    pub(crate) fn angular_velocity(&self) -> Vector3 {
        read(&self.kinematics).angular_velocity
    }

    pub(crate) fn global_position(&self) -> Vector3d {
        let Some(simulator) = self.network.native_current_sim() else {
            return Vector3d::zero();
        };
        let position = self.sim_position();
        let global_x = u32::try_from(simulator.handle >> 32).unwrap_or_default();
        let global_y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default();
        Vector3d {
            x: f64::from(global_x) + f64::from(position.x),
            y: f64::from(global_y) + f64::from(position.y),
            z: f64::from(position.z),
        }
    }

    pub(crate) fn home_position(&self) -> Vector3 {
        read(&self.kinematics).home_position
    }

    pub(crate) fn set_home_position(&self, value: Vector3) {
        write(&self.kinematics).home_position = value;
    }

    pub(crate) fn set_login_look_at(&self, value: Vector3) -> Result<(), Error> {
        self.camera(128.0).look_direction_with_vector3(value)
    }

    pub(crate) fn camera_at_axis(&self) -> Vector3 {
        self.camera(128.0).at_axis()
    }

    pub(crate) fn last_position_update(&self) -> SystemTime {
        read(&self.kinematics).last_position_update
    }

    pub(crate) fn set_last_position_update(&self, value: SystemTime) {
        write(&self.kinematics).last_position_update = value;
    }

    pub(crate) fn relative_position(&self) -> Vector3 {
        read(&self.kinematics).relative_position
    }

    pub(crate) fn set_relative_position(&self, value: Vector3) {
        write(&self.kinematics).relative_position = value;
    }

    pub(crate) fn update_object_kinematics(
        &self,
        position: Vector3,
        rotation: Quaternion,
        velocity: Vector3,
        acceleration: Vector3,
        angular_velocity: Vector3,
    ) {
        let mut state = write(&self.kinematics);
        state.relative_position = position;
        state.relative_rotation = rotation;
        state.velocity = velocity;
        state.acceleration = acceleration;
        state.angular_velocity = angular_velocity;
        state.last_position_update = SystemTime::now();
    }

    pub(crate) fn relative_position_estimate(&self) -> Vector3 {
        let state = *read(&self.kinematics);
        let elapsed = SystemTime::now()
            .duration_since(state.last_position_update)
            .unwrap_or_default()
            .as_secs_f32();
        add(state.relative_position, scale(state.velocity, elapsed))
    }

    pub(crate) fn relative_rotation(&self) -> Quaternion {
        read(&self.kinematics).relative_rotation
    }

    pub(crate) fn set_relative_rotation(&self, value: Quaternion) {
        write(&self.kinematics).relative_rotation = value;
    }

    pub(crate) fn sitting_on(&self) -> u32 {
        read(&self.kinematics).sitting_on
    }

    pub(crate) fn teleport_message(&self) -> String {
        mutex(&self.teleport).message.clone()
    }

    pub(crate) fn set_teleport_message(&self, value: String) {
        mutex(&self.teleport).message = value;
    }

    pub(crate) fn velocity(&self) -> Vector3 {
        read(&self.kinematics).velocity
    }

    pub(crate) fn request_sit(&self, target_id: UUID, offset: Vector3) -> Result<(), Error> {
        let (agent_id, session_id) = self.ids();
        let mut packet = AgentRequestSitPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.target_object.target_id = target_id;
        packet.target_object.offset = offset;
        self.send_current(
            PacketType::AgentRequestSit,
            packet.to_bytes_with_method()?,
            None,
        )
    }

    pub(crate) fn sit(&self) -> Result<(), Error> {
        let (agent_id, session_id) = self.ids();
        let mut packet = AgentSitPacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        self.send_current(PacketType::AgentSit, packet.to_bytes_with_method()?, None)
    }

    pub(crate) fn autopilot(&self, x: String, y: String, z: String) -> Result<(), Error> {
        let (agent_id, session_id) = self.ids();
        let mut packet = GenericMessagePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.agent_data.transaction_id = UUID::zero();
        packet.method_data.invoice = UUID::zero();
        packet.method_data.method = b"autopilot".to_vec();
        packet.param_list = [x, y, z]
            .into_iter()
            .map(|parameter| GenericMessagePacketParamListBlock {
                parameter: parameter.into_bytes(),
            })
            .collect();
        self.send_current(
            PacketType::GenericMessage,
            packet.to_bytes_with_method()?,
            None,
        )
    }

    pub(crate) fn send_lure(&self, target_id: UUID, message: String) -> Result<(), Error> {
        let (agent_id, session_id) = self.ids();
        let mut packet = StartLurePacket::new_with_constructor()?;
        packet.agent_data.agent_id = agent_id;
        packet.agent_data.session_id = session_id;
        packet.info.lure_type = 0;
        packet.info.message = message.into_bytes();
        packet.target_data = vec![StartLurePacketTargetDataBlock { target_id }];
        self.send_current(PacketType::StartLure, packet.to_bytes_with_method()?, None)
    }

    pub(crate) fn accept_lure(&self, session_id: UUID) -> Result<(), Error> {
        let (agent_id, own_session_id) = self.ids();
        let mut packet = TeleportLureRequestPacket::new_with_constructor()?;
        packet.info.agent_id = agent_id;
        packet.info.session_id = own_session_id;
        packet.info.lure_id = session_id;
        packet.info.teleport_flags = TeleportFlags::VIA_LURE.0;
        self.send_current(
            PacketType::TeleportLureRequest,
            packet.to_bytes_with_method()?,
            None,
        )
    }
}

impl AgentManager {
    pub(crate) fn native_movement_runtime(&self) -> &Arc<AgentMovementRuntime> {
        &self.movement.runtime
    }

    pub(crate) fn native_subscribe_teleport_progress(
        &self,
        handler: EventHandler<TeleportEventArgs>,
    ) -> Subscription {
        self.movement.runtime.subscribe_teleport_progress(handler)
    }

    pub(crate) fn native_subscribe_camera_constraint(
        &self,
        handler: EventHandler<CameraConstraintEventArgs>,
    ) -> Subscription {
        self.movement.runtime.subscribe_camera_constraint(handler)
    }

    pub(crate) fn native_subscribe_avatar_sit_response(
        &self,
        handler: EventHandler<AvatarSitResponseEventArgs>,
    ) -> Subscription {
        self.movement.runtime.subscribe_avatar_sit_response(handler)
    }

    pub(crate) fn native_subscribe_region_crossed(
        &self,
        handler: EventHandler<RegionCrossedEventArgs>,
    ) -> Subscription {
        self.movement.runtime.subscribe_region_crossed(handler)
    }

    pub(crate) fn native_subscribe_region_crossing_predicted(
        &self,
        handler: EventHandler<RegionCrossingPredictionEventArgs>,
    ) -> Subscription {
        self.movement
            .runtime
            .subscribe_region_crossing_predicted(handler)
    }

    pub(crate) fn native_track_object_in_simulator(&self, object_id: UUID, simulator: Simulator) {
        self.movement.runtime.track_object(object_id, simulator);
    }

    pub(crate) fn native_untrack_object_in_simulator(
        &self,
        object_id: UUID,
        simulator: &Simulator,
    ) {
        self.movement.runtime.untrack_object(object_id, simulator);
    }

    pub(crate) fn native_get_simulators_for_object(&self, object_id: UUID) -> Vec<Simulator> {
        self.movement.runtime.object_simulators(object_id)
    }

    #[allow(clippy::unused_self)] // Public C# method is instance-shaped but uses only its inputs.
    pub(crate) fn native_is_near_border(
        &self,
        position: Vector3,
        region_size_x: u32,
        region_size_y: u32,
        threshold: f32,
    ) -> bool {
        position.x < threshold
            || position.x > region_size_x as f32 - threshold
            || position.y < threshold
            || position.y > region_size_y as f32 - threshold
    }

    pub(crate) fn native_cancel_crossing(&self) {
        self.movement.runtime.cancel_crossing();
    }

    pub(crate) fn native_crossing_state(&self) -> AgentManagerCrossingState {
        self.movement.runtime.crossing_state()
    }

    pub(crate) fn native_crossing_failure_reason(&self) -> AgentManagerCrossingFailureReason {
        self.movement.runtime.crossing_failure_reason()
    }

    pub(crate) fn native_crossing_details(&self) -> String {
        self.movement.runtime.crossing_details()
    }

    pub(crate) fn native_is_crossing(&self) -> bool {
        self.movement.runtime.is_crossing()
    }

    pub(crate) fn native_complete_agent_movement(&self, simulator: Simulator) -> Result<(), Error> {
        self.movement.runtime.complete_agent_movement(simulator)
    }

    pub(crate) fn native_request_sit(&self, target_id: UUID, offset: Vector3) -> Result<(), Error> {
        self.movement.runtime.request_sit(target_id, offset)
    }

    pub(crate) fn native_sit(&self) -> Result<(), Error> {
        self.movement.runtime.sit()
    }

    pub(crate) fn native_stand(&self) -> Result<bool, Error> {
        if !self.client.settings_ref().agent.send_updates {
            return Ok(false);
        }
        self.movement.runtime.set_flag(1 << 17, false);
        self.movement.runtime.set_flag(1 << 16, true);
        self.movement.runtime.send_last_update(false, None)?;
        self.movement.runtime.set_flag(1 << 16, false);
        self.movement.runtime.send_last_update(false, None)?;
        Ok(true)
    }

    pub(crate) fn native_sit_on_ground(&self) -> Result<(), Error> {
        self.movement.runtime.set_flag(1 << 17, true);
        self.movement.runtime.send_last_update(true, None)
    }

    pub(crate) fn native_fly(&self, start: bool) -> Result<(), Error> {
        self.movement.runtime.set_flag(1 << 13, start);
        self.movement.runtime.send_last_update(true, None)
    }

    pub(crate) fn native_crouch(&self, crouching: bool) -> Result<(), Error> {
        self.movement.runtime.set_flag(1 << 5, crouching);
        self.movement.runtime.send_last_update(true, None)
    }

    pub(crate) fn native_jump(&self, jumping: bool) -> Result<(), Error> {
        self.movement.runtime.set_flag(1 << 4, jumping);
        self.movement.runtime.set_flag(1 << 12, jumping);
        self.movement.runtime.send_last_update(true, None)
    }

    pub(crate) fn native_autopilot_f64(&self, x: f64, y: f64, z: f64) -> Result<(), Error> {
        self.movement
            .runtime
            .autopilot(x.to_string(), y.to_string(), z.to_string())
    }

    pub(crate) fn native_autopilot_u64(&self, x: u64, y: u64, z: f32) -> Result<(), Error> {
        self.movement
            .runtime
            .autopilot(x.to_string(), y.to_string(), z.to_string())
    }

    pub(crate) fn native_autopilot_local(&self, x: i32, y: i32, z: f32) -> Result<(), Error> {
        let Some(simulator) = self.client.network().native_current_sim() else {
            return Ok(());
        };
        let global_x = i64::from(u32::try_from(simulator.handle >> 32).unwrap_or_default())
            .checked_add(i64::from(x))
            .ok_or(Error::Argument)?;
        let global_y =
            i64::from(u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default())
                .checked_add(i64::from(y))
                .ok_or(Error::Argument)?;
        self.movement.runtime.autopilot(
            u64::try_from(global_x)
                .map_err(|_| Error::Argument)?
                .to_string(),
            u64::try_from(global_y)
                .map_err(|_| Error::Argument)?
                .to_string(),
            z.to_string(),
        )
    }

    pub(crate) fn native_autopilot_cancel(&self) -> Result<bool, Error> {
        if !self.client.settings_ref().agent.send_updates {
            return Ok(false);
        }
        self.movement.runtime.set_flag(1, true);
        self.movement.runtime.send_last_update(false, None)?;
        self.movement.runtime.set_flag(1, false);
        self.movement.runtime.send_last_update(false, None)?;
        Ok(true)
    }

    pub(crate) fn native_request_teleport_landmark(&self, landmark: UUID) -> Result<(), Error> {
        self.movement.runtime.request_teleport_landmark(landmark)
    }

    pub(crate) fn native_request_teleport_location(
        &self,
        region_handle: u64,
        position: Vector3,
        look_at: Vector3,
        ignore_caps_status: Option<bool>,
    ) -> Result<(), Error> {
        self.movement.runtime.request_teleport_location(
            region_handle,
            position,
            look_at,
            ignore_caps_status.unwrap_or(false),
        )
    }

    pub(crate) async fn native_teleport_landmark(
        &self,
        landmark: UUID,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        if mutex(&self.movement.runtime.teleport).status == TeleportStatus::Progress {
            return Ok(false);
        }
        let (generation, receiver) = self.movement.runtime.begin_teleport();
        self.movement.runtime.request_teleport_landmark(landmark)?;
        self.movement
            .runtime
            .wait_for_teleport(generation, receiver, cancellation_token)
            .await
    }

    pub(crate) async fn native_teleport_location(
        &self,
        region_handle: u64,
        position: Vector3,
        look_at: Vector3,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        self.movement
            .runtime
            .wait_for_event_queue(cancellation_token.clone())
            .await?;
        let (generation, receiver) = self.movement.runtime.begin_teleport();
        self.movement
            .runtime
            .request_teleport_location(region_handle, position, look_at, true)?;
        self.movement
            .runtime
            .wait_for_teleport(generation, receiver, cancellation_token)
            .await
    }

    pub(crate) async fn native_teleport_name(
        &self,
        sim_name: String,
        position: Vector3,
        look_at: Vector3,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        if sim_name.is_empty() {
            self.movement.runtime.teleport_event(
                "Invalid simulator name".into(),
                TeleportStatus::Failed,
                TeleportFlags::DEFAULT,
            );
            return Ok(false);
        }
        let Some(current) = self.client.network().native_current_sim() else {
            self.movement.runtime.teleport_event(
                format!("Not in a current simulator, cannot teleport to {sim_name}"),
                TeleportStatus::Failed,
                TeleportFlags::DEFAULT,
            );
            return Ok(false);
        };
        let connected_handle = if current.name.eq_ignore_ascii_case(&sim_name) {
            Some(current.handle)
        } else {
            self.client
                .network()
                .simulators
                .snapshot()
                .into_iter()
                .find(|simulator| simulator.name.eq_ignore_ascii_case(&sim_name))
                .map(|simulator| simulator.handle)
        };
        let handle = if let Some(handle) = connected_handle {
            Some(handle)
        } else {
            self.client
                .grid()
                .get_grid_region_with_string_grid_layer_type_cancellation_token(
                    sim_name.clone(),
                    GridLayerType::Objects,
                    cancellation_token.clone(),
                )
                .await?
                .flatten()
                .map(|region| region.region_handle)
        };
        let Some(handle) = handle else {
            self.movement.runtime.teleport_event(
                format!("Unable to resolve simulator named: {sim_name}"),
                TeleportStatus::Failed,
                TeleportFlags::DEFAULT,
            );
            return Ok(false);
        };
        self.native_teleport_location(handle, position, look_at, cancellation_token)
            .await
    }

    pub(crate) fn native_send_teleport_lure(
        &self,
        target_id: UUID,
        message: Option<String>,
    ) -> Result<(), Error> {
        let message = message.unwrap_or_else(|| {
            let region = self
                .client
                .network()
                .native_current_sim()
                .map_or_else(|| "unknown".into(), |simulator| simulator.name.clone());
            format!("Join me in {region}!")
        });
        self.movement.runtime.send_lure(target_id, message)
    }

    pub(crate) fn native_teleport_lure_respond(
        &self,
        requester_id: UUID,
        session_id: UUID,
        accept: bool,
    ) -> Result<(), Error> {
        if accept {
            self.movement.runtime.accept_lure(session_id)
        } else {
            self.native_instant_message_full(
                self.native_name(),
                requester_id,
                String::new(),
                session_id,
                InstantMessageDialog::DenyTeleport,
                InstantMessageOnline::Offline,
                self.movement.runtime.sim_position(),
                UUID::zero(),
                Vec::new(),
            )
        }
    }

    pub(crate) fn native_send_teleport_lure_request(
        &self,
        target_id: UUID,
        session_id: UUID,
        message: String,
    ) -> Result<(), Error> {
        if target_id == self.native_agent_id() {
            return Ok(());
        }
        self.native_instant_message_full(
            self.native_name(),
            target_id,
            message,
            session_id,
            InstantMessageDialog::RequestLure,
            InstantMessageOnline::Offline,
            self.movement.runtime.sim_position(),
            UUID::zero(),
            Vec::new(),
        )
    }
}

#[cfg(test)]
#[allow(clippy::float_cmp)] // Wire fixtures use exactly representable values.
mod tests {
    use super::*;
    use crate::packets::{
        Packet, PacketAckPacket, PacketAckPacketPacketsBlock, RegionHandshakePacket,
    };
    use libremetaverse_types::compat::CancellationTokenSource;
    use std::sync::mpsc::{self, Receiver, Sender};

    fn approx(actual: Vector3, expected: Vector3) {
        assert!((actual.x - expected.x).abs() < 0.000_1, "x: {actual:?}");
        assert!((actual.y - expected.y).abs() < 0.000_1, "y: {actual:?}");
        assert!((actual.z - expected.z).abs() < 0.000_1, "z: {actual:?}");
    }

    fn packet_type(bytes: &[u8]) -> Option<PacketType> {
        let mut packet_end = i32::try_from(bytes.len()).ok()?.checked_sub(1)?;
        Packet::build_packet_with_bytes_int32_bytes(bytes.to_vec(), &mut packet_end, vec![0; 8192])
            .ok()
            .map(|packet| packet.type_)
    }

    fn ack(sequence: u32) -> Vec<u8> {
        let mut packet = PacketAckPacket::new_with_constructor().unwrap();
        packet.packets = vec![PacketAckPacketPacketsBlock { id: sequence }];
        let mut bytes = packet.to_bytes_with_method().unwrap();
        bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED);
        bytes
    }

    fn transport_wire(data: Vec<u8>) -> Vec<u8> {
        if data
            .first()
            .is_none_or(|flags| flags & crate::Helpers::MSG_ZEROCODED == 0)
        {
            return data;
        }
        let mut encoded = vec![0_u8; data.len().saturating_mul(2).saturating_add(2)];
        let length = crate::packet_wire::zero_encode(
            Some(&data),
            i32::try_from(data.len()).unwrap(),
            Some(&mut encoded),
        )
        .unwrap();
        encoded.truncate(usize::try_from(length).unwrap());
        encoded
    }

    struct FakeGrid {
        endpoint: SocketAddr,
        packets: Receiver<(PacketType, Vec<u8>)>,
        shutdown: Sender<()>,
        thread: Option<JoinHandle<()>>,
    }

    impl FakeGrid {
        fn start() -> Self {
            use std::io::ErrorKind;
            use std::net::UdpSocket;
            use std::sync::mpsc::TryRecvError;

            let socket =
                UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap();
            socket
                .set_read_timeout(Some(Duration::from_secs(3)))
                .unwrap();
            let endpoint = socket.local_addr().unwrap();
            let (packet_sender, packets) = mpsc::channel();
            let (shutdown, shutdown_receiver) = mpsc::channel();
            let thread = thread::spawn(move || {
                let mut buffer = [0_u8; 8192];
                let (length, client_endpoint) = socket.recv_from(&mut buffer).unwrap();
                assert_eq!(
                    packet_type(&buffer[..length]),
                    Some(PacketType::UseCircuitCode)
                );
                let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap());
                socket.send_to(&ack(sequence), client_endpoint).unwrap();

                let mut handshake = RegionHandshakePacket::new_with_constructor().unwrap();
                handshake.region_info.sim_name = b"Movement Test Region\0".to_vec();
                let mut bytes = handshake.to_bytes_with_method().unwrap();
                bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED);
                socket.send_to(&bytes, client_endpoint).unwrap();
                socket
                    .set_read_timeout(Some(Duration::from_millis(100)))
                    .unwrap();

                loop {
                    match shutdown_receiver.try_recv() {
                        Ok(()) | Err(TryRecvError::Disconnected) => break,
                        Err(TryRecvError::Empty) => {}
                    }
                    let (length, _) = match socket.recv_from(&mut buffer) {
                        Ok(value) => value,
                        Err(error)
                            if matches!(
                                error.kind(),
                                ErrorKind::WouldBlock | ErrorKind::TimedOut
                            ) =>
                        {
                            continue;
                        }
                        Err(_) => break,
                    };
                    if buffer[0] & crate::Helpers::MSG_RELIABLE != 0 {
                        let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap());
                        socket.send_to(&ack(sequence), client_endpoint).unwrap();
                    }
                    let Some(kind) = packet_type(&buffer[..length]) else {
                        continue;
                    };
                    if !matches!(
                        kind,
                        PacketType::PacketAck | PacketType::RegionHandshakeReply
                    ) {
                        packet_sender
                            .send((kind, buffer[..length].to_vec()))
                            .unwrap();
                    }
                }
            });
            Self {
                endpoint,
                packets,
                shutdown,
                thread: Some(thread),
            }
        }

        fn receive(&self, expected: PacketType) -> Vec<u8> {
            loop {
                let (kind, bytes) = self
                    .packets
                    .recv_timeout(Duration::from_secs(3))
                    .expect("fake grid packet");
                if kind == expected {
                    return bytes;
                }
            }
        }
    }

    impl Drop for FakeGrid {
        fn drop(&mut self) {
            let _ = self.shutdown.send(());
            if let Some(handle) = self.thread.take() {
                handle.join().unwrap();
            }
        }
    }

    #[test]
    fn camera_math_preserves_the_reference_frame_axis_mapping() {
        let camera = AgentManagerAgentMovementAgentCamera::new().unwrap();
        approx(
            camera.position(),
            Vector3 {
                x: 128.0,
                y: 128.0,
                z: 20.0,
            },
        );
        camera
            .look_direction_with_vector3(Vector3::unit_x())
            .unwrap();
        approx(camera.left_axis(), Vector3::unit_x());
        approx(camera.at_axis(), Vector3::unit_y());
        approx(camera.up_axis(), Vector3::unit_z());

        camera
            .look_at_with_vector3_vector3(
                Vector3::zero(),
                Vector3 {
                    x: 0.0,
                    y: 10.0,
                    z: 0.0,
                },
            )
            .unwrap();
        approx(camera.left_axis(), Vector3::unit_y());
        approx(
            camera.at_axis(),
            Vector3 {
                x: -1.0,
                y: 0.0,
                z: 0.0,
            },
        );
        assert!(camera.look_direction_with_double(f64::NAN).is_err());
    }

    #[test]
    fn control_bits_and_reset_rules_match_agent_movement() {
        let client = Arc::new(GridClient::new().unwrap());
        let manager = AgentManager::native_new(Some(client)).unwrap();
        let mut movement = manager.movement;
        movement.set_agent_controls(u32::MAX);
        movement.reset_control_flags().unwrap();
        assert_eq!(
            movement.agent_controls(),
            (1 << 27) | (1 << 13) | (1 << 18) | (1 << 5)
        );
        movement.set_at_pos(true);
        movement.set_turn_right(true);
        assert!(movement.at_pos());
        assert!(movement.turn_right());
        movement.set_update_interval(-5);
        assert_eq!(movement.update_interval(), 0);
        assert!(!movement.update_enabled());
    }

    #[test]
    fn fake_grid_decodes_locomotion_camera_and_autopilot_packets() {
        let grid = FakeGrid::start();
        let mut client = GridClient::new().unwrap();
        client.settings().timing().login_timeout = 2_000;
        let client = Arc::new(client);
        let mut manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap();
        manager.native_set_agent_id(UUID::random().unwrap());
        manager.native_set_session_id(UUID::random().unwrap());
        let mut network = client.network();
        network.set_circuit_code(0x1234_5678);
        let simulator = network
            .native_connect(grid.endpoint, 0x1000, true, None, 256, 256)
            .unwrap()
            .unwrap();
        let initial_complete = grid.receive(PacketType::CompleteAgentMovement);
        assert_ne!(
            initial_complete[0] & crate::Helpers::MSG_RELIABLE,
            0,
            "the simulator transition must survive UDP startup loss"
        );
        manager.complete_agent_movement(simulator.clone()).unwrap();
        let repeated_complete = grid.receive(PacketType::CompleteAgentMovement);
        assert_ne!(
            repeated_complete[0] & crate::Helpers::MSG_RELIABLE,
            0,
            "explicit simulator transitions must be reliable"
        );

        let mut complete = AgentMovementCompletePacket::new_with_constructor().unwrap();
        complete.data.position = Vector3 {
            x: 10.0,
            y: 20.0,
            z: 30.0,
        };
        complete.data.look_at = Vector3::unit_x();
        manager
            .native_movement_runtime()
            .handle_raw_packet(
                PacketType::AgentMovementComplete,
                &transport_wire(complete.to_bytes_with_method().unwrap()),
                simulator.clone(),
            )
            .unwrap();
        assert_eq!(manager.sim_position(), complete.data.position);

        manager.native_fly(true).unwrap();
        let update =
            decode_packet::<AgentUpdatePacket>(&grid.receive(PacketType::AgentUpdate)).unwrap();
        assert_eq!(update.agent_data.control_flags, 1 << 13);
        assert_eq!(update.agent_data.agent_id, manager.native_agent_id());
        assert_eq!(update.agent_data.session_id, manager.native_session_id());

        manager.movement.set_always_run(true);
        let run =
            decode_packet::<SetAlwaysRunPacket>(&grid.receive(PacketType::SetAlwaysRun)).unwrap();
        assert!(run.agent_data.always_run);

        manager.movement.set_fov_vertical_angle(1.25).unwrap();
        assert_eq!(manager.movement.camera.vertical_fov_angle(), 1.25);
        let fov = decode_packet::<AgentFOVPacket>(&grid.receive(PacketType::AgentFOV)).unwrap();
        assert_eq!(fov.fov_block.vertical_angle, 1.25);

        manager.native_autopilot_u64(1000, 2000, 30.5).unwrap();
        let autopilot =
            decode_packet::<GenericMessagePacket>(&grid.receive(PacketType::GenericMessage))
                .unwrap();
        assert_eq!(wire_string(autopilot.method_data.method), "autopilot");
        assert_eq!(autopilot.param_list.len(), 3);
        assert_eq!(
            wire_string(autopilot.param_list[0].parameter.clone()),
            "1000"
        );
        assert_eq!(
            wire_string(autopilot.param_list[1].parameter.clone()),
            "2000"
        );
        assert_eq!(
            wire_string(autopilot.param_list[2].parameter.clone()),
            "30.5"
        );

        manager.movement.set_auto_reset_controls(true);
        manager.movement.set_at_pos(true);
        manager
            .movement
            .send_update_with_boolean(Some(false))
            .unwrap();
        let _ = grid.receive(PacketType::AgentUpdate);
        assert_eq!(manager.movement.agent_controls(), 1 << 13);

        manager.dispose().unwrap();
        simulator.native_disconnect(false).unwrap();
    }

    #[tokio::test(flavor = "current_thread", start_paused = true)]
    async fn teleport_completion_timeout_and_cancellation_are_deterministic() {
        let mut client = GridClient::new().unwrap();
        client.settings().timing().teleport_timeout = 5_000;
        let client = Arc::new(client);
        let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap();
        let runtime = Arc::clone(manager.native_movement_runtime());
        let simulator = Simulator::native_new(
            (*client).clone(),
            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13001),
            0x1000,
            Some(256),
            Some(256),
        )
        .unwrap();
        runtime
            .network
            .native_set_current_sim(Some(simulator.clone()));
        let queue_source = CancellationTokenSource::new();
        let queue_token = queue_source.token();
        let queue_runtime = Arc::clone(&runtime);
        let queue_wait =
            tokio::spawn(
                async move { queue_runtime.wait_for_event_queue(Some(queue_token)).await },
            );
        tokio::task::yield_now().await;
        queue_source.cancel();
        assert!(matches!(queue_wait.await.unwrap(), Err(Error::Cancelled)));

        let events = Arc::new(Mutex::new(Vec::new()));
        let recorded = Arc::clone(&events);
        let _subscription = runtime.subscribe_teleport_progress(Arc::new(move |event| {
            mutex(&recorded).push((event.status(), event.message()));
        }));

        let (generation, receiver) = runtime.begin_teleport();
        let mut local = TeleportLocalPacket::new_with_constructor().unwrap();
        local.info.position = Vector3 {
            x: 40.0,
            y: 50.0,
            z: 60.0,
        };
        local.info.look_at = Vector3::unit_y();
        runtime
            .handle_raw_packet(
                PacketType::TeleportLocal,
                &transport_wire(local.to_bytes_with_method().unwrap()),
                simulator.clone(),
            )
            .unwrap();
        assert!(
            runtime
                .wait_for_teleport(generation, receiver, None)
                .await
                .unwrap()
        );
        assert_eq!(runtime.sim_position(), local.info.position);
        assert_eq!(
            mutex(&events).as_slice(),
            &[(TeleportStatus::Finished, "Teleport finished".into())]
        );

        let (generation, receiver) = runtime.begin_teleport();
        let timeout_runtime = Arc::clone(&runtime);
        let timeout = tokio::spawn(async move {
            timeout_runtime
                .wait_for_teleport(generation, receiver, None)
                .await
        });
        tokio::task::yield_now().await;
        tokio::time::advance(Duration::from_secs(5)).await;
        assert!(!timeout.await.unwrap().unwrap());
        assert_eq!(mutex(&runtime.teleport).status, TeleportStatus::Failed);

        let (generation, receiver) = runtime.begin_teleport();
        let source = CancellationTokenSource::new();
        let token = source.token();
        let cancelled_runtime = Arc::clone(&runtime);
        let cancelled = tokio::spawn(async move {
            cancelled_runtime
                .wait_for_teleport(generation, receiver, Some(token))
                .await
        });
        tokio::task::yield_now().await;
        source.cancel();
        assert!(matches!(cancelled.await.unwrap(), Err(Error::Cancelled)));
        assert!(mutex(&runtime.teleport).waiter.is_none());

        manager.dispose().unwrap();
    }

    #[test]
    fn crossing_diagnostics_and_manual_cancellation_are_consistent() {
        let client = Arc::new(GridClient::new().unwrap());
        let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap();
        let runtime = manager.native_movement_runtime();
        let simulator = Simulator::native_new(
            (*client).clone(),
            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13002),
            0x1000,
            Some(256),
            Some(256),
        )
        .unwrap();
        *mutex(&runtime.crossing.current) = Some(CrossingInfo {
            state: AgentManagerCrossingState::WaitingForComplete,
            started: Instant::now(),
            old_simulator: Some(simulator),
            new_simulator: None,
            region_handle: 0x2000,
            endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13003),
            seed: Uri("https://caps.test/seed".into()),
            position: Vector3::zero(),
            look_at: Vector3::unit_x(),
            size_x: 256,
            size_y: 256,
            retry_count: 2,
            failure_reason: AgentManagerCrossingFailureReason::ConnectionFailed,
            failure_message: "connection failed".into(),
            generation: 1,
            cancel_requested: false,
            restored_old_simulator: false,
        });
        assert!(manager.is_crossing().unwrap());
        assert_eq!(
            manager.get_crossing_state().unwrap(),
            AgentManagerCrossingState::WaitingForComplete
        );
        assert_eq!(
            manager.get_crossing_failure_reason().unwrap(),
            AgentManagerCrossingFailureReason::ConnectionFailed
        );
        let details = manager.get_crossing_details().unwrap();
        assert!(details.contains("State: WaitingForComplete"));
        assert!(details.contains("Retries: 2/3"));
        manager.cancel_crossing().unwrap();
        assert!(
            mutex(&runtime.crossing.current)
                .as_ref()
                .unwrap()
                .cancel_requested
        );
        assert!(
            manager
                .get_crossing_details()
                .unwrap()
                .contains("Crossing was manually cancelled")
        );
        manager.dispose().unwrap();
    }

    #[test]
    fn fake_grids_complete_a_region_crossing_and_raise_the_final_event() {
        let old_grid = FakeGrid::start();
        let new_grid = FakeGrid::start();
        let mut client = GridClient::new().unwrap();
        client.settings().timing().login_timeout = 2_000;
        let client = Arc::new(client);
        let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap();
        let mut network = client.network();
        network.set_circuit_code(0x1234_5678);
        let old_simulator = network
            .native_connect(old_grid.endpoint, 0x1000, true, None, 256, 256)
            .unwrap()
            .unwrap();
        let _ = old_grid.receive(PacketType::CompleteAgentMovement);
        old_simulator.native_set_agent_movement_complete(true);

        let (event_sender, event_receiver) = mpsc::channel();
        let _subscription = manager.native_subscribe_region_crossed(Arc::new(move |event| {
            event_sender.send(event).unwrap();
        }));
        let runtime = manager.native_movement_runtime();
        assert!(runtime.begin_crossing(
            old_simulator.clone(),
            new_grid.endpoint,
            0x2000,
            Uri("http://127.0.0.1:1/seed".into()),
            Vector3 {
                x: 12.0,
                y: 24.0,
                z: 36.0,
            },
            Vector3::unit_y(),
            256,
            256,
        ));
        let _ = new_grid.receive(PacketType::CompleteAgentMovement);

        let current = mutex(&runtime.crossing.current);
        let (current, timeout) = runtime
            .crossing
            .wake
            .wait_timeout_while(current, Duration::from_secs(3), |current| {
                current.as_ref().is_none_or(|crossing| {
                    crossing.state != AgentManagerCrossingState::WaitingForComplete
                })
            })
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        assert!(!timeout.timed_out(), "crossing did not reach movement wait");
        let new_simulator = current
            .as_ref()
            .and_then(|crossing| crossing.new_simulator.clone())
            .expect("destination simulator");
        drop(current);

        let mut complete = AgentMovementCompletePacket::new_with_constructor().unwrap();
        complete.data.position = Vector3 {
            x: 12.0,
            y: 24.0,
            z: 36.0,
        };
        complete.data.look_at = Vector3::unit_y();
        runtime
            .handle_raw_packet(
                PacketType::AgentMovementComplete,
                &transport_wire(complete.to_bytes_with_method().unwrap()),
                new_simulator.clone(),
            )
            .unwrap();
        let event = event_receiver
            .recv_timeout(Duration::from_secs(3))
            .expect("RegionCrossed event");
        assert_eq!(event.old_simulator(), Some(old_simulator.clone()));
        assert_eq!(event.new_simulator(), Some(new_simulator.clone()));
        assert_eq!(
            manager.get_crossing_state().unwrap(),
            AgentManagerCrossingState::Idle
        );
        assert_eq!(network.native_current_sim(), Some(new_simulator.clone()));
        assert!(!old_simulator.native_agent_movement_complete());

        manager.dispose().unwrap();
        old_simulator.native_disconnect(false).unwrap();
        new_simulator.native_disconnect(false).unwrap();
    }

    #[test]
    fn multi_simulator_prediction_and_object_visibility_match_reference_rules() {
        let mut client = GridClient::new().unwrap();
        client.settings().agent_settings_mut().multiple_sims = true;
        let client = Arc::new(client);
        let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap();
        let simulator = Simulator::native_new(
            (*client).clone(),
            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13010),
            (u64::from(1024_u32) << 32) | u64::from(2048_u32),
            Some(256),
            Some(256),
        )
        .unwrap();
        let other = Simulator::native_new(
            (*client).clone(),
            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13011),
            (u64::from(1280_u32) << 32) | u64::from(2048_u32),
            Some(256),
            Some(256),
        )
        .unwrap();
        simulator.native_set_agent_movement_complete(true);
        let runtime = manager.native_movement_runtime();
        {
            let mut state = write(&runtime.kinematics);
            state.relative_position = Vector3 {
                x: 255.0,
                y: 128.0,
                z: 20.0,
            };
            state.velocity = Vector3::unit_x();
        }
        let (prediction_sender, prediction_receiver) = mpsc::channel();
        let _prediction =
            manager.native_subscribe_region_crossing_predicted(Arc::new(move |event| {
                prediction_sender.send(event).unwrap();
            }));
        runtime.update_multi_simulator_state(&simulator);
        let prediction = prediction_receiver.try_recv().unwrap();
        assert_eq!(prediction.current_simulator(), simulator);
        assert_eq!(prediction.direction(), BorderCrossingDirection::East);
        assert_eq!(prediction.time_until_crossing(), 1.0);
        let state = read(&runtime.simulator_states)[&simulator.native_ip_end_point()];
        assert_eq!(state.position.x, 255.0);
        assert!(state.is_present);

        let object_id = UUID::random().unwrap();
        manager
            .track_object_in_simulator(object_id, simulator.clone())
            .unwrap();
        manager
            .track_object_in_simulator(object_id, other.clone())
            .unwrap();
        manager
            .track_object_in_simulator(object_id, simulator.clone())
            .unwrap();
        assert_eq!(
            manager.get_simulators_for_object(object_id).unwrap().len(),
            2
        );
        manager
            .untrack_object_in_simulator(object_id, simulator)
            .unwrap();
        assert_eq!(
            manager.get_simulators_for_object(object_id).unwrap(),
            [other]
        );
        assert!(
            manager
                .is_near_border(
                    Vector3 {
                        x: 31.0,
                        y: 128.0,
                        z: 0.0,
                    },
                    256,
                    256,
                    None,
                )
                .unwrap()
        );
        assert!(
            !manager
                .is_near_border(
                    Vector3 {
                        x: 128.0,
                        y: 128.0,
                        z: 0.0,
                    },
                    256,
                    256,
                    None,
                )
                .unwrap()
        );
        manager.dispose().unwrap();
    }
}
