//! Pure, synchronized RLV restriction, inventory, lock, and permission state.

#![allow(clippy::missing_errors_doc)] // Mapped Result signatures use the shared compatibility error.
#![allow(clippy::must_use_candidate)] // Mapped property signatures intentionally mirror C#.
#![allow(clippy::needless_pass_by_value)] // Owned arguments preserve mapped signatures.
#![allow(clippy::option_option)] // Nullable value types retain the generated three-state signature.
#![allow(clippy::unnecessary_wraps)] // Constructors retain mapped fallibility.

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
use std::sync::{
    Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
    atomic::{AtomicU64, Ordering},
};

use libremetaverse_types::compat::{
    CancellationToken, EventHandler, Guid, ImmutableDictionary, ImmutableList, Subscription,
    Vector3,
};
use libremetaverse_types::{Error, UUID};

use crate::{
    IRlvActionCallbacks, RlvAttachmentPoint, RlvGestureState,
    RlvPermissionsServiceHoverTextLocation, RlvPermissionsServiceObjectLocation,
    RlvPermissionsServiceTouchLocation, RlvRestriction, RlvRestrictionType, RlvValue,
    RlvWearableType, restriction_name,
};

/// Maximum number of folders accepted in one immutable inventory snapshot.
pub const MAX_RLV_INVENTORY_FOLDERS: usize = 100_000;
/// Maximum number of internal and external items accepted in one snapshot.
pub const MAX_RLV_INVENTORY_ITEMS: usize = 1_000_000;
/// Maximum number of simultaneously retained restrictions.
pub const MAX_RLV_RESTRICTIONS: usize = 100_000;

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

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

fn nullable<T>(value: Option<Option<T>>) -> Option<T> {
    value.flatten()
}

fn mapped_nullable<T>(value: Option<T>) -> Option<Option<T>> {
    Some(value)
}

fn starts_with_ignore_case(value: &str, prefix: &str) -> bool {
    value
        .get(..prefix.len())
        .is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
        || value.to_lowercase().starts_with(&prefix.to_lowercase())
}

#[derive(Debug)]
struct FolderData {
    id: Guid,
    name: String,
    parent: Option<Weak<RwLock<FolderData>>>,
    children: Vec<RlvSharedFolder>,
    items: Vec<RlvInventoryItem>,
}

/// Shared RLV inventory folder with reference semantics and short-held locks.
#[derive(Clone)]
pub struct RlvSharedFolder {
    inner: Arc<RwLock<FolderData>>,
}

impl fmt::Debug for RlvSharedFolder {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("RlvSharedFolder")
            .field("id", &self.native_id())
            .field("name", &self.native_name())
            .finish_non_exhaustive()
    }
}

impl RlvSharedFolder {
    pub(crate) fn native_new(id: Guid, name: String) -> Result<Self, Error> {
        Ok(Self {
            inner: Arc::new(RwLock::new(FolderData {
                id,
                name,
                parent: None,
                children: Vec::new(),
                items: Vec::new(),
            })),
        })
    }

    pub(crate) fn native_add_child(&self, id: Guid, name: String) -> Result<Self, Error> {
        if name.is_empty() {
            return Err(Error::Argument);
        }
        let child = Self {
            inner: Arc::new(RwLock::new(FolderData {
                id,
                name,
                parent: Some(Arc::downgrade(&self.inner)),
                children: Vec::new(),
                items: Vec::new(),
            })),
        };
        write_lock(&self.inner).children.push(child.clone());
        Ok(child)
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn native_add_item(
        &self,
        id: Guid,
        name: String,
        is_link: bool,
        attached_to: Option<Option<RlvAttachmentPoint>>,
        attached_prim_id: Option<Option<Guid>>,
        worn_on: Option<Option<RlvWearableType>>,
        gesture_state: Option<Option<RlvGestureState>>,
    ) -> Result<RlvInventoryItem, Error> {
        let item = RlvInventoryItem::new_internal(
            id,
            name,
            is_link,
            self,
            nullable(attached_to),
            nullable(attached_prim_id),
            nullable(worn_on),
            nullable(gesture_state),
        )?;
        write_lock(&self.inner).items.push(item.clone());
        Ok(item)
    }

    pub(crate) fn native_to_string(&self) -> String {
        let data = read_lock(&self.inner);
        format!(
            "{} (Children: {}, Items: {})",
            data.name,
            data.children.len(),
            data.items.len()
        )
    }

    pub(crate) fn native_children(&self) -> Vec<Self> {
        read_lock(&self.inner).children.clone()
    }

    pub(crate) fn native_id(&self) -> Guid {
        read_lock(&self.inner).id
    }

    pub(crate) fn native_items(&self) -> Vec<RlvInventoryItem> {
        read_lock(&self.inner).items.clone()
    }

    pub(crate) fn native_name(&self) -> String {
        read_lock(&self.inner).name.clone()
    }

    pub(crate) fn native_set_name(&mut self, value: String) {
        write_lock(&self.inner).name = value;
    }

    pub(crate) fn native_parent(&self) -> Option<Self> {
        read_lock(&self.inner)
            .parent
            .as_ref()
            .and_then(Weak::upgrade)
            .map(|inner| Self { inner })
    }

    pub(crate) fn native_set_parent(&mut self, value: Option<Self>) {
        write_lock(&self.inner).parent = value.map(|parent| Arc::downgrade(&parent.inner));
    }
}

#[derive(Debug)]
struct InventoryItemData {
    id: Guid,
    name: String,
    is_link: bool,
    folder: Option<Weak<RwLock<FolderData>>>,
    folder_id: Option<Guid>,
    attached_to: Option<RlvAttachmentPoint>,
    attached_prim_id: Option<Guid>,
    worn_on: Option<RlvWearableType>,
    gesture_state: Option<RlvGestureState>,
}

/// Shared mutable inventory item corresponding to the C# reference type.
#[derive(Clone)]
pub struct RlvInventoryItem {
    inner: Arc<RwLock<InventoryItemData>>,
}

impl fmt::Debug for RlvInventoryItem {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("RlvInventoryItem")
            .field("id", &self.native_id())
            .field("name", &self.native_name())
            .finish_non_exhaustive()
    }
}

impl RlvInventoryItem {
    #[allow(clippy::too_many_arguments)]
    pub(crate) fn native_new(
        id: Guid,
        name: String,
        is_link: bool,
        external_folder_id: Option<Option<Guid>>,
        attached_to: Option<Option<RlvAttachmentPoint>>,
        attached_prim_id: Option<Option<Guid>>,
        worn_on: Option<Option<RlvWearableType>>,
        gesture_state: Option<Option<RlvGestureState>>,
    ) -> Result<Self, Error> {
        if name.is_empty() {
            return Err(Error::Argument);
        }
        Ok(Self {
            inner: Arc::new(RwLock::new(InventoryItemData {
                id,
                name,
                is_link,
                folder: None,
                folder_id: nullable(external_folder_id),
                attached_to: nullable(attached_to),
                attached_prim_id: nullable(attached_prim_id),
                worn_on: nullable(worn_on),
                gesture_state: nullable(gesture_state),
            })),
        })
    }

    #[allow(clippy::too_many_arguments)]
    fn new_internal(
        id: Guid,
        name: String,
        is_link: bool,
        folder: &RlvSharedFolder,
        attached_to: Option<RlvAttachmentPoint>,
        attached_prim_id: Option<Guid>,
        worn_on: Option<RlvWearableType>,
        gesture_state: Option<RlvGestureState>,
    ) -> Result<Self, Error> {
        if name.is_empty() {
            return Err(Error::Argument);
        }
        Ok(Self {
            inner: Arc::new(RwLock::new(InventoryItemData {
                id,
                name,
                is_link,
                folder: Some(Arc::downgrade(&folder.inner)),
                folder_id: Some(folder.native_id()),
                attached_to,
                attached_prim_id,
                worn_on,
                gesture_state,
            })),
        })
    }

    pub(crate) fn native_to_string(&self) -> String {
        self.native_name()
    }

    pub(crate) fn native_attached_prim_id(&self) -> Option<Option<Guid>> {
        mapped_nullable(read_lock(&self.inner).attached_prim_id)
    }

    pub(crate) fn native_set_attached_prim_id(&mut self, value: Option<Option<Guid>>) {
        write_lock(&self.inner).attached_prim_id = nullable(value);
    }

    pub(crate) fn native_attached_to(&self) -> Option<Option<RlvAttachmentPoint>> {
        mapped_nullable(read_lock(&self.inner).attached_to)
    }

    pub(crate) fn native_set_attached_to(&mut self, value: Option<Option<RlvAttachmentPoint>>) {
        write_lock(&self.inner).attached_to = nullable(value);
    }

    pub(crate) fn native_folder(&self) -> Option<RlvSharedFolder> {
        read_lock(&self.inner)
            .folder
            .as_ref()
            .and_then(Weak::upgrade)
            .map(|inner| RlvSharedFolder { inner })
    }

    pub(crate) fn native_folder_id(&self) -> Option<Option<Guid>> {
        mapped_nullable(read_lock(&self.inner).folder_id)
    }

    pub(crate) fn native_gesture_state(&self) -> Option<Option<RlvGestureState>> {
        mapped_nullable(read_lock(&self.inner).gesture_state)
    }

    pub(crate) fn native_set_gesture_state(&mut self, value: Option<Option<RlvGestureState>>) {
        write_lock(&self.inner).gesture_state = nullable(value);
    }

    pub(crate) fn native_id(&self) -> Guid {
        read_lock(&self.inner).id
    }

    pub(crate) fn native_is_link(&self) -> bool {
        read_lock(&self.inner).is_link
    }

    pub(crate) fn native_set_is_link(&mut self, value: bool) {
        write_lock(&self.inner).is_link = value;
    }

    pub(crate) fn native_name(&self) -> String {
        read_lock(&self.inner).name.clone()
    }

    pub(crate) fn native_set_name(&mut self, value: String) {
        write_lock(&self.inner).name = value;
    }

    pub(crate) fn native_worn_on(&self) -> Option<Option<RlvWearableType>> {
        mapped_nullable(read_lock(&self.inner).worn_on)
    }

    pub(crate) fn native_set_worn_on(&mut self, value: Option<Option<RlvWearableType>>) {
        write_lock(&self.inner).worn_on = nullable(value);
    }

    pub fn attached_prim_id_value(&self) -> Option<Guid> {
        read_lock(&self.inner).attached_prim_id
    }

    pub fn attached_to_value(&self) -> Option<RlvAttachmentPoint> {
        read_lock(&self.inner).attached_to
    }

    pub fn folder_id_value(&self) -> Option<Guid> {
        read_lock(&self.inner).folder_id
    }

    pub fn gesture_state_value(&self) -> Option<RlvGestureState> {
        read_lock(&self.inner).gesture_state
    }

    pub fn worn_on_value(&self) -> Option<RlvWearableType> {
        read_lock(&self.inner).worn_on
    }
}

/// Immutable index over one shared-folder tree and external worn items.
#[derive(Clone, Debug)]
pub struct InventoryMap {
    root: RlvSharedFolder,
    items: ImmutableDictionary<Guid, ImmutableList<RlvInventoryItem>>,
    external_items: ImmutableDictionary<Guid, RlvInventoryItem>,
    folders: ImmutableDictionary<Guid, RlvSharedFolder>,
    all_items: Arc<[RlvInventoryItem]>,
    external_ordered: Arc<[RlvInventoryItem]>,
}

impl InventoryMap {
    pub(crate) fn native_new(
        root: RlvSharedFolder,
        external_items: Vec<RlvInventoryItem>,
    ) -> Result<Self, Error> {
        let mut folders = HashMap::new();
        let mut items: HashMap<Guid, Vec<RlvInventoryItem>> = HashMap::new();
        let mut all_items = Vec::new();
        // The pinned compatibility surface observes child-folder matches
        // before parent-folder matches for the deprecated single-result query.
        // An explicit post-order stack preserves that behavior without
        // recursive call-stack growth.
        let mut stack = vec![(root.clone(), false)];
        while let Some((folder, expanded)) = stack.pop() {
            if expanded {
                let folder_items = folder.native_items();
                if all_items.len().saturating_add(folder_items.len()) > MAX_RLV_INVENTORY_ITEMS {
                    return Err(Error::Argument);
                }
                for item in folder_items {
                    items
                        .entry(item.native_id())
                        .or_default()
                        .push(item.clone());
                    all_items.push(item);
                }
                continue;
            }
            if folders.contains_key(&folder.native_id()) {
                continue;
            }
            if folders.len() >= MAX_RLV_INVENTORY_FOLDERS {
                return Err(Error::Argument);
            }
            folders.insert(folder.native_id(), folder.clone());
            stack.push((folder.clone(), true));
            let mut children = folder.native_children();
            children.reverse();
            stack.extend(children.into_iter().map(|child| (child, false)));
        }
        if all_items.len().saturating_add(external_items.len()) > MAX_RLV_INVENTORY_ITEMS {
            return Err(Error::Argument);
        }
        let mut external_map = HashMap::new();
        for item in &external_items {
            if external_map
                .insert(item.native_id(), item.clone())
                .is_some()
            {
                return Err(Error::Argument);
            }
        }
        let items = items
            .into_iter()
            .map(|(id, values)| (id, values.into()))
            .collect();
        Ok(Self {
            root,
            items: ImmutableDictionary::from_map(items),
            external_items: ImmutableDictionary::from_map(external_map),
            folders: ImmutableDictionary::from_map(folders),
            all_items: all_items.into(),
            external_ordered: external_items.into(),
        })
    }

    pub(crate) fn native_find_folders_containing(
        &self,
        limit_to_one_result: bool,
        attached_prim_id: Option<Option<Guid>>,
        attachment_point: Option<Option<RlvAttachmentPoint>>,
        wearable_type: Option<Option<RlvWearableType>>,
    ) -> Result<Box<dyn Iterator<Item = RlvSharedFolder>>, Error> {
        let attached_prim_id = nullable(attached_prim_id);
        let attachment_point = nullable(attachment_point);
        let wearable_type = nullable(wearable_type);
        let found = if let Some(prim_id) = attached_prim_id {
            self.items_by(|item| item.attached_prim_id_value() == Some(prim_id))
        } else if let Some(point) = attachment_point {
            self.items_by(|item| item.attached_to_value() == Some(point))
        } else if let Some(wearable) = wearable_type {
            self.items_by(|item| item.worn_on_value() == Some(wearable))
        } else {
            Vec::new()
        };
        let mut seen = HashSet::new();
        let mut folders = Vec::new();
        for item in found {
            let Some(folder) = item.native_folder() else {
                return Ok(Box::new(Vec::new().into_iter()));
            };
            if seen.insert(folder.native_id()) {
                folders.push(folder);
                if limit_to_one_result {
                    break;
                }
            }
        }
        Ok(Box::new(folders.into_iter()))
    }

    pub(crate) fn native_get_items_by_attachment_point(
        &self,
        attachment_point: RlvAttachmentPoint,
    ) -> Result<Vec<RlvInventoryItem>, Error> {
        Ok(self.items_by(|item| item.attached_to_value() == Some(attachment_point)))
    }

    pub(crate) fn native_get_items_by_id(
        &self,
        item_id: Guid,
    ) -> Result<Vec<RlvInventoryItem>, Error> {
        let mut found = self
            .items
            .get(&item_id)
            .map_or_else(Vec::new, |values| values.as_slice().to_vec());
        if let Some(item) = self.external_items.get(&item_id) {
            found.push(item.clone());
        }
        Ok(found)
    }

    pub(crate) fn native_get_items_by_prim_id(
        &self,
        prim_id: Guid,
    ) -> Result<Vec<RlvInventoryItem>, Error> {
        Ok(self.items_by(|item| item.attached_prim_id_value() == Some(prim_id)))
    }

    pub(crate) fn native_get_items_by_wearable_type(
        &self,
        wearable_type: RlvWearableType,
    ) -> Result<Vec<RlvInventoryItem>, Error> {
        Ok(self.items_by(|item| item.worn_on_value() == Some(wearable_type)))
    }

    pub(crate) fn native_try_build_path_to_folder(
        &self,
        folder_id: Guid,
        final_path: &mut Option<String>,
    ) -> bool {
        *final_path = None;
        let Some(mut folder) = self.folders.get(&folder_id).cloned() else {
            return false;
        };
        let mut path = Vec::new();
        let mut visited = HashSet::new();
        loop {
            if !visited.insert(folder.native_id()) {
                return false;
            }
            let Some(parent) = folder.native_parent() else {
                break;
            };
            path.push(folder.native_name());
            folder = parent;
        }
        path.reverse();
        *final_path = Some(path.join("/"));
        true
    }

    pub(crate) fn native_try_get_folder_from_path(
        &self,
        path: String,
        skip_private_folders: bool,
        folder: &mut Option<RlvSharedFolder>,
    ) -> bool {
        *folder = None;
        if path.is_empty() {
            return false;
        }
        let mut variants = vec![path.as_str()];
        if let Some(value) = path.strip_prefix('/') {
            variants.push(value);
        }
        if let Some(value) = path.strip_suffix('/') {
            variants.push(value);
        }
        if let Some(value) = path
            .strip_prefix('/')
            .and_then(|value| value.strip_suffix('/'))
        {
            variants.push(value);
        }
        let found = variants
            .into_iter()
            .find_map(|candidate| self.folder_from_path(candidate, skip_private_folders));
        *folder = found;
        folder.is_some()
    }

    pub(crate) fn native_external_items(&self) -> ImmutableDictionary<Guid, RlvInventoryItem> {
        self.external_items.clone()
    }

    pub(crate) fn native_folders(&self) -> ImmutableDictionary<Guid, RlvSharedFolder> {
        self.folders.clone()
    }

    pub(crate) fn native_items(
        &self,
    ) -> ImmutableDictionary<Guid, ImmutableList<RlvInventoryItem>> {
        self.items.clone()
    }

    pub(crate) fn native_root(&self) -> RlvSharedFolder {
        self.root.clone()
    }

    pub fn current_outfit(&self) -> Vec<RlvInventoryItem> {
        self.items_by(|item| {
            item.worn_on_value().is_some()
                || item.attached_to_value().is_some()
                || item.gesture_state_value() == Some(RlvGestureState::Active)
        })
    }

    fn items_by(&self, predicate: impl Fn(&RlvInventoryItem) -> bool) -> Vec<RlvInventoryItem> {
        self.all_items
            .iter()
            .chain(self.external_ordered.iter())
            .filter(|item| predicate(item))
            .cloned()
            .collect()
    }

    fn folder_from_path(&self, path: &str, skip_private: bool) -> Option<RlvSharedFolder> {
        if path.is_empty() {
            return None;
        }
        let mut remaining = path;
        let mut current = self.root.clone();
        let mut visited = HashSet::new();
        loop {
            if !visited.insert(current.native_id()) {
                return None;
            }
            let mut selected: Option<(RlvSharedFolder, usize, bool)> = None;
            for child in current.native_children() {
                let name = child.native_name();
                if skip_private && name.starts_with('.') {
                    continue;
                }
                let exact = starts_with_ignore_case(remaining, &name);
                let prefixed =
                    !exact && matches!(name.as_bytes().first(), Some(b'.' | b'~' | b'+'));
                let fixed = if prefixed { &name[1..] } else { &name };
                if !starts_with_ignore_case(remaining, fixed) {
                    continue;
                }
                let length = fixed.len();
                let valid_boundary = remaining.len() == length
                    || remaining.as_bytes().get(length).copied() == Some(b'/');
                if !valid_boundary {
                    continue;
                }
                let replace = selected
                    .as_ref()
                    .is_none_or(|(_, best_len, best_prefixed)| {
                        length > *best_len || (length == *best_len && !prefixed && *best_prefixed)
                    });
                if replace {
                    selected = Some((child, length, prefixed));
                    if remaining.len() == length && !prefixed {
                        break;
                    }
                }
            }
            let (folder, consumed, _) = selected?;
            if remaining.len() == consumed {
                return Some(folder);
            }
            remaining = remaining.get(consumed + 1..)?;
            current = folder;
        }
    }
}

/// Thread-safe, case-insensitive behavior blacklist.
#[derive(Clone, Debug, Default)]
pub struct RlvBlacklist {
    entries: Arc<RwLock<BTreeSet<String>>>,
}

impl RlvBlacklist {
    pub fn new() -> Self {
        Self::default()
    }

    pub(crate) fn native_blacklist_behavior(&self, behavior: String) -> Result<(), Error> {
        if behavior.trim().is_empty() {
            return Err(Error::Argument);
        }
        write_lock(&self.entries).insert(behavior.to_lowercase());
        Ok(())
    }

    pub(crate) fn native_get_blacklist(&self) -> Result<Vec<String>, Error> {
        Ok(read_lock(&self.entries).iter().cloned().collect())
    }

    pub(crate) fn native_is_blacklisted(&self, behavior: String) -> Result<bool, Error> {
        if behavior.trim().is_empty() {
            return Ok(false);
        }
        Ok(read_lock(&self.entries).contains(&behavior.to_lowercase()))
    }

    pub(crate) fn native_un_blacklist_behavior(&self, behavior: String) -> Result<(), Error> {
        if behavior.trim().is_empty() {
            return Err(Error::Argument);
        }
        write_lock(&self.entries).remove(&behavior.to_lowercase());
        Ok(())
    }
}

/// Snapshot of effective camera restrictions.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CameraRestrictions {
    is_locked: bool,
    zoom_min: Option<f32>,
    zoom_max: Option<f32>,
    draw_min: Option<f32>,
    draw_max: Option<f32>,
    draw_alpha_min: Option<f32>,
    draw_alpha_max: Option<f32>,
    fov_min: Option<f32>,
    fov_max: Option<f32>,
    av_dist_min: Option<f32>,
    av_dist_max: Option<f32>,
    av_dist: Option<f32>,
    draw_color: Option<Vector3>,
    texture: Option<Guid>,
}

impl CameraRestrictions {
    fn from_manager(manager: &RlvRestrictionManager) -> Self {
        let maximum = |kind| manager.float_values(kind).into_iter().reduce(f32::max);
        let minimum = |kind| manager.float_values(kind).into_iter().reduce(f32::min);
        let color_values: Vec<[f32; 3]> = manager
            .restrictions_by_type(RlvRestrictionType::CamDrawColor)
            .into_iter()
            .filter_map(|restriction| match restriction.values() {
                [RlvValue::Real(x), RlvValue::Real(y), RlvValue::Real(z)] => {
                    Some([x.clamp(0.0, 1.0), y.clamp(0.0, 1.0), z.clamp(0.0, 1.0)])
                }
                _ => None,
            })
            .collect();
        let draw_color = (!color_values.is_empty()).then(|| {
            let (sums, divisor) =
                color_values
                    .iter()
                    .fold(([0.0; 3], 0.0_f32), |(mut sums, count), color| {
                        sums[0] += color[0];
                        sums[1] += color[1];
                        sums[2] += color[2];
                        (sums, count + 1.0)
                    });
            Vector3([sums[0] / divisor, sums[1] / divisor, sums[2] / divisor])
        });
        let texture = manager
            .restrictions_by_type(RlvRestrictionType::SetCamTextures)
            .last()
            .and_then(|restriction| match restriction.values() {
                [] => Some(Guid([0; 16])),
                [RlvValue::Uuid(value)] => Some(*value),
                _ => None,
            });
        Self {
            zoom_min: maximum(RlvRestrictionType::CamZoomMin),
            fov_min: maximum(RlvRestrictionType::SetCamFovMin),
            av_dist_min: maximum(RlvRestrictionType::SetCamAvDistMin),
            zoom_max: minimum(RlvRestrictionType::CamZoomMax),
            draw_min: minimum(RlvRestrictionType::CamDrawMin),
            draw_max: minimum(RlvRestrictionType::CamDrawMax),
            draw_alpha_min: minimum(RlvRestrictionType::CamDrawAlphaMin),
            draw_alpha_max: minimum(RlvRestrictionType::CamDrawAlphaMax),
            fov_max: minimum(RlvRestrictionType::SetCamFovMax),
            av_dist_max: minimum(RlvRestrictionType::SetCamAvDistMax),
            av_dist: minimum(RlvRestrictionType::CamAvDist),
            draw_color,
            texture,
            is_locked: !manager
                .restrictions_by_type(RlvRestrictionType::SetCamUnlock)
                .is_empty(),
        }
    }

    pub(crate) fn native_av_dist(&self) -> Option<Option<f32>> {
        mapped_nullable(self.av_dist)
    }
    pub(crate) fn native_av_dist_max(&self) -> Option<Option<f32>> {
        mapped_nullable(self.av_dist_max)
    }
    pub(crate) fn native_av_dist_min(&self) -> Option<Option<f32>> {
        mapped_nullable(self.av_dist_min)
    }
    pub(crate) fn native_draw_alpha_max(&self) -> Option<Option<f32>> {
        mapped_nullable(self.draw_alpha_max)
    }
    pub(crate) fn native_draw_alpha_min(&self) -> Option<Option<f32>> {
        mapped_nullable(self.draw_alpha_min)
    }
    pub(crate) fn native_draw_color(&self) -> Option<Option<Vector3>> {
        mapped_nullable(self.draw_color)
    }
    pub(crate) fn native_draw_max(&self) -> Option<Option<f32>> {
        mapped_nullable(self.draw_max)
    }
    pub(crate) fn native_draw_min(&self) -> Option<Option<f32>> {
        mapped_nullable(self.draw_min)
    }
    pub(crate) fn native_fov_max(&self) -> Option<Option<f32>> {
        mapped_nullable(self.fov_max)
    }
    pub(crate) fn native_fov_min(&self) -> Option<Option<f32>> {
        mapped_nullable(self.fov_min)
    }
    pub(crate) fn native_is_locked(&self) -> bool {
        self.is_locked
    }
    pub(crate) fn native_texture(&self) -> Option<Option<Guid>> {
        mapped_nullable(self.texture)
    }
    pub(crate) fn native_zoom_max(&self) -> Option<Option<f32>> {
        mapped_nullable(self.zoom_max)
    }
    pub(crate) fn native_zoom_min(&self) -> Option<Option<f32>> {
        mapped_nullable(self.zoom_min)
    }
}

/// Immutable current viewer camera settings supplied to query handlers.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CameraSettings {
    av_dist_min: f32,
    av_dist_max: f32,
    fov_min: f32,
    fov_max: f32,
    zoom_min: f32,
    current_fov: f32,
}

impl CameraSettings {
    pub(crate) fn native_new(
        av_dist_min: f32,
        av_dist_max: f32,
        fov_min: f32,
        fov_max: f32,
        zoom_min: f32,
        current_fov: f32,
    ) -> Result<Self, Error> {
        Ok(Self {
            av_dist_min,
            av_dist_max,
            fov_min,
            fov_max,
            zoom_min,
            current_fov,
        })
    }

    pub(crate) fn native_av_dist_max(&self) -> f32 {
        self.av_dist_max
    }
    pub(crate) fn native_av_dist_min(&self) -> f32 {
        self.av_dist_min
    }
    pub(crate) fn native_current_fov(&self) -> f32 {
        self.current_fov
    }
    pub(crate) fn native_fov_max(&self) -> f32 {
        self.fov_max
    }
    pub(crate) fn native_fov_min(&self) -> f32 {
        self.fov_min
    }
    pub(crate) fn native_zoom_min(&self) -> f32 {
        self.zoom_min
    }
}

/// Immutable event payload emitted after a restriction state update.
#[derive(Clone, Debug)]
pub struct RestrictionUpdatedEventArgs {
    restriction: RlvRestriction,
    is_new: bool,
    is_deleted: bool,
}

impl RestrictionUpdatedEventArgs {
    pub(crate) fn native_new(
        restriction: RlvRestriction,
        is_new: bool,
        is_deleted: bool,
    ) -> Result<Self, Error> {
        Ok(Self {
            restriction,
            is_new,
            is_deleted,
        })
    }

    pub(crate) fn native_is_deleted(&self) -> bool {
        self.is_deleted
    }

    pub(crate) fn native_is_new(&self) -> bool {
        self.is_new
    }

    pub(crate) fn native_restriction(&self) -> RlvRestriction {
        self.restriction.clone()
    }
}

#[derive(Clone, Debug, Default)]
struct LockedFolderAccumulator {
    folder: Option<RlvSharedFolder>,
    detach_restrictions: Vec<RlvRestriction>,
    attach_restrictions: Vec<RlvRestriction>,
    detach_exceptions: Vec<RlvRestriction>,
    attach_exceptions: Vec<RlvRestriction>,
}

/// Public immutable snapshot of one shared folder's effective locks.
#[derive(Clone, Debug)]
pub struct LockedFolderPublic {
    id: Guid,
    name: String,
    detach_restrictions: Arc<[RlvRestriction]>,
    attach_restrictions: Arc<[RlvRestriction]>,
    detach_exceptions: Arc<[RlvRestriction]>,
    attach_exceptions: Arc<[RlvRestriction]>,
}

impl LockedFolderPublic {
    fn from_accumulator(folder: LockedFolderAccumulator) -> Option<Self> {
        let source = folder.folder?;
        Some(Self {
            id: source.native_id(),
            name: source.native_name(),
            detach_restrictions: folder.detach_restrictions.into(),
            attach_restrictions: folder.attach_restrictions.into(),
            detach_exceptions: folder.detach_exceptions.into(),
            attach_exceptions: folder.attach_exceptions.into(),
        })
    }

    pub(crate) fn native_to_string(&self) -> String {
        format!(
            "LockedFolder: {} ({}) - Locked: {}, CanAttach: {}, CanDetach: {}",
            self.name,
            guid_text(self.id),
            self.native_is_locked(),
            self.native_can_attach(),
            self.native_can_detach()
        )
    }

    pub(crate) fn native_attach_exceptions(&self) -> Vec<RlvRestriction> {
        self.attach_exceptions.to_vec()
    }
    pub(crate) fn native_attach_restrictions(&self) -> Vec<RlvRestriction> {
        self.attach_restrictions.to_vec()
    }
    pub(crate) fn native_can_attach(&self) -> bool {
        !self.attach_exceptions.is_empty() || self.attach_restrictions.is_empty()
    }
    pub(crate) fn native_can_detach(&self) -> bool {
        !self.detach_exceptions.is_empty() || self.detach_restrictions.is_empty()
    }
    pub(crate) fn native_detach_exceptions(&self) -> Vec<RlvRestriction> {
        self.detach_exceptions.to_vec()
    }
    pub(crate) fn native_detach_restrictions(&self) -> Vec<RlvRestriction> {
        self.detach_restrictions.to_vec()
    }
    pub(crate) fn native_id(&self) -> Guid {
        self.id
    }
    pub(crate) fn native_is_locked(&self) -> bool {
        !self.detach_restrictions.is_empty() || !self.attach_restrictions.is_empty()
    }
    pub(crate) fn native_name(&self) -> String {
        self.name.clone()
    }
}

#[derive(Debug, Default)]
struct RestrictionState {
    restrictions: Vec<RlvRestriction>,
    locked_folders: HashMap<Guid, LockedFolderPublic>,
    inventory: Option<InventoryMap>,
    revision: u64,
}

struct RestrictionManagerInner {
    state: RwLock<RestrictionState>,
    action_callbacks: RwLock<Option<Arc<dyn IRlvActionCallbacks>>>,
    handlers: Arc<Mutex<BTreeMap<u64, EventHandler<RestrictionUpdatedEventArgs>>>>,
    next_handler: AtomicU64,
}

/// Thread-safe restriction provider with snapshot reads and post-lock events.
#[derive(Clone)]
pub struct RlvRestrictionManager {
    inner: Arc<RestrictionManagerInner>,
}

impl fmt::Debug for RlvRestrictionManager {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("RlvRestrictionManager")
            .field(
                "restriction_count",
                &read_lock(&self.inner.state).restrictions.len(),
            )
            .finish_non_exhaustive()
    }
}

impl Default for RlvRestrictionManager {
    fn default() -> Self {
        Self::new()
    }
}

impl RlvRestrictionManager {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(RestrictionManagerInner {
                state: RwLock::new(RestrictionState::default()),
                action_callbacks: RwLock::new(None),
                handlers: Arc::new(Mutex::new(BTreeMap::new())),
                next_handler: AtomicU64::new(1),
            }),
        }
    }

    pub(crate) fn set_action_callbacks(&self, callbacks: Arc<dyn IRlvActionCallbacks>) {
        *write_lock(&self.inner.action_callbacks) = Some(callbacks);
    }

    /// Replaces the shared-inventory snapshot used for folder-lock decisions.
    pub fn set_inventory_map(&self, inventory: Option<InventoryMap>) {
        let mut state = write_lock(&self.inner.state);
        state.inventory = inventory;
        state.revision = state.revision.wrapping_add(1);
        drop(state);
        self.rebuild_locked_folders();
    }

    /// Adds a unique restriction and emits an update after releasing state locks.
    pub fn add_restriction(&self, restriction: RlvRestriction) -> Result<bool, Error> {
        let added = {
            let mut state = write_lock(&self.inner.state);
            if state.restrictions.contains(&restriction) {
                false
            } else {
                if state.restrictions.len() >= MAX_RLV_RESTRICTIONS {
                    return Err(Error::Argument);
                }
                state.restrictions.push(restriction.clone());
                state.revision = state.revision.wrapping_add(1);
                true
            }
        };
        if added {
            self.rebuild_locked_folders();
            self.emit(RestrictionUpdatedEventArgs {
                restriction,
                is_new: true,
                is_deleted: false,
            });
        }
        Ok(added)
    }

    /// Removes one restriction equal by behavior, sender, and exact arguments.
    pub fn remove_restriction(&self, restriction: &RlvRestriction) -> bool {
        let removed = {
            let mut state = write_lock(&self.inner.state);
            let removed = state
                .restrictions
                .iter()
                .position(|candidate| candidate == restriction)
                .map(|index| state.restrictions.remove(index));
            if removed.is_some() {
                state.revision = state.revision.wrapping_add(1);
            }
            removed
        };
        if let Some(removed) = removed {
            self.rebuild_locked_folders();
            self.emit(RestrictionUpdatedEventArgs {
                restriction: removed,
                is_new: false,
                is_deleted: true,
            });
            true
        } else {
            false
        }
    }

    /// Removes restrictions from one sender whose canonical name contains the filter.
    pub fn clear_sender(&self, sender: Guid, behavior_filter: &str) -> Vec<RlvRestriction> {
        let filter = behavior_filter.to_lowercase();
        let removed = {
            let mut state = write_lock(&self.inner.state);
            let mut removed = Vec::new();
            state.restrictions.retain(|restriction| {
                let matches = restriction.sender() == sender
                    && restriction_name(restriction.behavior())
                        .to_lowercase()
                        .contains(&filter);
                if matches {
                    removed.push(restriction.clone());
                }
                !matches
            });
            if !removed.is_empty() {
                state.revision = state.revision.wrapping_add(1);
            }
            removed
        };
        if !removed.is_empty() {
            self.rebuild_locked_folders();
            for restriction in &removed {
                self.emit(RestrictionUpdatedEventArgs {
                    restriction: restriction.clone(),
                    is_new: false,
                    is_deleted: true,
                });
            }
        }
        removed
    }

    pub(crate) fn native_subscribe_restriction_updated(
        &self,
        handler: Option<EventHandler<RestrictionUpdatedEventArgs>>,
    ) -> Subscription {
        let Some(handler) = handler else {
            return Subscription::detached();
        };
        let id = self.inner.next_handler.fetch_add(1, Ordering::Relaxed);
        self.inner
            .handlers
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .insert(id, handler);
        let handlers = Arc::downgrade(&self.inner.handlers);
        Subscription::new(move || {
            let Some(handlers) = handlers.upgrade() else {
                return;
            };
            handlers
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .remove(&id);
        })
    }

    pub(crate) fn native_find_restrictions(
        &self,
        behavior_name_filter: Option<String>,
        sender_filter: Option<Option<Guid>>,
    ) -> Result<Vec<RlvRestriction>, Error> {
        let filter = behavior_name_filter.unwrap_or_default().to_lowercase();
        let sender = nullable(sender_filter);
        Ok(read_lock(&self.inner.state)
            .restrictions
            .iter()
            .filter(|restriction| {
                restriction_name(restriction.behavior())
                    .to_lowercase()
                    .contains(&filter)
                    && sender.is_none_or(|sender| sender == restriction.sender())
            })
            .cloned()
            .collect())
    }

    pub(crate) fn native_get_locked_folders(
        &self,
    ) -> Result<HashMap<Guid, LockedFolderPublic>, Error> {
        Ok(read_lock(&self.inner.state).locked_folders.clone())
    }

    pub(crate) fn native_get_restrictions_by_type(
        &self,
        restriction_type: RlvRestrictionType,
    ) -> Result<Vec<RlvRestriction>, Error> {
        Ok(self.restrictions_by_type(restriction_type))
    }

    pub(crate) fn native_get_tracked_prim_ids(
        &self,
    ) -> Result<Box<dyn Iterator<Item = Guid>>, Error> {
        let mut seen = HashSet::new();
        let ids: Vec<_> = read_lock(&self.inner.state)
            .restrictions
            .iter()
            .filter_map(|restriction| {
                let sender = restriction.sender();
                seen.insert(sender).then_some(sender)
            })
            .collect();
        Ok(Box::new(ids.into_iter()))
    }

    #[allow(clippy::unused_async)] // The mapped C# contract is asynchronous.
    pub(crate) async fn native_remove_restrictions_for_objects(
        &self,
        prim_ids: Box<dyn Iterator<Item = Guid>>,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<(), Error> {
        if let Some(token) = &cancellation_token {
            token.throw_if_cancellation_requested()?;
        }
        let ids = prim_ids.take(MAX_RLV_RESTRICTIONS + 1).collect::<Vec<_>>();
        if ids.len() > MAX_RLV_RESTRICTIONS {
            return Err(Error::Argument);
        }
        let ids: HashSet<_> = ids.into_iter().collect();
        let removed = {
            let mut state = write_lock(&self.inner.state);
            let mut removed = Vec::new();
            state.restrictions.retain(|restriction| {
                if ids.contains(&restriction.sender()) {
                    removed.push(restriction.clone());
                    false
                } else {
                    true
                }
            });
            if !removed.is_empty() {
                state.revision = state.revision.wrapping_add(1);
            }
            removed
        };
        if !removed.is_empty() {
            self.rebuild_locked_folders();
            for restriction in removed {
                if let Some(token) = &cancellation_token {
                    token.throw_if_cancellation_requested()?;
                }
                self.emit(RestrictionUpdatedEventArgs {
                    restriction: restriction.clone(),
                    is_new: false,
                    is_deleted: true,
                });
                self.notify_removed_restriction(&restriction, cancellation_token.clone())
                    .await?;
            }
        }
        Ok(())
    }

    async fn notify_removed_restriction(
        &self,
        restriction: &RlvRestriction,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<(), Error> {
        let callbacks = read_lock(&self.inner.action_callbacks).clone();
        let Some(callbacks) = callbacks else {
            return Ok(());
        };
        let listeners = self.restrictions_by_type(RlvRestrictionType::Notify);
        let behavior = restriction_name(restriction.original_behavior());
        let mut message = behavior.to_owned();
        if !restriction.values().is_empty() {
            message.push(':');
            message.push_str(
                &restriction
                    .values()
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .join(";"),
            );
        }
        message.push_str("=y");
        let token = cancellation_token.unwrap_or_default();
        for listener in listeners {
            token.throw_if_cancellation_requested()?;
            let [RlvValue::Integer(channel), rest @ ..] = listener.values() else {
                continue;
            };
            let filter = match rest.first() {
                Some(RlvValue::String(value)) => value.as_str(),
                _ => "",
            };
            if behavior
                .to_ascii_lowercase()
                .contains(&filter.to_ascii_lowercase())
            {
                callbacks
                    .send_reply(*channel, format!("/{message}"), token.clone())
                    .await?;
            }
        }
        Ok(())
    }

    pub(crate) fn native_try_get_locked_folder(
        &self,
        folder_id: Guid,
        locked_folder: &mut Option<LockedFolderPublic>,
    ) -> bool {
        let value = read_lock(&self.inner.state)
            .locked_folders
            .get(&folder_id)
            .cloned();
        *locked_folder = value;
        locked_folder.is_some()
    }

    fn restrictions_by_type(&self, restriction_type: RlvRestrictionType) -> Vec<RlvRestriction> {
        let canonical = canonical_restriction(restriction_type);
        read_lock(&self.inner.state)
            .restrictions
            .iter()
            .filter(|restriction| restriction.behavior() == canonical)
            .cloned()
            .collect()
    }

    fn float_values(&self, restriction_type: RlvRestrictionType) -> Vec<f32> {
        self.restrictions_by_type(restriction_type)
            .into_iter()
            .filter_map(|restriction| match restriction.values().first() {
                Some(RlvValue::Real(value)) => Some(*value),
                _ => None,
            })
            .collect()
    }

    fn emit(&self, event: RestrictionUpdatedEventArgs) {
        let handlers: Vec<_> = self
            .inner
            .handlers
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .values()
            .cloned()
            .collect();
        for handler in handlers {
            handler(event.clone());
        }
    }

    fn rebuild_locked_folders(&self) {
        loop {
            let (revision, inventory, restrictions) = {
                let state = read_lock(&self.inner.state);
                (
                    state.revision,
                    state.inventory.clone(),
                    state.restrictions.clone(),
                )
            };
            let locked_folders = inventory.map_or_else(HashMap::new, |inventory| {
                build_locked_folders(&inventory, &restrictions)
            });
            let mut state = write_lock(&self.inner.state);
            if state.revision == revision {
                state.locked_folders = locked_folders;
                return;
            }
        }
    }
}

fn build_locked_folders(
    inventory: &InventoryMap,
    restrictions: &[RlvRestriction],
) -> HashMap<Guid, LockedFolderPublic> {
    let mut result: HashMap<Guid, LockedFolderAccumulator> = HashMap::new();
    for restriction in restrictions {
        let behavior = restriction.behavior();
        if !matches!(
            behavior,
            RlvRestrictionType::DetachThis
                | RlvRestrictionType::DetachAllThis
                | RlvRestrictionType::AttachThis
                | RlvRestrictionType::AttachAllThis
                | RlvRestrictionType::DetachThisExcept
                | RlvRestrictionType::DetachAllThisExcept
                | RlvRestrictionType::AttachThisExcept
                | RlvRestrictionType::AttachAllThisExcept
        ) {
            continue;
        }
        let folders = folders_for_lock(inventory, restriction);
        let recursive = matches!(
            behavior,
            RlvRestrictionType::DetachAllThis
                | RlvRestrictionType::AttachAllThis
                | RlvRestrictionType::DetachAllThisExcept
                | RlvRestrictionType::AttachAllThisExcept
        );
        for folder in folders {
            add_folder_lock(
                &mut result,
                folder,
                restriction,
                recursive,
                &mut HashSet::new(),
            );
        }
    }
    result
        .into_iter()
        .filter_map(|(id, folder)| {
            LockedFolderPublic::from_accumulator(folder).map(|value| (id, value))
        })
        .collect()
}

fn folders_for_lock(
    inventory: &InventoryMap,
    restriction: &RlvRestriction,
) -> Vec<RlvSharedFolder> {
    match restriction.values() {
        [] => inventory
            .native_get_items_by_prim_id(restriction.sender())
            .unwrap_or_default()
            .into_iter()
            .filter_map(|item| item.native_folder())
            .collect(),
        [RlvValue::WearableType(value)] => inventory
            .native_get_items_by_wearable_type(*value)
            .unwrap_or_default()
            .into_iter()
            .filter_map(|item| item.native_folder())
            .collect(),
        [RlvValue::AttachmentPoint(value)] => inventory
            .native_get_items_by_attachment_point(*value)
            .unwrap_or_default()
            .into_iter()
            .filter_map(|item| item.native_folder())
            .collect(),
        [RlvValue::String(path)] => {
            let mut folder = None;
            if inventory.native_try_get_folder_from_path(path.clone(), false, &mut folder) {
                folder.into_iter().collect()
            } else {
                Vec::new()
            }
        }
        _ => Vec::new(),
    }
}

fn add_folder_lock(
    result: &mut HashMap<Guid, LockedFolderAccumulator>,
    folder: RlvSharedFolder,
    restriction: &RlvRestriction,
    recursive: bool,
    visited: &mut HashSet<Guid>,
) {
    if !visited.insert(folder.native_id()) {
        return;
    }
    let entry = result.entry(folder.native_id()).or_default();
    entry.folder.get_or_insert_with(|| folder.clone());
    match restriction.behavior() {
        RlvRestrictionType::DetachThis | RlvRestrictionType::DetachAllThis => {
            entry.detach_restrictions.push(restriction.clone());
        }
        RlvRestrictionType::AttachThis | RlvRestrictionType::AttachAllThis => {
            entry.attach_restrictions.push(restriction.clone());
        }
        RlvRestrictionType::DetachThisExcept | RlvRestrictionType::DetachAllThisExcept => {
            entry.detach_exceptions.push(restriction.clone());
        }
        RlvRestrictionType::AttachThisExcept | RlvRestrictionType::AttachAllThisExcept => {
            entry.attach_exceptions.push(restriction.clone());
        }
        _ => {}
    }
    if recursive {
        for child in folder.native_children() {
            add_folder_lock(result, child, restriction, true, visited);
        }
    }
}

fn canonical_restriction(value: RlvRestrictionType) -> RlvRestrictionType {
    match value {
        RlvRestrictionType::CamDistMax => RlvRestrictionType::SetCamAvDistMax,
        RlvRestrictionType::CamDistMin => RlvRestrictionType::SetCamAvDistMin,
        RlvRestrictionType::CamUnlock => RlvRestrictionType::SetCamUnlock,
        RlvRestrictionType::CamTextures => RlvRestrictionType::SetCamTextures,
        RlvRestrictionType::FarTouch => RlvRestrictionType::TouchFar,
        other => other,
    }
}

fn guid_text(value: Guid) -> String {
    UUID::new_with_guid(value).map_or_else(|_| format!("{value:?}"), |value| value.to_string())
}

/// Pure permission evaluator over a shared restriction manager.
#[derive(Clone, Debug)]
pub struct RlvPermissionsService {
    restrictions: RlvRestrictionManager,
}

impl RlvPermissionsService {
    pub fn new(restrictions: RlvRestrictionManager) -> Self {
        Self { restrictions }
    }

    pub fn restriction_manager(&self) -> RlvRestrictionManager {
        self.restrictions.clone()
    }

    fn restrictions(&self, kind: RlvRestrictionType) -> Vec<RlvRestriction> {
        self.restrictions.restrictions_by_type(kind)
    }

    fn absent(&self, kind: RlvRestrictionType) -> bool {
        self.restrictions(kind).is_empty()
    }

    fn optional_min(&self, kind: RlvRestrictionType, default: f32) -> (bool, f32) {
        let restrictions = self.restrictions(kind);
        if restrictions.is_empty() {
            return (false, default);
        }
        if restrictions
            .iter()
            .any(|restriction| restriction.values().is_empty())
        {
            return (true, default);
        }
        let value = restrictions
            .iter()
            .filter_map(|restriction| match restriction.values().first() {
                Some(RlvValue::Real(value)) => Some(*value),
                _ => None,
            })
            .reduce(f32::min)
            .unwrap_or(default);
        (true, value)
    }

    fn secure_allowed(
        &self,
        user_id: Option<Guid>,
        group_name: Option<&str>,
        normal: RlvRestrictionType,
        secure: Option<RlvRestrictionType>,
        explicit: Option<RlvRestrictionType>,
    ) -> bool {
        if explicit.is_some_and(|kind| {
            self.restrictions(kind)
                .iter()
                .any(|restriction| restriction_matches_target(restriction, user_id, group_name))
        }) {
            return false;
        }
        let normal_restrictions = self.restrictions(normal);
        let exceptions: Vec<_> = normal_restrictions
            .iter()
            .filter(|restriction| {
                restriction.is_exception()
                    && restriction_matches_target(restriction, user_id, group_name)
            })
            .collect();
        if let Some(secure) = secure {
            for restriction in self.restrictions(secure) {
                if exceptions
                    .iter()
                    .any(|exception| exception.sender() == restriction.sender())
                {
                    continue;
                }
                return false;
            }
        }
        let permissive = self.is_permissive_value();
        for restriction in normal_restrictions
            .iter()
            .filter(|restriction| !restriction.is_exception() && restriction.values().is_empty())
        {
            if exceptions
                .iter()
                .any(|exception| permissive || exception.sender() == restriction.sender())
            {
                continue;
            }
            return false;
        }
        true
    }

    fn is_permissive_value(&self) -> bool {
        self.absent(RlvRestrictionType::Permissive)
    }

    pub(crate) fn native_can_allow_idle(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::AllowIdle))
    }
    pub(crate) fn native_can_always_run(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::AlwaysRun))
    }
    pub(crate) fn native_can_fly(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Fly))
    }
    pub(crate) fn native_can_jump(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Jump))
    }
    pub(crate) fn native_can_temp_run(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::TempRun))
    }
    pub(crate) fn native_can_unsit(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Unsit))
    }
    pub(crate) fn native_can_interact(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Interact))
    }
    pub(crate) fn native_can_sit(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Interact) && self.absent(RlvRestrictionType::Sit))
    }
    pub(crate) fn native_can_tp_lm(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::TpLm))
    }
    pub(crate) fn native_can_tp_loc(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::TpLoc))
    }
    pub(crate) fn native_can_stand_tp(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::StandTp))
    }
    pub(crate) fn native_can_show_inv(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ShowInv))
    }
    pub(crate) fn native_can_view_note(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ViewNote))
    }
    pub(crate) fn native_can_view_script(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ViewScript))
    }
    pub(crate) fn native_can_view_texture(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ViewTexture))
    }
    pub(crate) fn native_can_default_wear(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::DefaultWear))
    }
    pub(crate) fn native_can_set_group(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::SetGroup))
    }
    pub(crate) fn native_can_set_debug(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::SetDebug))
    }
    pub(crate) fn native_can_set_env(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::SetEnv))
    }
    pub(crate) fn native_can_show_world_map(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ShowWorldMap))
    }
    pub(crate) fn native_can_show_mini_map(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ShowMiniMap))
    }
    pub(crate) fn native_can_show_loc(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ShowLoc))
    }
    pub(crate) fn native_can_show_nearby(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ShowNearby))
    }
    pub(crate) fn native_can_chat_shout(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ChatShout))
    }
    pub(crate) fn native_can_chat_whisper(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ChatWhisper))
    }
    pub(crate) fn native_can_chat_normal(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::ChatNormal))
    }
    pub(crate) fn native_can_send_chat(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::SendChat))
    }
    pub(crate) fn native_can_emote(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Emote))
    }
    pub(crate) fn native_can_send_gesture(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::SendGesture))
    }

    pub(crate) fn native_can_sit_tp(&self, max_distance: &mut f32) -> Result<bool, Error> {
        let (restricted, value) = self.optional_min(RlvRestrictionType::SitTp, 1.5);
        *max_distance = value;
        Ok(restricted)
    }

    pub(crate) fn native_can_tp_local(&self, max_distance: &mut f32) -> Result<bool, Error> {
        let (restricted, value) = self.optional_min(RlvRestrictionType::TpLocal, 0.0);
        *max_distance = value;
        Ok(restricted)
    }

    pub(crate) fn native_try_get_max_far_touch_distance(&self, max_distance: &mut f32) -> bool {
        let (restricted, value) = self.optional_min(RlvRestrictionType::FarTouch, 1.5);
        *max_distance = value;
        restricted
    }

    pub(crate) fn native_can_tp_lure(&self, user_id: Option<Option<Guid>>) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            None,
            RlvRestrictionType::TpLure,
            Some(RlvRestrictionType::TpLureSec),
            None,
        ))
    }

    pub(crate) fn native_can_tp_request(
        &self,
        user_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            None,
            RlvRestrictionType::TpRequest,
            Some(RlvRestrictionType::TpRequestSec),
            None,
        ))
    }

    pub(crate) fn native_is_auto_accept_tp(
        &self,
        user_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        Ok(self.auto_accept(RlvRestrictionType::AcceptTp, nullable(user_id)))
    }

    pub(crate) fn native_is_auto_accept_tp_request(
        &self,
        user_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        Ok(self.auto_accept(RlvRestrictionType::AcceptTpRequest, nullable(user_id)))
    }

    fn auto_accept(&self, kind: RlvRestrictionType, user_id: Option<Guid>) -> bool {
        self.restrictions(kind).iter().any(|restriction| {
            restriction.values().is_empty()
                || matches!(restriction.values(), [RlvValue::Uuid(value)] if Some(*value) == user_id)
        })
    }

    pub(crate) fn native_can_start_im(&self, user_id: Guid) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            Some(user_id),
            None,
            RlvRestrictionType::StartIm,
            None,
            Some(RlvRestrictionType::StartImTo),
        ))
    }

    pub(crate) fn native_can_send_im(
        &self,
        _message: String,
        user_id: Option<Option<Guid>>,
        group_name: Option<String>,
    ) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            group_name.as_deref(),
            RlvRestrictionType::SendIm,
            Some(RlvRestrictionType::SendImSec),
            Some(RlvRestrictionType::SendImTo),
        ))
    }

    pub(crate) fn native_can_receive_im(
        &self,
        _message: String,
        user_id: Option<Option<Guid>>,
        group_name: Option<String>,
    ) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            group_name.as_deref(),
            RlvRestrictionType::RecvIm,
            Some(RlvRestrictionType::RecvImSec),
            Some(RlvRestrictionType::RecvImFrom),
        ))
    }

    pub(crate) fn native_can_receive_chat(
        &self,
        message: String,
        user_id: Guid,
    ) -> Result<bool, Error> {
        let (normal, secure, explicit) = if starts_with_ignore_case(&message, "/me ") {
            (
                RlvRestrictionType::RecvEmote,
                RlvRestrictionType::RecvEmoteSec,
                RlvRestrictionType::RecvEmoteFrom,
            )
        } else {
            (
                RlvRestrictionType::RecvChat,
                RlvRestrictionType::RecvChatSec,
                RlvRestrictionType::RecvChatFrom,
            )
        };
        Ok(self.secure_allowed(Some(user_id), None, normal, Some(secure), Some(explicit)))
    }

    pub(crate) fn native_try_get_redir_chat_channels(&self, channels: &mut Vec<i32>) -> bool {
        *channels = self.distinct_channels(RlvRestrictionType::RedirChat);
        !channels.is_empty()
    }

    pub(crate) fn native_try_get_redir_emote_channels(&self, channels: &mut Vec<i32>) -> bool {
        *channels = self.distinct_channels(RlvRestrictionType::RedirEmote);
        !channels.is_empty()
    }

    fn distinct_channels(&self, kind: RlvRestrictionType) -> Vec<i32> {
        let mut seen = HashSet::new();
        self.restrictions(kind)
            .iter()
            .filter_map(|restriction| match restriction.values() {
                [RlvValue::Integer(value)] if seen.insert(*value) => Some(*value),
                _ => None,
            })
            .collect()
    }

    pub(crate) fn native_can_chat(&self, channel: i32, message: String) -> Result<bool, Error> {
        if channel == 0 {
            if starts_with_ignore_case(&message, "/me ") && !self.absent(RlvRestrictionType::Emote)
            {
                return Ok(false);
            }
            if !self.absent(RlvRestrictionType::SendChat) {
                if message.chars().any(|value| "()\"-*=_^".contains(value)) {
                    return Ok(false);
                }
                return Ok(message.starts_with('/'));
            }
            return Ok(true);
        }
        Ok(self.can_chat_private(channel))
    }

    fn can_chat_private(&self, channel: i32) -> bool {
        if self
            .restrictions(RlvRestrictionType::SendChannelExcept)
            .iter()
            .any(|restriction| {
                matches!(restriction.values(), [RlvValue::Integer(value)] if *value == channel)
            })
        {
            return false;
        }
        let normal = self.restrictions(RlvRestrictionType::SendChannel);
        let exceptions: Vec<_> = normal
            .iter()
            .filter(|restriction| {
                restriction.is_exception()
                    && matches!(restriction.values(), [RlvValue::Integer(value)] if *value == channel)
            })
            .collect();
        for secure in self.restrictions(RlvRestrictionType::SendChannelSec) {
            if !exceptions
                .iter()
                .any(|exception| exception.sender() == secure.sender())
            {
                return false;
            }
        }
        let permissive = self.is_permissive_value();
        for restriction in normal
            .iter()
            .filter(|restriction| !restriction.is_exception() && restriction.values().is_empty())
        {
            if !exceptions
                .iter()
                .any(|exception| permissive || exception.sender() == restriction.sender())
            {
                return false;
            }
        }
        true
    }

    pub(crate) fn native_can_show_names(
        &self,
        user_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            None,
            RlvRestrictionType::ShowNames,
            Some(RlvRestrictionType::ShowNamesSec),
            None,
        ))
    }

    pub(crate) fn native_can_show_name_tags(
        &self,
        user_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            None,
            RlvRestrictionType::ShowNameTags,
            None,
            None,
        ))
    }

    pub(crate) fn native_can_share(&self, user_id: Option<Option<Guid>>) -> Result<bool, Error> {
        Ok(self.secure_allowed(
            nullable(user_id),
            None,
            RlvRestrictionType::Share,
            Some(RlvRestrictionType::ShareSec),
            None,
        ))
    }

    pub(crate) fn native_is_auto_deny_permissions(&self) -> Result<bool, Error> {
        Ok(!self.absent(RlvRestrictionType::DenyPermission))
    }

    pub(crate) fn native_is_auto_accept_permissions(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::DenyPermission)
            && !self.absent(RlvRestrictionType::AcceptPermission))
    }

    pub(crate) fn native_is_permissive(&self) -> Result<bool, Error> {
        Ok(self.is_permissive_value())
    }

    pub(crate) fn native_can_rez(&self) -> Result<bool, Error> {
        Ok(self.absent(RlvRestrictionType::Interact) && self.absent(RlvRestrictionType::Rez))
    }

    pub(crate) fn native_can_edit(
        &self,
        object_location: RlvPermissionsServiceObjectLocation,
        object_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        if !self.absent(RlvRestrictionType::Interact)
            || !self.secure_allowed(
                nullable(object_id),
                None,
                RlvRestrictionType::Edit,
                None,
                Some(RlvRestrictionType::EditObj),
            )
        {
            return Ok(false);
        }
        Ok(match object_location {
            RlvPermissionsServiceObjectLocation::RezzedInWorld => {
                self.absent(RlvRestrictionType::EditWorld)
            }
            RlvPermissionsServiceObjectLocation::Attached => {
                self.absent(RlvRestrictionType::EditAttach)
            }
            RlvPermissionsServiceObjectLocation::Hud => true,
        })
    }

    pub(crate) fn native_can_touch(
        &self,
        location: RlvPermissionsServiceTouchLocation,
        prim_id: Guid,
        user_id: Option<Option<Guid>>,
        distance: Option<Option<f32>>,
    ) -> Result<bool, Error> {
        if let Some(distance) = nullable(distance)
            && let Some(maximum) = self
                .restrictions
                .float_values(RlvRestrictionType::FarTouch)
                .into_iter()
                .reduce(f32::min)
            && distance > maximum
        {
            return Ok(false);
        }
        if !self.absent(RlvRestrictionType::Interact) {
            return Ok(false);
        }
        if self
            .restrictions(RlvRestrictionType::TouchMe)
            .iter()
            .any(|restriction| restriction.sender() == prim_id)
        {
            return Ok(true);
        }
        if self
            .restrictions(RlvRestrictionType::TouchThis)
            .iter()
            .any(|restriction| {
                matches!(restriction.values(), [RlvValue::Uuid(value)] if *value == prim_id)
            })
        {
            return Ok(false);
        }
        if location != RlvPermissionsServiceTouchLocation::Hud
            && !self.absent(RlvRestrictionType::TouchAll)
        {
            return Ok(false);
        }
        match location {
            RlvPermissionsServiceTouchLocation::RezzedInWorld => {
                let restrictions = self.restrictions(RlvRestrictionType::TouchWorld);
                let exception = restrictions.iter().any(|restriction| {
                    restriction.is_exception()
                        && matches!(restriction.values(), [RlvValue::Uuid(value)] if *value == prim_id)
                });
                if !exception
                    && restrictions
                        .iter()
                        .any(|restriction| restriction.values().is_empty())
                {
                    return Ok(false);
                }
            }
            RlvPermissionsServiceTouchLocation::AttachedSelf => {
                if !self.can_touch_attachment(true, nullable(user_id)) {
                    return Ok(false);
                }
            }
            RlvPermissionsServiceTouchLocation::AttachedOther => {
                if !self.can_touch_attachment(false, nullable(user_id)) {
                    return Ok(false);
                }
            }
            RlvPermissionsServiceTouchLocation::Hud => {
                if self
                    .restrictions(RlvRestrictionType::TouchHud)
                    .iter()
                    .any(|restriction| {
                        restriction.values().is_empty()
                            || matches!(restriction.values(), [RlvValue::Uuid(value)] if *value == prim_id)
                    })
                {
                    return Ok(false);
                }
            }
        }
        Ok(true)
    }

    fn can_touch_attachment(&self, attached_to_self: bool, user_id: Option<Guid>) -> bool {
        if !self.absent(RlvRestrictionType::TouchAttach) {
            return false;
        }
        if attached_to_self {
            return self.absent(RlvRestrictionType::TouchAttachSelf);
        }
        !self
            .restrictions(RlvRestrictionType::TouchAttachOther)
            .iter()
            .any(|restriction| {
                restriction.values().is_empty()
                    || matches!(restriction.values(), [RlvValue::Uuid(value)] if Some(*value) == user_id)
            })
    }

    pub(crate) fn native_can_show_hover_text(
        &self,
        location: RlvPermissionsServiceHoverTextLocation,
        object_id: Option<Option<Guid>>,
    ) -> Result<bool, Error> {
        if !self.absent(RlvRestrictionType::ShowHoverTextAll) {
            return Ok(false);
        }
        let object_id = nullable(object_id);
        if self
            .restrictions(RlvRestrictionType::ShowHoverText)
            .iter()
            .any(|restriction| {
                matches!(restriction.values(), [RlvValue::Uuid(value)] if Some(*value) == object_id)
            })
        {
            return Ok(false);
        }
        Ok(match location {
            RlvPermissionsServiceHoverTextLocation::Hud => {
                self.absent(RlvRestrictionType::ShowHoverTextHud)
            }
            RlvPermissionsServiceHoverTextLocation::World => {
                self.absent(RlvRestrictionType::ShowHoverTextWorld)
            }
        })
    }

    pub(crate) fn native_can_attach_with_item(
        &self,
        item: RlvInventoryItem,
        is_shared: bool,
    ) -> Result<bool, Error> {
        self.native_can_attach(
            mapped_nullable(item.folder_id_value()),
            is_shared,
            mapped_nullable(item.attached_to_value()),
            mapped_nullable(item.worn_on_value()),
        )
    }

    pub(crate) fn native_can_attach(
        &self,
        object_folder_id: Option<Option<Guid>>,
        is_shared: bool,
        attachment_point: Option<Option<RlvAttachmentPoint>>,
        wearable_type: Option<Option<RlvWearableType>>,
    ) -> Result<bool, Error> {
        let folder_id = nullable(object_folder_id);
        let attachment_point = nullable(attachment_point);
        let wearable_type = nullable(wearable_type);
        if wearable_type.is_some_and(|wearable| {
            self.restrictions(RlvRestrictionType::AddOutfit)
                .iter()
                .any(|restriction| {
                    restriction.values().is_empty()
                        || matches!(restriction.values(), [RlvValue::WearableType(value)] if *value == wearable)
                })
        }) || attachment_point.is_some_and(|point| {
            self.restrictions(RlvRestrictionType::AddAttach)
                .iter()
                .any(|restriction| {
                    restriction.values().is_empty()
                        || matches!(restriction.values(), [RlvValue::AttachmentPoint(value)] if *value == point)
                })
        }) {
            return Ok(false);
        }
        if is_shared {
            if !self.absent(RlvRestrictionType::SharedWear) {
                return Ok(false);
            }
            let Some(folder_id) = folder_id else {
                return Ok(false);
            };
            let mut locked = None;
            if self
                .restrictions
                .native_try_get_locked_folder(folder_id, &mut locked)
                && locked.is_some_and(|folder| !folder.native_can_attach())
            {
                return Ok(false);
            }
        } else if !self.absent(RlvRestrictionType::UnsharedWear) {
            return Ok(false);
        }
        Ok(true)
    }

    pub(crate) fn native_can_detach_with_item(
        &self,
        item: RlvInventoryItem,
    ) -> Result<bool, Error> {
        self.native_can_detach(
            mapped_nullable(Some(item.native_id())),
            mapped_nullable(item.attached_prim_id_value()),
            mapped_nullable(item.folder_id_value()),
            item.native_folder().is_some(),
            mapped_nullable(item.attached_to_value()),
            mapped_nullable(item.worn_on_value()),
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn native_can_detach(
        &self,
        _item_id: Option<Option<Guid>>,
        prim_id: Option<Option<Guid>>,
        folder_id: Option<Option<Guid>>,
        is_shared: bool,
        attachment_point: Option<Option<RlvAttachmentPoint>>,
        wearable_type: Option<Option<RlvWearableType>>,
    ) -> Result<bool, Error> {
        let prim_id = nullable(prim_id);
        let folder_id = nullable(folder_id);
        let attachment_point = nullable(attachment_point);
        let wearable_type = nullable(wearable_type);
        if wearable_type.is_some_and(|wearable| {
            self.restrictions(RlvRestrictionType::RemOutfit)
                .iter()
                .any(|restriction| {
                    restriction.values().is_empty()
                        || matches!(restriction.values(), [RlvValue::WearableType(value)] if *value == wearable)
                })
        }) || attachment_point.is_some_and(|point| {
            self.restrictions(RlvRestrictionType::RemAttach)
                .iter()
                .any(|restriction| {
                    restriction.values().is_empty()
                        || matches!(restriction.values(), [RlvValue::AttachmentPoint(value)] if *value == point)
                })
        }) {
            return Ok(false);
        }
        for restriction in self.restrictions(RlvRestrictionType::Detach) {
            match restriction.values() {
                [] if prim_id == Some(restriction.sender()) => return Ok(false),
                [RlvValue::AttachmentPoint(value)] if Some(*value) == attachment_point => {
                    return Ok(false);
                }
                _ => {}
            }
        }
        if is_shared {
            if !self.absent(RlvRestrictionType::SharedUnwear) {
                return Ok(false);
            }
            let Some(folder_id) = folder_id else {
                return Ok(false);
            };
            let mut locked = None;
            if self
                .restrictions
                .native_try_get_locked_folder(folder_id, &mut locked)
                && locked.is_some_and(|folder| !folder.native_can_detach())
            {
                return Ok(false);
            }
        } else if !self.absent(RlvRestrictionType::UnsharedUnwear) {
            return Ok(false);
        }
        Ok(true)
    }

    pub(crate) fn native_get_camera_restrictions(&self) -> Result<CameraRestrictions, Error> {
        Ok(CameraRestrictions::from_manager(&self.restrictions))
    }
}

fn restriction_matches_target(
    restriction: &RlvRestriction,
    user_id: Option<Guid>,
    group_name: Option<&str>,
) -> bool {
    match restriction.values() {
        [RlvValue::Uuid(value)] => Some(*value) == user_id,
        [RlvValue::String(value)] => {
            group_name.is_some_and(|group| value == "allgroups" || value == group)
        }
        _ => false,
    }
}
