//! Grid map discovery, region caches, map items, and simulator feature storage.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_possible_truncation)] // Mapped hash and wire casts match CLR semantics.
#![allow(clippy::cast_sign_loss)] // Positive timeout is clamped before conversion.
#![allow(clippy::duration_suboptimal_units)] // Cache duration is explicit in protocol seconds.
#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString method.
#![allow(clippy::too_many_lines)] // Packet dispatch is centralized and bounded per packet.
#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructors are fallible.

use crate::agent_manager::EventRegistry;
use crate::network_manager::RawPacketReceivedEventArgs;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::{Error, GridClient, GridItemType, GridLayerType, RegionFlags, SimAccess, Simulator};
use futures_channel::oneshot;
use futures_util::future::{Either, select};
use futures_util::pin_mut;
use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use libremetaverse_types::{UUID, Vector3};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, SystemTime};
use tokio::sync::Notify;

const MAX_GRID_PACKET_BYTES: usize = 8 * 1024 * 1024;
const CACHE_TTL: Duration = Duration::from_secs(15 * 60);

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(value: &[u8]) -> String {
    String::from_utf8_lossy(value.split(|byte| *byte == 0).next().unwrap_or_default()).into_owned()
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GridLayer {
    pub bottom: i32,
    pub image_id: UUID,
    pub left: i32,
    pub right: i32,
    pub top: i32,
}
impl GridLayer {
    pub fn contains_region(&self, x: i32, y: i32) -> Result<bool, Error> {
        Ok(x >= self.left && x <= self.right && y >= self.bottom && y <= self.top)
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GridRegion {
    pub access: SimAccess,
    pub agents: u8,
    pub map_image_id: UUID,
    pub name: String,
    pub region_flags: RegionFlags,
    pub region_handle: u64,
    pub water_height: u8,
    pub x: i32,
    pub y: i32,
}
impl GridRegion {
    pub fn equals(&self, obj: Option<libremetaverse_types::compat::Object>) -> bool {
        obj.as_ref().and_then(|value| value.downcast_ref::<Self>()) == Some(self)
    }
    pub fn get_hash_code(&self) -> i32 {
        let folded = self.region_handle ^ (self.region_handle >> 32);
        folded as i32
    }
    pub fn to_string(&self) -> String {
        format!("{} ({}, {})", self.name, self.x, self.y)
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapItem {
    pub global_x: u32,
    pub global_y: u32,
    pub id: UUID,
    pub name: String,
    pub extra: i32,
    pub extra2: i32,
    /// Protocol-specific payload retained from the map-item reply.
    pub data: MapItemData,
}

/// Native Rust representation of the C# `MapItem` subclass carried by a map
/// item reply. Unknown/legacy item kinds retain every wire field in `Raw`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MapItemData {
    Telehub(MapTelehub),
    AgentLocation(MapAgentLocation),
    LandForSale(MapLandForSale),
    AdultLandForSale(MapAdultLandForSale),
    PgEvent(MapPGEvent),
    MatureEvent(MapMatureEvent),
    AdultEvent(MapAdultEvent),
    Raw {
        id: UUID,
        name: String,
        extra: i32,
        extra2: i32,
    },
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapAgentLocation {
    pub avatar_count: i32,
    pub identifier: String,
}
impl MapAgentLocation {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            avatar_count: 0,
            identifier: String::new(),
        })
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapLandForSale {
    pub id: UUID,
    pub name: String,
    pub price: i32,
    pub size: i32,
}
impl MapLandForSale {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            id: UUID::zero(),
            name: String::new(),
            price: 0,
            size: 0,
        })
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapAdultLandForSale {
    pub id: UUID,
    pub name: String,
    pub price: i32,
    pub size: i32,
}
impl MapAdultLandForSale {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            id: UUID::zero(),
            name: String::new(),
            price: 0,
            size: 0,
        })
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapPGEvent {
    pub category: crate::DirectoryManagerEventCategories,
    pub description: String,
    pub flags: crate::DirectoryManagerEventFlags,
}
impl MapPGEvent {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            category: crate::DirectoryManagerEventCategories::All,
            description: String::new(),
            flags: crate::DirectoryManagerEventFlags::PG,
        })
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapMatureEvent {
    pub category: crate::DirectoryManagerEventCategories,
    pub description: String,
    pub flags: crate::DirectoryManagerEventFlags,
}
impl MapMatureEvent {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            category: crate::DirectoryManagerEventCategories::All,
            description: String::new(),
            flags: crate::DirectoryManagerEventFlags::Mature,
        })
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MapAdultEvent {
    pub category: crate::DirectoryManagerEventCategories,
    pub description: String,
    pub flags: crate::DirectoryManagerEventFlags,
}
impl MapAdultEvent {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            category: crate::DirectoryManagerEventCategories::All,
            description: String::new(),
            flags: crate::DirectoryManagerEventFlags::Adult,
        })
    }
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MapTelehub;
impl MapTelehub {
    pub fn new() -> Result<Self, Error> {
        Ok(Self)
    }
}
impl MapItem {
    pub fn local_x(&self) -> u32 {
        self.global_x & 0xff
    }
    pub fn local_y(&self) -> u32 {
        self.global_y & 0xff
    }
    pub fn region_handle(&self) -> u64 {
        (u64::from(self.global_x & !0xff) << 32) | u64::from(self.global_y & !0xff)
    }
}

#[derive(Clone)]
pub struct CoarseLocationUpdateEventArgs {
    simulator: Simulator,
    positions: HashMap<UUID, Vector3>,
    new_entries: Vec<UUID>,
    removed_entries: Vec<UUID>,
}
impl CoarseLocationUpdateEventArgs {
    pub fn new(
        simulator: Simulator,
        positions: HashMap<UUID, Vector3>,
        new_entries: Vec<UUID>,
        removed_entries: Vec<UUID>,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            positions,
            new_entries,
            removed_entries,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn positions(&self) -> HashMap<UUID, Vector3> {
        self.positions.clone()
    }
    pub fn new_entries(&self) -> Vec<UUID> {
        self.new_entries.clone()
    }
    pub fn removed_entries(&self) -> Vec<UUID> {
        self.removed_entries.clone()
    }
}

#[derive(Clone)]
pub struct GridItemsEventArgs {
    type_: GridItemType,
    items: Vec<MapItem>,
}
impl GridItemsEventArgs {
    pub fn new(type_: GridItemType, items: Vec<MapItem>) -> Result<Self, Error> {
        Ok(Self { type_, items })
    }
    pub fn type_(&self) -> GridItemType {
        self.type_
    }
    pub fn items(&self) -> Vec<MapItem> {
        self.items.clone()
    }
}
#[derive(Clone)]
pub struct GridLayerEventArgs {
    layer: GridLayer,
}
impl GridLayerEventArgs {
    pub fn new(layer: GridLayer) -> Result<Self, Error> {
        Ok(Self { layer })
    }
    pub fn layer(&self) -> GridLayer {
        self.layer.clone()
    }
}
#[derive(Clone)]
pub struct GridRegionEventArgs {
    region: GridRegion,
}
impl GridRegionEventArgs {
    pub fn new(region: GridRegion) -> Result<Self, Error> {
        Ok(Self { region })
    }
    pub fn region(&self) -> GridRegion {
        self.region.clone()
    }
}
#[derive(Clone)]
pub struct RegionHandleReplyEventArgs {
    region_id: UUID,
    region_handle: u64,
}
impl RegionHandleReplyEventArgs {
    pub fn new(region_id: UUID, region_handle: u64) -> Result<Self, Error> {
        Ok(Self {
            region_id,
            region_handle,
        })
    }
    pub fn region_id(&self) -> UUID {
        self.region_id
    }
    pub fn region_handle(&self) -> u64 {
        self.region_handle
    }
}

#[derive(Clone, Default)]
pub struct SimulatorFeatures(Arc<RwLock<HashMap<String, OSD>>>);
impl SimulatorFeatures {
    pub fn get(&self, feature: String) -> Result<Option<OSD>, Error> {
        Ok(read(&self.0).get(&feature).cloned())
    }
    pub fn has(&self, feature: String) -> Result<bool, Error> {
        Ok(read(&self.0).contains_key(&feature))
    }
    pub fn set_features(
        &self,
        _response: Option<libremetaverse_types::compat::HttpResponse>,
        response_data: Option<Vec<u8>>,
        error: Option<libremetaverse_types::compat::ExternalError>,
    ) -> Result<(), Error> {
        if error.is_some() {
            return Err(Error::InvalidOperation);
        }
        let bytes = response_data.ok_or(Error::ArgumentNull)?;
        if bytes.len() > MAX_GRID_PACKET_BYTES {
            return Err(Error::Argument);
        }
        let OSD::Map(features) = OSDParser::deserialize_with_bytes(bytes)? else {
            return Err(Error::Argument);
        };
        *write(&self.0) = features;
        Ok(())
    }
}

#[derive(Default)]
struct GridEvents {
    coarse: EventRegistry<CoarseLocationUpdateEventArgs>,
    items: EventRegistry<GridItemsEventArgs>,
    layer: EventRegistry<GridLayerEventArgs>,
    region: EventRegistry<GridRegionEventArgs>,
    handle: EventRegistry<RegionHandleReplyEventArgs>,
}
#[derive(Clone)]
struct CachedRegion {
    region: GridRegion,
    seen: SystemTime,
}
pub(crate) struct GridManagerInner {
    client: Arc<GridClient>,
    events: GridEvents,
    regions: RwLock<HashMap<String, CachedRegion>>,
    handles: RwLock<HashMap<u64, CachedRegion>>,
    uuids: RwLock<HashMap<UUID, u64>>,
    items: RwLock<HashMap<GridItemType, Vec<MapItem>>>,
    coarse_positions: RwLock<HashMap<u64, HashMap<UUID, Vector3>>>,
    notify: Notify,
    raw_subscription: Mutex<Option<Subscription>>,
    caps_subscriptions: Mutex<Vec<Subscription>>,
    disposed: AtomicBool,
    sun_ang_velocity: RwLock<Vector3>,
    sun_direction: RwLock<Vector3>,
    sun_phase: RwLock<f32>,
    time_of_day: RwLock<u64>,
}
#[derive(Clone)]
pub struct GridManager {
    pub(crate) inner: Arc<GridManagerInner>,
}

impl GridManager {
    pub fn new(client: Option<Arc<GridClient>>) -> Result<Self, Error> {
        Self::native_new(client.ok_or(Error::ArgumentNull)?)
    }
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        if let Some(inner) = client.cached_grid_manager_inner() {
            return Ok(Self { inner });
        }
        let inner = Arc::new(GridManagerInner {
            client: Arc::clone(&client),
            events: GridEvents::default(),
            regions: RwLock::new(HashMap::new()),
            handles: RwLock::new(HashMap::new()),
            uuids: RwLock::new(HashMap::new()),
            items: RwLock::new(HashMap::new()),
            coarse_positions: RwLock::new(HashMap::new()),
            notify: Notify::new(),
            raw_subscription: Mutex::new(None),
            caps_subscriptions: Mutex::new(Vec::new()),
            disposed: AtomicBool::new(false),
            sun_ang_velocity: RwLock::new(Vector3::default()),
            sun_direction: RwLock::new(Vector3::default()),
            sun_phase: RwLock::new(0.0),
            time_of_day: RwLock::new(0),
        });
        let weak = Arc::downgrade(&inner);
        *mutex(&inner.raw_subscription) = Some(client.network().subscribe_raw_packet(Arc::new(
            move |event| {
                if let Some(inner) = weak.upgrade() {
                    inner.handle_packet(event);
                }
            },
        )));
        mutex(&inner.caps_subscriptions).push(client.network().subscribe_caps(
            "SimulatorFeatures".into(),
            crate::CapsEventQueueCallback::from_handler(move |_, message, simulator| {
                if let Ok(map) = message.serialize()
                    && let Ok(bytes) = OSDParser::serialize_llsd_xml_bytes(OSD::Map(map.snapshot()))
                {
                    let _ = simulator.features.set_features(None, Some(bytes), None);
                }
            }),
        ));
        Ok(Self {
            inner: client.install_grid_manager_inner(inner),
        })
    }
    fn agent_ids(&self) -> (UUID, UUID) {
        let mut client = (*self.inner.client).clone();
        let agent = client.self_();
        (agent.agent_id(), agent.session_id())
    }
    fn send<T: GeneratedPacket>(&self, packet: &T, kind: PacketType) -> Result<(), Error> {
        if self.inner.disposed.load(Ordering::Acquire) {
            return Err(Error::InvalidOperation);
        }
        let sim = self
            .inner
            .client
            .network()
            .current_sim()
            .ok_or(Error::InvalidOperation)?;
        let bytes = packet.encode_packet()?;
        let result = sim.native_send_packet_data(
            bytes.clone(),
            i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
            kind,
            false,
        );
        match result {
            Err(Error::InvalidOperation) if !sim.connected() => Ok(()),
            result => result,
        }
    }
    async fn capability(
        &self,
        name: &str,
        token: &CancellationToken,
    ) -> Result<Option<Uri>, Error> {
        let Some(caps) = self
            .inner
            .client
            .network()
            .current_sim()
            .and_then(|sim| sim.native_caps())
        else {
            return Ok(None);
        };
        if let Some(uri) = caps.capability_uri(name.to_owned())? {
            return Ok(Some(uri));
        }
        if caps.seed_request_finished() {
            return Ok(None);
        }
        let (sender, receiver) = oneshot::channel();
        let sender = Arc::new(Mutex::new(Some(sender)));
        let wake = Arc::clone(&sender);
        let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| {
            if let Some(sender) = mutex(&wake).take() {
                let _ = sender.send(());
            }
        })));
        if let Some(uri) = caps.capability_uri(name.to_owned())? {
            return Ok(Some(uri));
        }
        let cancelled = token.cancelled();
        pin_mut!(receiver, cancelled);
        match select(receiver, cancelled).await {
            Either::Left(_) => caps.capability_uri(name.to_owned()),
            Either::Right(_) => Err(Error::Cancelled),
        }
    }
    pub fn dispose(&self) -> Result<(), Error> {
        self.inner.disposed.store(true, Ordering::Release);
        mutex(&self.inner.raw_subscription).take();
        mutex(&self.inner.caps_subscriptions).clear();
        Ok(())
    }
    pub fn subscribe_coarse_location_update(
        &self,
        h: EventHandler<CoarseLocationUpdateEventArgs>,
    ) -> Subscription {
        self.inner.events.coarse.subscribe(h)
    }
    pub fn subscribe_grid_items(&self, h: EventHandler<GridItemsEventArgs>) -> Subscription {
        self.inner.events.items.subscribe(h)
    }
    pub fn subscribe_grid_layer(&self, h: EventHandler<GridLayerEventArgs>) -> Subscription {
        self.inner.events.layer.subscribe(h)
    }
    pub fn subscribe_grid_region(&self, h: EventHandler<GridRegionEventArgs>) -> Subscription {
        self.inner.events.region.subscribe(h)
    }
    pub fn subscribe_region_handle_reply(
        &self,
        h: EventHandler<RegionHandleReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.handle.subscribe(h)
    }

    pub fn request_map_blocks(
        &self,
        layer: GridLayerType,
        min_x: u16,
        min_y: u16,
        max_x: u16,
        max_y: u16,
        return_non_existent: bool,
    ) -> Result<(), Error> {
        if min_x > max_x || min_y > max_y {
            return Err(Error::Argument);
        }
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::MapBlockRequestPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.agent_data.flags = (layer as u32) | if return_non_existent { 0x10000 } else { 0 };
        p.position_data.min_x = min_x;
        p.position_data.min_y = min_y;
        p.position_data.max_x = max_x;
        p.position_data.max_y = max_y;
        self.send(&p, PacketType::MapBlockRequest)
    }
    pub fn request_mainland_sims(&self, layer: GridLayerType) -> Result<(), Error> {
        self.request_map_blocks(layer, 0, 0, u16::MAX, u16::MAX, false)
    }
    pub fn request_map_region(
        &self,
        region_name: String,
        layer: GridLayerType,
    ) -> Result<(), Error> {
        let name = region_name.trim();
        if name.is_empty() || name.len() > 255 {
            return Err(Error::Argument);
        }
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::MapNameRequestPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.agent_data.flags = layer as u32;
        p.name_data.name = name.as_bytes().to_vec();
        self.send(&p, PacketType::MapNameRequest)
    }
    pub fn request_map_items(
        &self,
        region_handle: u64,
        item: GridItemType,
        layer: GridLayerType,
    ) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::MapItemRequestPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.agent_data.flags = layer as u32;
        p.request_data.region_handle = region_handle;
        p.request_data.item_type = item as u32;
        self.send(&p, PacketType::MapItemRequest)
    }
    pub fn request_region_handle(&self, region_id: UUID) -> Result<(), Error> {
        let mut p = crate::packets::RegionHandleRequestPacket::new_with_constructor()?;
        p.request_block.region_id = region_id;
        self.send(&p, PacketType::RegionHandleRequest)
    }
    pub async fn request_map_layer(
        &self,
        layer: GridLayerType,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<(), Error> {
        let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
        token.throw_if_cancellation_requested()?;
        if let Some(uri) = self.capability("MapLayer", &token).await? {
            let request = OSD::Map(HashMap::from([(
                "Flags".to_owned(),
                OSD::Integer(layer as i32),
            )]));
            let (response, bytes) = self
                .inner
                .client
                .native_http_caps_client()
                .post_with_uri_osd_format_osd_cancellation_token_i_progress(
                    uri,
                    OSDFormat::Xml,
                    request,
                    token.clone(),
                    None,
                )
                .await?;
            if !response.is_success_status_code() || bytes.len() > MAX_GRID_PACKET_BYTES {
                return Err(Error::InvalidOperation);
            }
            let OSD::Map(values) = OSDParser::deserialize_with_bytes(bytes)? else {
                return Err(Error::Argument);
            };
            let mut reply = crate::messages::linden::MapLayerReplyVariant::new()?;
            reply.deserialize(OSDMap::new_with_dictionary(values)?)?;
            for block in reply.layer_data_blocks.into_iter().take(65_535) {
                self.inner.events.layer.emit(GridLayerEventArgs {
                    layer: GridLayer {
                        bottom: block.bottom,
                        image_id: block.image_id,
                        left: block.left,
                        right: block.right,
                        top: block.top,
                    },
                });
            }
            self.inner.notify.notify_waiters();
            return Ok(());
        }
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::MapLayerRequestPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.agent_data.flags = layer as u32;
        self.send(&p, PacketType::MapLayerRequest)?;
        token.throw_if_cancellation_requested()
    }
    pub async fn get_grid_region_with_string_grid_layer_type_cancellation_token(
        &self,
        name: String,
        layer: GridLayerType,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<Option<Option<GridRegion>>, Error> {
        let key = name.trim().to_ascii_lowercase();
        if key.is_empty() {
            return Err(Error::Argument);
        }
        if let Some(region) = self.inner.region_by_name(&key) {
            return Ok(Some(Some(region)));
        }
        let notified = self.inner.notify.notified();
        self.request_map_region(name, layer)?;
        if let Some(region) = self.inner.region_by_name(&key) {
            return Ok(Some(Some(region)));
        }
        self.wait_for(cancellation_token, notified).await?;
        Ok(Some(self.inner.region_by_name(&key)))
    }
    pub async fn get_grid_region_with_u_int64_grid_layer_type_cancellation_token(
        &self,
        handle: u64,
        layer: GridLayerType,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<Option<Option<GridRegion>>, Error> {
        if let Some(region) = self.inner.region_by_handle(handle) {
            return Ok(Some(Some(region)));
        }
        let x = u16::try_from((handle >> 32) / 256).map_err(|_| Error::Argument)?;
        let y = u16::try_from((handle & u64::from(u32::MAX)) / 256).map_err(|_| Error::Argument)?;
        let notified = self.inner.notify.notified();
        self.request_map_blocks(layer, x, y, x, y, true)?;
        if let Some(region) = self.inner.region_by_handle(handle) {
            return Ok(Some(Some(region)));
        }
        self.wait_for(cancellation_token, notified).await?;
        Ok(Some(self.inner.region_by_handle(handle)))
    }
    pub async fn map_items(
        &self,
        region_handle: u64,
        item: GridItemType,
        layer: GridLayerType,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<Vec<MapItem>, Error> {
        let notified = self.inner.notify.notified();
        self.request_map_items(region_handle, item, layer)?;
        self.wait_for(cancellation_token, notified).await?;
        Ok(read(&self.inner.items)
            .get(&item)
            .cloned()
            .unwrap_or_default()
            .into_iter()
            .filter(|value| value.region_handle() == region_handle)
            .collect())
    }
    async fn wait_for(
        &self,
        cancellation_token: Option<CancellationToken>,
        notified: impl std::future::Future<Output = ()>,
    ) -> Result<(), Error> {
        let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
        token.throw_if_cancellation_requested()?;
        let millis = self
            .inner
            .client
            .settings_ref()
            .timing
            .map_request_timeout
            .max(1) as u64;
        tokio::select! { () = notified => Ok(()), () = token.cancelled() => Err(Error::Cancelled), () = tokio::time::sleep(Duration::from_millis(millis)) => Ok(()) }
    }
    pub fn regions_read_only(&self) -> HashMap<String, GridRegion> {
        self.inner.prune();
        read(&self.inner.regions)
            .iter()
            .map(|(k, v)| (k.clone(), v.region.clone()))
            .collect()
    }
    pub fn regions_by_handle_read_only(&self) -> HashMap<u64, GridRegion> {
        self.inner.prune();
        read(&self.inner.handles)
            .iter()
            .map(|(k, v)| (*k, v.region.clone()))
            .collect()
    }
    pub fn regions_by_uuid_read_only(&self) -> HashMap<UUID, u64> {
        read(&self.inner.uuids).clone()
    }
    pub fn sun_ang_velocity(&self) -> Vector3 {
        *read(&self.inner.sun_ang_velocity)
    }
    pub fn set_sun_ang_velocity(&mut self, value: Vector3) {
        *write(&self.inner.sun_ang_velocity) = value;
    }
    pub fn sun_direction(&self) -> Vector3 {
        *read(&self.inner.sun_direction)
    }
    pub fn set_sun_direction(&mut self, value: Vector3) {
        *write(&self.inner.sun_direction) = value;
    }
    pub fn sun_phase(&self) -> f32 {
        *read(&self.inner.sun_phase)
    }
    pub fn set_sun_phase(&mut self, value: f32) {
        *write(&self.inner.sun_phase) = value;
    }
    pub fn time_of_day(&self) -> u64 {
        *read(&self.inner.time_of_day)
    }
    pub fn set_time_of_day(&mut self, value: u64) {
        *write(&self.inner.time_of_day) = value;
    }
}

impl GridManagerInner {
    fn prune(&self) {
        let now = self.client.time_provider.get_utc_now();
        write(&self.regions)
            .retain(|_, value| now.duration_since(value.seen).unwrap_or_default() <= CACHE_TTL);
        write(&self.handles)
            .retain(|_, value| now.duration_since(value.seen).unwrap_or_default() <= CACHE_TTL);
    }
    fn region_by_name(&self, name: &str) -> Option<GridRegion> {
        self.prune();
        read(&self.regions)
            .get(name)
            .map(|value| value.region.clone())
    }
    fn region_by_handle(&self, handle: u64) -> Option<GridRegion> {
        self.prune();
        read(&self.handles)
            .get(&handle)
            .map(|value| value.region.clone())
    }
    fn insert_region(&self, region: GridRegion) {
        let cached = CachedRegion {
            region: region.clone(),
            seen: self.client.time_provider.get_utc_now(),
        };
        write(&self.regions).insert(region.name.to_ascii_lowercase(), cached.clone());
        write(&self.handles).insert(region.region_handle, cached);
        self.events.region.emit(GridRegionEventArgs { region });
        self.notify.notify_waiters();
    }
    fn handle_packet(&self, event: RawPacketReceivedEventArgs) {
        if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_GRID_PACKET_BYTES {
            return;
        }
        let mut pos = 0;
        match event.packet_type {
            PacketType::MapBlockReply => {
                if let Ok(packet) =
                    crate::packets::MapBlockReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    for block in packet.data.into_iter().take(65_535) {
                        let global_x = u32::from(block.x) * 256;
                        let global_y = u32::from(block.y) * 256;
                        self.insert_region(GridRegion {
                            access: SimAccess(block.access),
                            agents: block.agents,
                            map_image_id: block.map_image_id,
                            name: wire_string(&block.name),
                            region_flags: RegionFlags(u64::from(block.region_flags)),
                            region_handle: (u64::from(global_x) << 32) | u64::from(global_y),
                            water_height: block.water_height,
                            x: i32::from(block.x),
                            y: i32::from(block.y),
                        });
                    }
                }
            }
            PacketType::MapLayerReply => {
                if let Ok(packet) =
                    crate::packets::MapLayerReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    for block in packet.layer_data.into_iter().take(65_535) {
                        self.events.layer.emit(GridLayerEventArgs {
                            layer: GridLayer {
                                bottom: block.bottom as i32,
                                image_id: block.image_id,
                                left: block.left as i32,
                                right: block.right as i32,
                                top: block.top as i32,
                            },
                        });
                    }
                    self.notify.notify_waiters();
                }
            }
            PacketType::MapItemReply => {
                if let Ok(packet) =
                    crate::packets::MapItemReplyPacket::new_from_bytes(&event.data, &mut pos)
                    && let Some(kind) = grid_item_type(packet.request_data.item_type)
                {
                    let values: Vec<_> = packet
                        .data
                        .into_iter()
                        .take(65_535)
                        .map(|b| map_item_from_block(kind, b))
                        .collect();
                    write(&self.items).insert(kind, values.clone());
                    self.events.items.emit(GridItemsEventArgs {
                        type_: kind,
                        items: values,
                    });
                    self.notify.notify_waiters();
                }
            }
            PacketType::RegionIDAndHandleReply => {
                if let Ok(packet) = crate::packets::RegionIDAndHandleReplyPacket::new_from_bytes(
                    &event.data,
                    &mut pos,
                ) {
                    let value = packet.reply_block;
                    write(&self.uuids).insert(value.region_id, value.region_handle);
                    self.events.handle.emit(RegionHandleReplyEventArgs {
                        region_id: value.region_id,
                        region_handle: value.region_handle,
                    });
                    self.notify.notify_waiters();
                }
            }
            PacketType::CoarseLocationUpdate => {
                if let Ok(packet) = crate::packets::CoarseLocationUpdatePacket::new_from_bytes(
                    &event.data,
                    &mut pos,
                ) {
                    let positions: HashMap<_, _> = packet
                        .agent_data
                        .into_iter()
                        .zip(packet.location)
                        .take(65_535)
                        .map(|(agent, location)| {
                            (
                                agent.agent_id,
                                Vector3 {
                                    x: f32::from(location.x),
                                    y: f32::from(location.y),
                                    z: f32::from(location.z) * 4.0,
                                },
                            )
                        })
                        .collect();
                    let previous = write(&self.coarse_positions)
                        .insert(event.simulator.handle, positions.clone())
                        .unwrap_or_default();
                    let new_entries = positions
                        .keys()
                        .filter(|id| !previous.contains_key(id))
                        .copied()
                        .collect();
                    let removed_entries = previous
                        .keys()
                        .filter(|id| !positions.contains_key(id))
                        .copied()
                        .collect();
                    self.events.coarse.emit(CoarseLocationUpdateEventArgs {
                        simulator: event.simulator,
                        positions,
                        new_entries,
                        removed_entries,
                    });
                }
            }
            _ => {}
        }
    }
}
fn grid_item_type(value: u32) -> Option<GridItemType> {
    Some(match value {
        1 => GridItemType::Telehub,
        2 => GridItemType::PgEvent,
        3 => GridItemType::MatureEvent,
        4 => GridItemType::Popular,
        6 => GridItemType::AgentLocations,
        7 => GridItemType::LandForSale,
        8 => GridItemType::Classified,
        9 => GridItemType::AdultEvent,
        10 => GridItemType::AdultLandForSale,
        _ => return None,
    })
}

fn event_category(value: i32) -> crate::DirectoryManagerEventCategories {
    use crate::DirectoryManagerEventCategories as Category;
    match value {
        18 => Category::Discussion,
        19 => Category::Sports,
        20 => Category::LiveMusic,
        22 => Category::Commercial,
        23 => Category::Nightlife,
        24 => Category::Games,
        25 => Category::Pageants,
        26 => Category::Education,
        27 => Category::Arts,
        28 => Category::Charity,
        29 => Category::Miscellaneous,
        _ => Category::All,
    }
}

fn event_flags(value: i32, kind: GridItemType) -> crate::DirectoryManagerEventFlags {
    use crate::DirectoryManagerEventFlags as Flags;
    match value {
        0 => Flags::PG,
        1 => Flags::Mature,
        2 => Flags::Adult,
        _ => match kind {
            GridItemType::MatureEvent => Flags::Mature,
            GridItemType::AdultEvent => Flags::Adult,
            _ => Flags::PG,
        },
    }
}

fn map_item_from_block(
    kind: GridItemType,
    block: crate::packets::MapItemReplyPacketDataBlock,
) -> MapItem {
    let name = wire_string(&block.name);
    let data = match kind {
        GridItemType::Telehub => MapItemData::Telehub(MapTelehub),
        GridItemType::AgentLocations => MapItemData::AgentLocation(MapAgentLocation {
            avatar_count: block.extra,
            identifier: name.clone(),
        }),
        GridItemType::LandForSale => MapItemData::LandForSale(MapLandForSale {
            id: block.id,
            name: name.clone(),
            price: block.extra2,
            size: block.extra,
        }),
        GridItemType::AdultLandForSale => MapItemData::AdultLandForSale(MapAdultLandForSale {
            id: block.id,
            name: name.clone(),
            price: block.extra2,
            size: block.extra,
        }),
        GridItemType::PgEvent => MapItemData::PgEvent(MapPGEvent {
            category: event_category(block.extra),
            description: name.clone(),
            flags: event_flags(block.extra2, kind),
        }),
        GridItemType::MatureEvent => MapItemData::MatureEvent(MapMatureEvent {
            category: event_category(block.extra),
            description: name.clone(),
            flags: event_flags(block.extra2, kind),
        }),
        GridItemType::AdultEvent => MapItemData::AdultEvent(MapAdultEvent {
            category: event_category(block.extra),
            description: name.clone(),
            flags: event_flags(block.extra2, kind),
        }),
        GridItemType::Classified | GridItemType::Popular => MapItemData::Raw {
            id: block.id,
            name: name.clone(),
            extra: block.extra,
            extra2: block.extra2,
        },
    };
    MapItem {
        global_x: block.x,
        global_y: block.y,
        id: block.id,
        name,
        extra: block.extra,
        extra2: block.extra2,
        data,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use libremetaverse_types::compat::TimeProvider;
    use std::sync::atomic::{AtomicU64, Ordering};
    #[test]
    fn map_item_handle_math() {
        let item = MapItem {
            global_x: 1025,
            global_y: 2050,
            id: UUID::zero(),
            name: String::new(),
            extra: 0,
            extra2: 0,
            data: MapItemData::Telehub(MapTelehub),
        };
        assert_eq!(item.local_x(), 1);
        assert_eq!(item.local_y(), 2);
        assert_eq!(item.region_handle(), (1024_u64 << 32) | 2048);
    }
    #[test]
    fn layers_include_edges() {
        let layer = GridLayer {
            bottom: 2,
            image_id: UUID::zero(),
            left: 1,
            right: 3,
            top: 4,
        };
        assert!(layer.contains_region(1, 4).unwrap());
        assert!(!layer.contains_region(4, 4).unwrap());
    }
    #[test]
    fn map_item_fixture_retains_and_interprets_wire_fields() {
        let id = UUID::random().unwrap();
        let mut block =
            crate::packets::MapItemReplyPacketDataBlock::new_with_constructor().unwrap();
        block.x = 4097;
        block.y = 8194;
        block.id = id;
        block.name = b"Parcel One\0".to_vec();
        block.extra = 1024;
        block.extra2 = 55_000;
        let item = map_item_from_block(GridItemType::LandForSale, block);
        assert_eq!((item.global_x, item.global_y), (4097, 8194));
        assert_eq!((item.id, item.name.as_str()), (id, "Parcel One"));
        assert_eq!((item.extra, item.extra2), (1024, 55_000));
        assert_eq!(
            item.data,
            MapItemData::LandForSale(MapLandForSale {
                id,
                name: "Parcel One".to_owned(),
                price: 55_000,
                size: 1024,
            })
        );
    }
    #[test]
    fn region_cache_expiry_uses_injected_clock() {
        let seconds = Arc::new(AtomicU64::new(1_000));
        let clock = Arc::clone(&seconds);
        let client = GridClient::builder()
            .with_time_provider(TimeProvider::from_fn(move || {
                std::time::UNIX_EPOCH + Duration::from_secs(clock.load(Ordering::Relaxed))
            }))
            .build()
            .unwrap();
        let manager = GridManager::native_new(Arc::new(client)).unwrap();
        manager.inner.insert_region(GridRegion {
            access: SimAccess::PG,
            agents: 0,
            map_image_id: UUID::zero(),
            name: "Example".into(),
            region_flags: RegionFlags::NONE,
            region_handle: (256_u64 << 32) | 512,
            water_height: 20,
            x: 1,
            y: 2,
        });
        assert!(manager.inner.region_by_name("example").is_some());
        seconds.store(1_000 + CACHE_TTL.as_secs() + 1, Ordering::Relaxed);
        assert!(manager.inner.region_by_name("example").is_none());
    }
    #[test]
    fn simulator_features_preserve_unknown_fields() {
        let features = SimulatorFeatures::default();
        let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([
            ("MeshRezEnabled".into(), OSD::Boolean(true)),
            ("FutureFeature".into(), OSD::String("kept".into())),
        ])))
        .unwrap();
        features.set_features(None, Some(body), None).unwrap();
        assert!(features.has("FutureFeature".into()).unwrap());
        assert_eq!(
            features.get("FutureFeature".into()).unwrap(),
            Some(OSD::String("kept".into()))
        );
    }
}
