//! Directory search requests, paged reply decoding, and correlated result events.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_truncation)] // Wire coordinates intentionally follow CLR casts.
#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString methods.
#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructors are fallible.
#![allow(clippy::unused_self)] // Helper shares the manager-owned stream construction contract.
#![allow(clippy::too_many_lines)] // Packet dispatch is centralized and bounded by reply type.

use crate::agent_manager::EventRegistry;
use crate::network_manager::RawPacketReceivedEventArgs;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::{Error, GridClient};
use libremetaverse_types::compat::{
    CancellationToken, EventHandler, IAsyncEnumerable, Subscription,
};
use libremetaverse_types::{UUID, Vector3d};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::Notify;

const MAX_DIRECTORY_PACKET_BYTES: usize = 8 * 1024 * 1024;
const MAX_QUERY_BYTES: usize = 255;
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(|b| *b == 0).next().unwrap_or_default()).into_owned()
}
fn valid_query(value: &str) -> Result<(), Error> {
    if value.len() > MAX_QUERY_BYTES {
        Err(Error::Argument)
    } else {
        Ok(())
    }
}
fn finish_stream<T: Clone>(
    streams: &Mutex<HashMap<UUID, IAsyncEnumerable<T>>>,
    query_id: UUID,
    values: &[T],
) {
    if let Some(stream) = mutex(streams).remove(&query_id) {
        for value in values {
            stream.push(value.clone());
        }
        stream.close();
    }
}
fn publish_stream_page<T: Clone>(
    streams: &Mutex<HashMap<UUID, IAsyncEnumerable<T>>>,
    query_id: UUID,
    values: &[T],
    terminal: bool,
) {
    let stream = mutex(streams).get(&query_id).cloned();
    if let Some(stream) = stream {
        for value in values {
            stream.push(value.clone());
        }
        if terminal {
            mutex(streams).remove(&query_id);
            stream.close();
        }
    }
}
fn close_streams<T>(streams: &Mutex<HashMap<UUID, IAsyncEnumerable<T>>>) {
    for stream in mutex(streams).drain().map(|(_, stream)| stream) {
        stream.close();
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerAgentSearchData {
    pub agent_id: UUID,
    pub first_name: String,
    pub last_name: String,
    pub online: bool,
}
impl DirectoryManagerAgentSearchData {
    pub fn to_string(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
            .trim()
            .to_owned()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerClassified {
    pub creation_date: SystemTime,
    pub expiration_date: SystemTime,
    pub flags: crate::DirectoryManagerClassifiedFlags,
    pub id: UUID,
    pub name: String,
    pub price: i32,
}
impl DirectoryManagerClassified {
    pub fn to_string(&self) -> String {
        self.name.clone()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerDirectoryParcel {
    pub actual_area: i32,
    pub auction: bool,
    pub dwell: f32,
    pub for_sale: bool,
    pub id: UUID,
    pub name: String,
    pub sale_price: i32,
}
impl DirectoryManagerDirectoryParcel {
    pub fn to_string(&self) -> String {
        self.name.clone()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerEventsSearchData {
    pub date: String,
    pub flags: crate::DirectoryManagerEventFlags,
    pub id: u32,
    pub name: String,
    pub owner: UUID,
    pub time: u32,
}
impl DirectoryManagerEventsSearchData {
    pub fn to_string(&self) -> String {
        self.name.clone()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerGroupSearchData {
    pub group_id: UUID,
    pub group_name: String,
    pub members: i32,
}
impl DirectoryManagerGroupSearchData {
    pub fn to_string(&self) -> String {
        self.group_name.clone()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerPlacesSearchData {
    pub actual_area: i32,
    pub billable_area: i32,
    pub desc: String,
    pub dwell: f32,
    pub flags: crate::DirectoryManagerPlacesFlags,
    pub global_x: f32,
    pub global_y: f32,
    pub global_z: f32,
    pub name: String,
    pub owner_id: UUID,
    pub price: i32,
    pub sku: String,
    pub sim_name: String,
    pub snapshot_id: UUID,
}
impl DirectoryManagerPlacesSearchData {
    pub fn to_s_lurl(&self) -> Result<String, Error> {
        crate::SlurlParser::get_teleport_url(
            self.sim_name.clone(),
            Some(self.global_x as i32 & 0xff),
            Some(self.global_y as i32 & 0xff),
            Some(self.global_z as i32),
        )
    }
    pub fn to_string(&self) -> String {
        self.name.clone()
    }
}
#[derive(Clone, Debug, PartialEq)]
pub struct DirectoryManagerEventInfo {
    pub amount: u32,
    pub category: crate::DirectoryManagerEventCategories,
    pub cover: u32,
    pub creator: UUID,
    pub date: String,
    pub date_utc: u32,
    pub desc: String,
    pub duration: u32,
    pub flags: crate::DirectoryManagerEventFlags,
    pub global_pos: Vector3d,
    pub id: u32,
    pub name: String,
    pub sim_name: String,
}
impl DirectoryManagerEventInfo {
    pub fn to_s_lurl(&self) -> Result<String, Error> {
        crate::SlurlParser::get_teleport_url(
            self.sim_name.clone(),
            Some(self.global_pos.x as i32 & 0xff),
            Some(self.global_pos.y as i32 & 0xff),
            Some(self.global_pos.z as i32),
        )
    }
    pub fn to_string(&self) -> String {
        self.name.clone()
    }
}

macro_rules! correlated_args {
    ($name:ident, $item:ty, $field:ident, $get:ident) => {
        #[derive(Clone)]
        pub struct $name {
            query_id: UUID,
            $field: Vec<$item>,
        }
        impl $name {
            pub fn new(query_id: UUID, $field: Vec<$item>) -> Result<Self, Error> {
                Ok(Self { query_id, $field })
            }
            pub fn query_id(&self) -> UUID {
                self.query_id
            }
            pub fn $get(&self) -> Vec<$item> {
                self.$field.clone()
            }
        }
    };
}
correlated_args!(
    DirEventsReplyEventArgs,
    DirectoryManagerEventsSearchData,
    matched_events,
    matched_events
);
correlated_args!(
    DirGroupsReplyEventArgs,
    DirectoryManagerGroupSearchData,
    matched_groups,
    matched_groups
);
correlated_args!(
    DirPeopleReplyEventArgs,
    DirectoryManagerAgentSearchData,
    matched_people,
    matched_people
);
correlated_args!(
    DirPlacesReplyEventArgs,
    DirectoryManagerDirectoryParcel,
    matched_parcels,
    matched_parcels
);
correlated_args!(
    PlacesReplyEventArgs,
    DirectoryManagerPlacesSearchData,
    matched_places,
    matched_places
);

#[derive(Clone)]
pub struct DirClassifiedsReplyEventArgs {
    classifieds: Vec<DirectoryManagerClassified>,
}
impl DirClassifiedsReplyEventArgs {
    pub fn new(classifieds: Vec<DirectoryManagerClassified>) -> Result<Self, Error> {
        Ok(Self { classifieds })
    }
    pub fn classifieds(&self) -> Vec<DirectoryManagerClassified> {
        self.classifieds.clone()
    }
}
#[derive(Clone)]
pub struct DirLandReplyEventArgs {
    dir_parcels: Vec<DirectoryManagerDirectoryParcel>,
}
impl DirLandReplyEventArgs {
    pub fn new(dir_parcels: Vec<DirectoryManagerDirectoryParcel>) -> Result<Self, Error> {
        Ok(Self { dir_parcels })
    }
    pub fn dir_parcels(&self) -> Vec<DirectoryManagerDirectoryParcel> {
        self.dir_parcels.clone()
    }
}
#[derive(Clone)]
pub struct EventInfoReplyEventArgs {
    matched_event: DirectoryManagerEventInfo,
}
impl EventInfoReplyEventArgs {
    pub fn new(matched_event: DirectoryManagerEventInfo) -> Result<Self, Error> {
        Ok(Self { matched_event })
    }
    pub fn matched_event(&self) -> DirectoryManagerEventInfo {
        self.matched_event.clone()
    }
}

#[derive(Default)]
struct DirectoryEvents {
    classifieds: EventRegistry<DirClassifiedsReplyEventArgs>,
    events: EventRegistry<DirEventsReplyEventArgs>,
    groups: EventRegistry<DirGroupsReplyEventArgs>,
    land: EventRegistry<DirLandReplyEventArgs>,
    people: EventRegistry<DirPeopleReplyEventArgs>,
    dir_places: EventRegistry<DirPlacesReplyEventArgs>,
    event_info: EventRegistry<EventInfoReplyEventArgs>,
    places: EventRegistry<PlacesReplyEventArgs>,
}
#[derive(Clone, Copy)]
enum SearchStreamKind {
    People,
    Groups,
    Classifieds,
    DirPlaces,
    Events,
    Land,
    Places,
}
pub(crate) struct DirectoryManagerInner {
    client: Arc<GridClient>,
    events: DirectoryEvents,
    raw_subscription: Mutex<Option<Subscription>>,
    disposed: AtomicBool,
    event_info: RwLock<Option<DirectoryManagerEventInfo>>,
    event_notify: Notify,
    people_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerAgentSearchData>>>,
    group_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerGroupSearchData>>>,
    classified_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerClassified>>>,
    dir_place_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerDirectoryParcel>>>,
    event_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerEventsSearchData>>>,
    land_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerDirectoryParcel>>>,
    place_streams: Mutex<HashMap<UUID, IAsyncEnumerable<DirectoryManagerPlacesSearchData>>>,
    find_pages: Mutex<HashMap<UUID, (String, crate::DirectoryManagerDirFindFlags, i32)>>,
}
#[derive(Clone)]
pub struct DirectoryManager {
    pub(crate) inner: Arc<DirectoryManagerInner>,
}

impl DirectoryManager {
    pub fn new(client: GridClient) -> Result<Self, Error> {
        Self::native_new(Arc::new(client))
    }
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        if let Some(inner) = client.cached_directory_manager_inner() {
            return Ok(Self { inner });
        }
        let inner = Arc::new(DirectoryManagerInner {
            client: Arc::clone(&client),
            events: DirectoryEvents::default(),
            raw_subscription: Mutex::new(None),
            disposed: AtomicBool::new(false),
            event_info: RwLock::new(None),
            event_notify: Notify::new(),
            people_streams: Mutex::new(HashMap::new()),
            group_streams: Mutex::new(HashMap::new()),
            classified_streams: Mutex::new(HashMap::new()),
            dir_place_streams: Mutex::new(HashMap::new()),
            event_streams: Mutex::new(HashMap::new()),
            land_streams: Mutex::new(HashMap::new()),
            place_streams: Mutex::new(HashMap::new()),
            find_pages: Mutex::new(HashMap::new()),
        });
        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);
                }
            },
        )));
        Ok(Self {
            inner: client.install_directory_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 result_stream<T: Send + 'static>(
        &self,
        cancellation: CancellationToken,
    ) -> IAsyncEnumerable<T> {
        IAsyncEnumerable::with_cancellation(cancellation)
    }
    fn arm_stream_timeout<T: Send + 'static>(
        &self,
        query_id: UUID,
        kind: SearchStreamKind,
        stream: IAsyncEnumerable<T>,
        cancellation: CancellationToken,
    ) {
        if let Ok(runtime) = tokio::runtime::Handle::try_current() {
            let timeout = Duration::from_millis(
                self.inner
                    .client
                    .settings_ref()
                    .timing
                    .map_request_timeout
                    .max(1) as u64,
            );
            let weak = Arc::downgrade(&self.inner);
            runtime.spawn(async move {
                tokio::select! {
                    () = cancellation.cancelled() => {},
                    () = tokio::time::sleep(timeout) => {},
                }
                stream.close();
                if let Some(inner) = weak.upgrade() {
                    match kind {
                        SearchStreamKind::People => {
                            mutex(&inner.people_streams).remove(&query_id);
                        }
                        SearchStreamKind::Groups => {
                            mutex(&inner.group_streams).remove(&query_id);
                        }
                        SearchStreamKind::Classifieds => {
                            mutex(&inner.classified_streams).remove(&query_id);
                        }
                        SearchStreamKind::DirPlaces => {
                            mutex(&inner.dir_place_streams).remove(&query_id);
                        }
                        SearchStreamKind::Events => {
                            mutex(&inner.event_streams).remove(&query_id);
                        }
                        SearchStreamKind::Land => {
                            mutex(&inner.land_streams).remove(&query_id);
                        }
                        SearchStreamKind::Places => {
                            mutex(&inner.place_streams).remove(&query_id);
                        }
                    }
                    mutex(&inner.find_pages).remove(&query_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,
        }
    }
    pub fn dispose(&self) -> Result<(), Error> {
        self.inner.disposed.store(true, Ordering::Release);
        mutex(&self.inner.raw_subscription).take();
        close_streams(&self.inner.people_streams);
        close_streams(&self.inner.group_streams);
        close_streams(&self.inner.classified_streams);
        close_streams(&self.inner.dir_place_streams);
        close_streams(&self.inner.event_streams);
        close_streams(&self.inner.land_streams);
        close_streams(&self.inner.place_streams);
        mutex(&self.inner.find_pages).clear();
        Ok(())
    }
    pub fn subscribe_dir_classifieds_reply(
        &self,
        h: EventHandler<DirClassifiedsReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.classifieds.subscribe(h)
    }
    pub fn subscribe_dir_events_reply(
        &self,
        h: EventHandler<DirEventsReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.events.subscribe(h)
    }
    pub fn subscribe_dir_groups_reply(
        &self,
        h: EventHandler<DirGroupsReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.groups.subscribe(h)
    }
    pub fn subscribe_dir_land_reply(&self, h: EventHandler<DirLandReplyEventArgs>) -> Subscription {
        self.inner.events.land.subscribe(h)
    }
    pub fn subscribe_dir_people_reply(
        &self,
        h: EventHandler<DirPeopleReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.people.subscribe(h)
    }
    pub fn subscribe_dir_places_reply(
        &self,
        h: EventHandler<DirPlacesReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.dir_places.subscribe(h)
    }
    pub fn subscribe_event_info_reply(
        &self,
        h: EventHandler<EventInfoReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.event_info.subscribe(h)
    }
    pub fn subscribe_places_reply(&self, h: EventHandler<PlacesReplyEventArgs>) -> Subscription {
        self.inner.events.places.subscribe(h)
    }

    fn start_find(
        &self,
        text: String,
        start: i32,
        flags: crate::DirectoryManagerDirFindFlags,
    ) -> Result<UUID, Error> {
        valid_query(&text)?;
        if start < 0 {
            return Err(Error::Argument);
        }
        let query_id = UUID::random()?;
        self.send_find(query_id, text, start, flags)?;
        Ok(query_id)
    }
    fn send_find(
        &self,
        query_id: UUID,
        text: String,
        start: i32,
        flags: crate::DirectoryManagerDirFindFlags,
    ) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::DirFindQueryPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.query_data.query_id = query_id;
        p.query_data.query_text = text.into_bytes();
        p.query_data.query_flags = flags.0 as u32;
        p.query_data.query_start = start;
        self.send(&p, PacketType::DirFindQuery)
    }
    pub fn start_people_search(
        &self,
        search_text: String,
        query_start: i32,
    ) -> Result<UUID, Error> {
        self.start_find(
            search_text,
            query_start,
            crate::DirectoryManagerDirFindFlags::PEOPLE,
        )
    }
    pub fn start_group_search_with_string_int32(
        &self,
        search_text: String,
        query_start: i32,
    ) -> Result<UUID, Error> {
        self.start_group_search_with_string_int32_dir_find_flags(
            search_text,
            query_start,
            crate::DirectoryManagerDirFindFlags::GROUPS,
        )
    }
    pub fn start_group_search_with_string_int32_dir_find_flags(
        &self,
        search_text: String,
        query_start: i32,
        flags: crate::DirectoryManagerDirFindFlags,
    ) -> Result<UUID, Error> {
        self.start_find(
            search_text,
            query_start,
            crate::DirectoryManagerDirFindFlags(
                flags.0 | crate::DirectoryManagerDirFindFlags::GROUPS.0,
            ),
        )
    }
    pub fn start_events_search_with_string_u_int32(
        &self,
        search_text: String,
        query_start: u32,
    ) -> Result<UUID, Error> {
        self.start_events_search_with_string_dir_find_flags_string_u_int32_event_categories(
            search_text,
            crate::DirectoryManagerDirFindFlags::EVENTS,
            "u".into(),
            query_start,
            crate::DirectoryManagerEventCategories::All,
        )
    }
    pub fn start_events_search_with_string_dir_find_flags_string_u_int32_event_categories(
        &self,
        search_text: String,
        query_flags: crate::DirectoryManagerDirFindFlags,
        event_day: String,
        query_start: u32,
        category: crate::DirectoryManagerEventCategories,
    ) -> Result<UUID, Error> {
        let text = format!("{event_day}|{}|{search_text}", category as i32);
        self.start_find(
            text,
            i32::try_from(query_start).map_err(|_| Error::Argument)?,
            crate::DirectoryManagerDirFindFlags(
                query_flags.0 | crate::DirectoryManagerDirFindFlags::EVENTS.0,
            ),
        )
    }
    pub fn start_classified_search_with_string(&self, search_text: String) -> Result<UUID, Error> {
        self.start_classified_search_with_string_classified_categories_classified_query_flags(
            search_text,
            crate::DirectoryManagerClassifiedCategories::Any,
            crate::DirectoryManagerClassifiedQueryFlags::ALL,
        )
    }
    pub fn start_classified_search_with_string_classified_categories_classified_query_flags(
        &self,
        search_text: String,
        category: crate::DirectoryManagerClassifiedCategories,
        query_flags: crate::DirectoryManagerClassifiedQueryFlags,
    ) -> Result<UUID, Error> {
        valid_query(&search_text)?;
        let id = UUID::random()?;
        self.send_classified(id, search_text, category, query_flags)?;
        Ok(id)
    }
    fn send_classified(
        &self,
        id: UUID,
        search_text: String,
        category: crate::DirectoryManagerClassifiedCategories,
        query_flags: crate::DirectoryManagerClassifiedQueryFlags,
    ) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::DirClassifiedQueryPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.query_data.query_id = id;
        p.query_data.query_text = search_text.into_bytes();
        p.query_data.category = category as u32;
        p.query_data.query_flags = query_flags.0 as u32;
        p.query_data.query_start = 0;
        self.send(&p, PacketType::DirClassifiedQuery)
    }
    pub fn start_dir_places_search_with_string_int32(
        &self,
        search_text: String,
        query_start: i32,
    ) -> Result<UUID, Error> {
        self.start_dir_places_search_with_string_dir_find_flags_parcel_category_int32(
            search_text,
            crate::DirectoryManagerDirFindFlags::INCLUDE_PG,
            crate::ParcelCategory::Any,
            query_start,
        )
    }
    pub fn start_dir_places_search_with_string_dir_find_flags_parcel_category_int32(
        &self,
        search_text: String,
        query_flags: crate::DirectoryManagerDirFindFlags,
        category: crate::ParcelCategory,
        query_start: i32,
    ) -> Result<UUID, Error> {
        valid_query(&search_text)?;
        if query_start < 0 {
            return Err(Error::Argument);
        }
        let id = UUID::random()?;
        self.send_dir_places(id, search_text, query_flags, category, query_start)?;
        Ok(id)
    }
    fn send_dir_places(
        &self,
        id: UUID,
        search_text: String,
        query_flags: crate::DirectoryManagerDirFindFlags,
        category: crate::ParcelCategory,
        query_start: i32,
    ) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::DirPlacesQueryPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.query_data.query_id = id;
        p.query_data.query_text = search_text.into_bytes();
        p.query_data.query_flags = query_flags.0 as u32;
        p.query_data.category = category as i8;
        p.query_data.query_start = query_start;
        self.send(&p, PacketType::DirPlacesQuery)
    }
    pub fn start_land_search_with_search_type_flags(
        &self,
        type_flags: crate::DirectoryManagerSearchTypeFlags,
    ) -> Result<(), Error> {
        self.start_land_search_with_search_type_flags_int32_int32_int32(type_flags, 0, 0, 0)
    }
    pub fn start_land_search_with_search_type_flags_int32_int32_int32(
        &self,
        type_flags: crate::DirectoryManagerSearchTypeFlags,
        price_limit: i32,
        area_limit: i32,
        query_start: i32,
    ) -> Result<(), Error> {
        self.start_land_search_with_dir_find_flags_search_type_flags_int32_int32_int32(
            crate::DirectoryManagerDirFindFlags::INCLUDE_PG,
            type_flags,
            price_limit,
            area_limit,
            query_start,
        )
    }
    pub fn start_land_search_with_dir_find_flags_search_type_flags_int32_int32_int32(
        &self,
        find_flags: crate::DirectoryManagerDirFindFlags,
        type_flags: crate::DirectoryManagerSearchTypeFlags,
        price_limit: i32,
        area_limit: i32,
        query_start: i32,
    ) -> Result<(), Error> {
        self.start_land_search(find_flags, type_flags, price_limit, area_limit, query_start)?;
        Ok(())
    }
    fn start_land_search(
        &self,
        find_flags: crate::DirectoryManagerDirFindFlags,
        type_flags: crate::DirectoryManagerSearchTypeFlags,
        price_limit: i32,
        area_limit: i32,
        query_start: i32,
    ) -> Result<UUID, Error> {
        if price_limit < 0 || area_limit < 0 || query_start < 0 {
            return Err(Error::Argument);
        }
        let query_id = UUID::random()?;
        self.send_land(
            query_id,
            find_flags,
            type_flags,
            price_limit,
            area_limit,
            query_start,
        )?;
        Ok(query_id)
    }
    fn send_land(
        &self,
        query_id: UUID,
        find_flags: crate::DirectoryManagerDirFindFlags,
        type_flags: crate::DirectoryManagerSearchTypeFlags,
        price_limit: i32,
        area_limit: i32,
        query_start: i32,
    ) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::DirLandQueryPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.query_data.query_id = query_id;
        p.query_data.query_flags = find_flags.0 as u32;
        p.query_data.search_type = type_flags.0 as u32;
        p.query_data.price = price_limit;
        p.query_data.area = area_limit;
        p.query_data.query_start = query_start;
        self.send(&p, PacketType::DirLandQuery)
    }
    pub fn start_places_search_with_method(&self) -> Result<UUID, Error> {
        self.start_places_search_with_string(String::new())
    }
    pub fn start_places_search_with_uuid(&self, group_id: UUID) -> Result<UUID, Error> {
        self.start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid(
            crate::DirectoryManagerDirFindFlags::INCLUDE_PG,
            crate::ParcelCategory::Any,
            String::new(),
            String::new(),
            group_id,
            UUID::random()?,
        )
    }
    pub fn start_places_search_with_string(&self, search_text: String) -> Result<UUID, Error> {
        self.start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid(
            crate::DirectoryManagerDirFindFlags::INCLUDE_PG,
            crate::ParcelCategory::Any,
            search_text,
            String::new(),
            UUID::zero(),
            UUID::random()?,
        )
    }
    pub fn start_places_search_with_dir_find_flags_parcel_category_string_string_uuid_uuid(
        &self,
        find_flags: crate::DirectoryManagerDirFindFlags,
        search_category: crate::ParcelCategory,
        search_text: String,
        simulator_name: String,
        group_id: UUID,
        transaction_id: UUID,
    ) -> Result<UUID, Error> {
        valid_query(&search_text)?;
        valid_query(&simulator_name)?;
        self.send_places(
            find_flags,
            search_category,
            search_text,
            simulator_name,
            group_id,
            transaction_id,
        )?;
        Ok(transaction_id)
    }
    fn send_places(
        &self,
        find_flags: crate::DirectoryManagerDirFindFlags,
        search_category: crate::ParcelCategory,
        search_text: String,
        simulator_name: String,
        group_id: UUID,
        transaction_id: UUID,
    ) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::PlacesQueryPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.agent_data.query_id = group_id;
        p.query_data.query_flags = find_flags.0 as u32;
        p.query_data.category = search_category as i8;
        p.query_data.query_text = search_text.into_bytes();
        p.query_data.sim_name = simulator_name.into_bytes();
        p.transaction_data.transaction_id = transaction_id;
        self.send(&p, PacketType::PlacesQuery)
    }
    pub fn event_info_request(&self, event_id: u32) -> Result<(), Error> {
        let (agent, session) = self.agent_ids();
        let mut p = crate::packets::EventInfoRequestPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.event_data.event_id = event_id;
        self.send(&p, PacketType::EventInfoRequest)
    }
    pub async fn get_event_info(
        &self,
        event_id: u32,
        ct: Option<CancellationToken>,
    ) -> Result<Option<Option<DirectoryManagerEventInfo>>, Error> {
        if let Some(value) = read(&self.inner.event_info)
            .clone()
            .filter(|v| v.id == event_id)
        {
            return Ok(Some(Some(value)));
        }
        let notified = self.inner.event_notify.notified();
        self.event_info_request(event_id)?;
        let token = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        token.throw_if_cancellation_requested()?;
        let ms = self
            .inner
            .client
            .settings_ref()
            .timing
            .map_request_timeout
            .max(1) as u64;
        tokio::select! {()=notified=>{},()=token.cancelled()=>return Err(Error::Cancelled),()=tokio::time::sleep(Duration::from_millis(ms))=>{}}
        Ok(Some(
            read(&self.inner.event_info)
                .clone()
                .filter(|v| v.id == event_id),
        ))
    }
    pub fn search_people(
        &self,
        name: String,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerAgentSearchData>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        valid_query(&name)?;
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.people_streams).insert(id, stream.clone());
        self.arm_stream_timeout(id, SearchStreamKind::People, stream.clone(), cancellation);
        mutex(&self.inner.find_pages).insert(
            id,
            (name.clone(), crate::DirectoryManagerDirFindFlags::PEOPLE, 0),
        );
        if let Err(error) = self.send_find(id, name, 0, crate::DirectoryManagerDirFindFlags::PEOPLE)
        {
            mutex(&self.inner.people_streams).remove(&id);
            mutex(&self.inner.find_pages).remove(&id);
            return Err(error);
        }
        Ok(stream)
    }
    pub fn search_groups(
        &self,
        query: String,
        flags: Option<crate::DirectoryManagerDirFindFlags>,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerGroupSearchData>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        valid_query(&query)?;
        let flags = flags.unwrap_or(crate::DirectoryManagerDirFindFlags::GROUPS);
        let flags = crate::DirectoryManagerDirFindFlags(
            flags.0 | crate::DirectoryManagerDirFindFlags::GROUPS.0,
        );
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.group_streams).insert(id, stream.clone());
        self.arm_stream_timeout(id, SearchStreamKind::Groups, stream.clone(), cancellation);
        mutex(&self.inner.find_pages).insert(id, (query.clone(), flags, 0));
        if let Err(error) = self.send_find(id, query, 0, flags) {
            mutex(&self.inner.group_streams).remove(&id);
            mutex(&self.inner.find_pages).remove(&id);
            return Err(error);
        }
        Ok(stream)
    }
    pub fn search_classifieds(
        &self,
        query: String,
        category: Option<crate::DirectoryManagerClassifiedCategories>,
        flags: Option<crate::DirectoryManagerClassifiedQueryFlags>,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerClassified>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        valid_query(&query)?;
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.classified_streams).insert(id, stream.clone());
        if let Err(error) = self.send_classified(
            id,
            query,
            category.unwrap_or(crate::DirectoryManagerClassifiedCategories::Any),
            flags.unwrap_or(crate::DirectoryManagerClassifiedQueryFlags::ALL),
        ) {
            mutex(&self.inner.classified_streams).remove(&id);
            stream.close();
            return Err(error);
        }
        self.arm_stream_timeout(
            id,
            SearchStreamKind::Classifieds,
            stream.clone(),
            cancellation,
        );
        Ok(stream)
    }
    pub fn search_dir_places(
        &self,
        query: String,
        flags: Option<crate::DirectoryManagerDirFindFlags>,
        category: Option<crate::ParcelCategory>,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerDirectoryParcel>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        valid_query(&query)?;
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.dir_place_streams).insert(id, stream.clone());
        if let Err(error) = self.send_dir_places(
            id,
            query,
            flags.unwrap_or(crate::DirectoryManagerDirFindFlags::INCLUDE_PG),
            category.unwrap_or(crate::ParcelCategory::Any),
            0,
        ) {
            mutex(&self.inner.dir_place_streams).remove(&id);
            stream.close();
            return Err(error);
        }
        self.arm_stream_timeout(
            id,
            SearchStreamKind::DirPlaces,
            stream.clone(),
            cancellation,
        );
        Ok(stream)
    }
    pub fn search_events(
        &self,
        query: String,
        flags: Option<crate::DirectoryManagerDirFindFlags>,
        event_day: Option<String>,
        category: Option<crate::DirectoryManagerEventCategories>,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerEventsSearchData>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        let flags = flags.unwrap_or(crate::DirectoryManagerDirFindFlags::EVENTS);
        let flags = crate::DirectoryManagerDirFindFlags(
            flags.0 | crate::DirectoryManagerDirFindFlags::EVENTS.0,
        );
        let text = format!(
            "{}|{}|{query}",
            event_day.unwrap_or_else(|| "u".into()),
            category.unwrap_or(crate::DirectoryManagerEventCategories::All) as i32
        );
        valid_query(&text)?;
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.event_streams).insert(id, stream.clone());
        self.arm_stream_timeout(id, SearchStreamKind::Events, stream.clone(), cancellation);
        mutex(&self.inner.find_pages).insert(id, (text.clone(), flags, 0));
        if let Err(error) = self.send_find(id, text, 0, flags) {
            mutex(&self.inner.event_streams).remove(&id);
            mutex(&self.inner.find_pages).remove(&id);
            return Err(error);
        }
        Ok(stream)
    }
    pub fn search_land(
        &self,
        type_flags: crate::DirectoryManagerSearchTypeFlags,
        find_flags: Option<crate::DirectoryManagerDirFindFlags>,
        price_limit: Option<i32>,
        area_limit: Option<i32>,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerDirectoryParcel>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        let price_limit = price_limit.unwrap_or(0);
        let area_limit = area_limit.unwrap_or(0);
        if price_limit < 0 || area_limit < 0 {
            return Err(Error::Argument);
        }
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.land_streams).insert(id, stream.clone());
        if let Err(error) = self.send_land(
            id,
            find_flags.unwrap_or(crate::DirectoryManagerDirFindFlags::INCLUDE_PG),
            type_flags,
            price_limit,
            area_limit,
            0,
        ) {
            mutex(&self.inner.land_streams).remove(&id);
            stream.close();
            return Err(error);
        }
        self.arm_stream_timeout(id, SearchStreamKind::Land, stream.clone(), cancellation);
        Ok(stream)
    }
    pub fn search_places(
        &self,
        find_flags: crate::DirectoryManagerDirFindFlags,
        category: crate::ParcelCategory,
        query: String,
        simulator_name: Option<String>,
        group_id: Option<UUID>,
        ct: Option<CancellationToken>,
    ) -> Result<IAsyncEnumerable<DirectoryManagerPlacesSearchData>, Error> {
        let cancellation = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        cancellation.throw_if_cancellation_requested()?;
        let simulator_name = simulator_name.unwrap_or_default();
        valid_query(&query)?;
        valid_query(&simulator_name)?;
        let id = UUID::random()?;
        let stream = self.result_stream(cancellation.clone());
        mutex(&self.inner.place_streams).insert(id, stream.clone());
        if let Err(error) = self.send_places(
            find_flags,
            category,
            query,
            simulator_name,
            group_id.unwrap_or_else(UUID::zero),
            id,
        ) {
            mutex(&self.inner.place_streams).remove(&id);
            stream.close();
            return Err(error);
        }
        self.arm_stream_timeout(id, SearchStreamKind::Places, stream.clone(), cancellation);
        Ok(stream)
    }
}

impl DirectoryManagerInner {
    fn next_find_page(
        &self,
        query_id: UUID,
        count: usize,
    ) -> Option<(String, crate::DirectoryManagerDirFindFlags, i32)> {
        if count < 100 {
            mutex(&self.find_pages).remove(&query_id);
            return None;
        }
        let mut pages = mutex(&self.find_pages);
        let (text, flags, start) = pages.get_mut(&query_id)?;
        *start = start.saturating_add(100);
        Some((text.clone(), *flags, *start))
    }
    fn handle_packet(self: &Arc<Self>, event: RawPacketReceivedEventArgs) {
        if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_DIRECTORY_PACKET_BYTES {
            return;
        }
        let mut pos = 0;
        match event.packet_type {
            PacketType::DirPeopleReply => {
                if let Ok(p) =
                    crate::packets::DirPeopleReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let values = p
                        .query_replies
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerAgentSearchData {
                            agent_id: v.agent_id,
                            first_name: wire_string(&v.first_name),
                            last_name: wire_string(&v.last_name),
                            online: v.online,
                        })
                        .collect::<Vec<_>>();
                    let next = self.next_find_page(p.query_data.query_id, values.len());
                    publish_stream_page(
                        &self.people_streams,
                        p.query_data.query_id,
                        &values,
                        next.is_none(),
                    );
                    self.events.people.emit(DirPeopleReplyEventArgs {
                        query_id: p.query_data.query_id,
                        matched_people: values,
                    });
                    if let Some((text, flags, start)) = next
                        && (DirectoryManager {
                            inner: Arc::clone(self),
                        })
                        .send_find(p.query_data.query_id, text, start, flags)
                        .is_err()
                    {
                        mutex(&self.find_pages).remove(&p.query_data.query_id);
                        publish_stream_page(&self.people_streams, p.query_data.query_id, &[], true);
                    }
                }
            }
            PacketType::DirGroupsReply => {
                if let Ok(p) =
                    crate::packets::DirGroupsReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let values = p
                        .query_replies
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerGroupSearchData {
                            group_id: v.group_id,
                            group_name: wire_string(&v.group_name),
                            members: v.members,
                        })
                        .collect::<Vec<_>>();
                    let next = self.next_find_page(p.query_data.query_id, values.len());
                    publish_stream_page(
                        &self.group_streams,
                        p.query_data.query_id,
                        &values,
                        next.is_none(),
                    );
                    self.events.groups.emit(DirGroupsReplyEventArgs {
                        query_id: p.query_data.query_id,
                        matched_groups: values,
                    });
                    if let Some((text, flags, start)) = next
                        && (DirectoryManager {
                            inner: Arc::clone(self),
                        })
                        .send_find(p.query_data.query_id, text, start, flags)
                        .is_err()
                    {
                        mutex(&self.find_pages).remove(&p.query_data.query_id);
                        publish_stream_page(&self.group_streams, p.query_data.query_id, &[], true);
                    }
                }
            }
            PacketType::DirLandReply => {
                if let Ok(p) =
                    crate::packets::DirLandReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let values = p
                        .query_replies
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerDirectoryParcel {
                            actual_area: v.actual_area,
                            auction: v.auction,
                            dwell: 0.0,
                            for_sale: v.for_sale,
                            id: v.parcel_id,
                            name: wire_string(&v.name),
                            sale_price: v.sale_price,
                        })
                        .collect::<Vec<_>>();
                    finish_stream(&self.land_streams, p.query_data.query_id, &values);
                    self.events.land.emit(DirLandReplyEventArgs {
                        dir_parcels: values,
                    });
                }
            }
            PacketType::DirPlacesReply => {
                if let Ok(p) =
                    crate::packets::DirPlacesReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let query = p.query_data.first().map_or_else(UUID::zero, |v| v.query_id);
                    let values = p
                        .query_replies
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerDirectoryParcel {
                            actual_area: 0,
                            auction: v.auction,
                            dwell: v.dwell,
                            for_sale: v.for_sale,
                            id: v.parcel_id,
                            name: wire_string(&v.name),
                            sale_price: 0,
                        })
                        .collect::<Vec<_>>();
                    finish_stream(&self.dir_place_streams, query, &values);
                    self.events.dir_places.emit(DirPlacesReplyEventArgs {
                        query_id: query,
                        matched_parcels: values,
                    });
                }
            }
            PacketType::DirClassifiedReply => {
                if let Ok(p) =
                    crate::packets::DirClassifiedReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let values = p
                        .query_replies
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerClassified {
                            creation_date: UNIX_EPOCH
                                + Duration::from_secs(u64::from(v.creation_date)),
                            expiration_date: UNIX_EPOCH
                                + Duration::from_secs(u64::from(v.expiration_date)),
                            flags: crate::DirectoryManagerClassifiedFlags(v.classified_flags),
                            id: v.classified_id,
                            name: wire_string(&v.name),
                            price: v.price_for_listing,
                        })
                        .collect::<Vec<_>>();
                    finish_stream(&self.classified_streams, p.query_data.query_id, &values);
                    self.events.classifieds.emit(DirClassifiedsReplyEventArgs {
                        classifieds: values,
                    });
                }
            }
            PacketType::DirEventsReply => {
                if let Ok(p) =
                    crate::packets::DirEventsReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let values = p
                        .query_replies
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerEventsSearchData {
                            date: wire_string(&v.date),
                            flags: event_flags(v.event_flags),
                            id: v.event_id,
                            name: wire_string(&v.name),
                            owner: v.owner_id,
                            time: v.unix_time,
                        })
                        .collect::<Vec<_>>();
                    let next = self.next_find_page(p.query_data.query_id, values.len());
                    publish_stream_page(
                        &self.event_streams,
                        p.query_data.query_id,
                        &values,
                        next.is_none(),
                    );
                    self.events.events.emit(DirEventsReplyEventArgs {
                        query_id: p.query_data.query_id,
                        matched_events: values,
                    });
                    if let Some((text, flags, start)) = next
                        && (DirectoryManager {
                            inner: Arc::clone(self),
                        })
                        .send_find(p.query_data.query_id, text, start, flags)
                        .is_err()
                    {
                        mutex(&self.find_pages).remove(&p.query_data.query_id);
                        publish_stream_page(&self.event_streams, p.query_data.query_id, &[], true);
                    }
                }
            }
            PacketType::PlacesReply => {
                if let Ok(p) =
                    crate::packets::PlacesReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let query_id = p.transaction_data.transaction_id;
                    let values = p
                        .query_data
                        .into_iter()
                        .take(1024)
                        .map(|v| DirectoryManagerPlacesSearchData {
                            actual_area: v.actual_area,
                            billable_area: v.billable_area,
                            desc: wire_string(&v.desc),
                            dwell: v.dwell,
                            flags: if v.flags & 128 != 0 {
                                crate::DirectoryManagerPlacesFlags::ForSale
                            } else {
                                crate::DirectoryManagerPlacesFlags::NotForSale
                            },
                            global_x: v.global_x,
                            global_y: v.global_y,
                            global_z: v.global_z,
                            name: wire_string(&v.name),
                            owner_id: v.owner_id,
                            price: v.price,
                            sku: String::new(),
                            sim_name: wire_string(&v.sim_name),
                            snapshot_id: v.snapshot_id,
                        })
                        .collect::<Vec<_>>();
                    finish_stream(&self.place_streams, query_id, &values);
                    self.events.places.emit(PlacesReplyEventArgs {
                        query_id,
                        matched_places: values,
                    });
                }
            }
            PacketType::EventInfoReply => {
                if let Ok(p) =
                    crate::packets::EventInfoReplyPacket::new_from_bytes(&event.data, &mut pos)
                {
                    let v = p.event_data;
                    let info = DirectoryManagerEventInfo {
                        amount: v.amount,
                        category: event_category(&wire_string(&v.category)),
                        cover: v.cover,
                        creator: UUID::parse(wire_string(&v.creator))
                            .unwrap_or_else(|_| UUID::zero()),
                        date: wire_string(&v.date),
                        date_utc: v.date_utc,
                        desc: wire_string(&v.desc),
                        duration: v.duration,
                        flags: event_flags(v.event_flags),
                        global_pos: v.global_pos,
                        id: v.event_id,
                        name: wire_string(&v.name),
                        sim_name: wire_string(&v.sim_name),
                    };
                    *write(&self.event_info) = Some(info.clone());
                    self.events.event_info.emit(EventInfoReplyEventArgs {
                        matched_event: info,
                    });
                    self.event_notify.notify_waiters();
                }
            }
            _ => {}
        }
    }
}
fn event_flags(value: u32) -> crate::DirectoryManagerEventFlags {
    match value & 3 {
        1 => crate::DirectoryManagerEventFlags::Mature,
        2 => crate::DirectoryManagerEventFlags::Adult,
        _ => crate::DirectoryManagerEventFlags::PG,
    }
}
fn event_category(value: &str) -> crate::DirectoryManagerEventCategories {
    match value.parse::<i32>().unwrap_or_default() {
        18 => crate::DirectoryManagerEventCategories::Discussion,
        19 => crate::DirectoryManagerEventCategories::Sports,
        20 => crate::DirectoryManagerEventCategories::LiveMusic,
        22 => crate::DirectoryManagerEventCategories::Commercial,
        23 => crate::DirectoryManagerEventCategories::Nightlife,
        24 => crate::DirectoryManagerEventCategories::Games,
        25 => crate::DirectoryManagerEventCategories::Pageants,
        26 => crate::DirectoryManagerEventCategories::Education,
        27 => crate::DirectoryManagerEventCategories::Arts,
        28 => crate::DirectoryManagerEventCategories::Charity,
        29 => crate::DirectoryManagerEventCategories::Miscellaneous,
        _ => crate::DirectoryManagerEventCategories::All,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use libremetaverse_types::compat::CancellationTokenSource;
    #[test]
    fn place_slurl_uses_local_coordinates() {
        let value = DirectoryManagerPlacesSearchData {
            actual_area: 0,
            billable_area: 0,
            desc: String::new(),
            dwell: 0.0,
            flags: crate::DirectoryManagerPlacesFlags::NotForSale,
            global_x: 1025.0,
            global_y: 2050.0,
            global_z: 30.0,
            name: String::new(),
            owner_id: UUID::zero(),
            price: 0,
            sku: String::new(),
            sim_name: "Example Region".into(),
            snapshot_id: UUID::zero(),
        };
        assert_eq!(
            value.to_s_lurl().unwrap(),
            "secondlife:///app/teleport/Example%20Region/1/2/30"
        );
    }
    #[tokio::test]
    async fn async_search_results_are_ordered_and_complete() {
        let stream = IAsyncEnumerable::new();
        stream.push(DirectoryManagerAgentSearchData {
            agent_id: UUID::zero(),
            first_name: "Ada".into(),
            last_name: "Lovelace".into(),
            online: true,
        });
        stream.close();
        assert_eq!(stream.next().await.unwrap().to_string(), "Ada Lovelace");
        assert!(stream.next().await.is_none());
    }
    #[tokio::test]
    async fn async_search_cancellation_wakes_pending_consumer() {
        let source = CancellationTokenSource::new();
        let stream =
            IAsyncEnumerable::<DirectoryManagerAgentSearchData>::with_cancellation(source.token());
        let pending = stream.next();
        source.cancel();
        assert!(pending.await.is_none());
    }
}
