//! Native LLSD messages owned by the non-movement agent service.

#![allow(clippy::inherent_to_string)] // Public names mirror C# ToString mappings.
#![allow(clippy::cast_possible_truncation)] // C# double-to-float mappings truncate identically.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API.
#![allow(clippy::needless_pass_by_value)] // Owned parameters mirror mapped C# signatures.
#![allow(clippy::struct_excessive_bools)] // Chat member flags are independent protocol fields.

use crate::{Error, interfaces::IMessage, messages::linden::ExperienceFlags};
use libremetaverse_structured_data::{OSD, OSDMap};
use libremetaverse_types::UUID;
use libremetaverse_types::Vector3;
use libremetaverse_types::compat::Object;
use std::collections::HashMap;

pub(crate) fn avatar_properties_get_osd(
    value: &crate::AvatarAvatarProperties,
) -> Result<OSD, Error> {
    Ok(OSD::Map(HashMap::from([
        (
            "first_life_text".into(),
            OSD::String(value.first_life_text.clone()),
        ),
        ("first_life_image".into(), OSD::UUID(value.first_life_image)),
        ("partner".into(), OSD::UUID(value.partner)),
        ("about_text".into(), OSD::String(value.about_text.clone())),
        ("born_on".into(), OSD::String(value.born_on.clone())),
        (
            "charter_member".into(),
            OSD::String(value.charter_member.clone()),
        ),
        ("profile_image".into(), OSD::UUID(value.profile_image)),
        (
            "flags".into(),
            OSD::Integer(i32::try_from(value.flags.0).map_err(|_| Error::Argument)?),
        ),
        ("profile_url".into(), OSD::String(value.profile_url.clone())),
    ])))
}

pub(crate) fn avatar_properties_from_osd(
    value: OSD,
) -> Result<crate::AvatarAvatarProperties, Error> {
    let OSD::Map(map) = value else {
        return Err(Error::Argument);
    };
    let string = |key: &str| map.get(key).map_or(Ok(String::new()), OSD::as_string);
    let uuid = |key: &str| map.get(key).map_or(Ok(UUID::zero()), OSD::as_uuid);
    Ok(crate::AvatarAvatarProperties {
        first_life_text: string("first_life_text")?,
        first_life_image: uuid("first_life_image")?,
        partner: uuid("partner")?,
        about_text: string("about_text")?,
        born_on: string("born_on")?,
        // Preserve the reference parser's historical `chart_member` spelling,
        // while accepting the serializer's corrected key for interoperability.
        charter_member: if map.contains_key("chart_member") {
            string("chart_member")?
        } else {
            string("charter_member")?
        },
        profile_image: uuid("profile_image")?,
        flags: crate::ProfileFlags(map.get("flags").map_or(Ok(0), OSD::as_u_integer)?),
        profile_url: string("profile_url")?,
    })
}

pub(crate) fn avatar_interests_get_osd(value: &crate::AvatarInterests) -> Result<OSD, Error> {
    Ok(OSD::Map(HashMap::from([
        (
            "languages_text".into(),
            OSD::String(value.languages_text.clone()),
        ),
        (
            "skills_mask".into(),
            OSD::Integer(i32::try_from(value.skills_mask).map_err(|_| Error::Argument)?),
        ),
        ("skills_text".into(), OSD::String(value.skills_text.clone())),
        (
            "want_to_mask".into(),
            OSD::Integer(i32::try_from(value.want_to_mask).map_err(|_| Error::Argument)?),
        ),
        (
            "want_to_text".into(),
            OSD::String(value.want_to_text.clone()),
        ),
    ])))
}

pub(crate) fn avatar_interests_from_osd(value: OSD) -> Result<crate::AvatarInterests, Error> {
    let OSD::Map(map) = value else {
        return Err(Error::Argument);
    };
    let string = |key: &str| map.get(key).map_or(Ok(String::new()), OSD::as_string);
    let integer = |key: &str| map.get(key).map_or(Ok(0), OSD::as_u_integer);
    Ok(crate::AvatarInterests {
        languages_text: string("languages_text")?,
        skills_mask: integer("skills_mask")?,
        skills_text: string("skills_text")?,
        want_to_mask: integer("want_to_mask")?,
        want_to_text: string("want_to_text")?,
    })
}

fn integer(map: &OSDMap, key: &str) -> Result<i32, Error> {
    map.get(key).map_or(Ok(0), |value| value.as_integer())
}

#[derive(Clone)]
pub struct ChatEventArgs {
    simulator: crate::Simulator,
    message: String,
    audible_level: crate::ChatAudibleLevel,
    type_: crate::ChatType,
    source_type: crate::ChatSourceType,
    from_name: String,
    source_id: UUID,
    owner_id: UUID,
    position: Vector3,
}

impl ChatEventArgs {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        simulator: crate::Simulator,
        message: String,
        audible: crate::ChatAudibleLevel,
        type_: crate::ChatType,
        source_type: crate::ChatSourceType,
        from_name: String,
        source_id: UUID,
        owner_id: UUID,
        position: Vector3,
    ) -> Result<Self, Error> {
        Ok(Self {
            simulator,
            message,
            audible_level: audible,
            type_,
            source_type,
            from_name,
            source_id,
            owner_id,
            position,
        })
    }

    #[must_use]
    pub fn simulator(&self) -> crate::Simulator {
        self.simulator.clone()
    }
    #[must_use]
    pub fn message(&self) -> String {
        self.message.clone()
    }
    #[must_use]
    pub const fn audible_level(&self) -> crate::ChatAudibleLevel {
        self.audible_level
    }
    #[must_use]
    pub const fn type_(&self) -> crate::ChatType {
        self.type_
    }
    #[must_use]
    pub const fn source_type(&self) -> crate::ChatSourceType {
        self.source_type
    }
    #[must_use]
    pub fn from_name(&self) -> String {
        self.from_name.clone()
    }
    #[must_use]
    pub const fn source_id(&self) -> UUID {
        self.source_id
    }
    #[must_use]
    pub const fn owner_id(&self) -> UUID {
        self.owner_id
    }
    #[must_use]
    pub const fn position(&self) -> Vector3 {
        self.position
    }

    #[must_use]
    pub fn to_string(&self) -> String {
        format!(
            "[ChatEvent: Sim={}, Message={}, AudibleLevel={:?}, Type={:?}, SourceType={:?}, FromName={}, SourceID={}, Position={}, OwnerID={}]",
            self.simulator.native_to_string(),
            self.message,
            self.audible_level,
            self.type_,
            self.source_type,
            self.from_name,
            self.source_id,
            self.position.to_string(),
            self.owner_id
        )
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AgentDataReplyEventArgs {
    first_name: String,
    last_name: String,
    active_group_id: UUID,
    group_title: String,
    group_powers: crate::GroupPowers,
    group_name: String,
}

impl AgentDataReplyEventArgs {
    pub const fn new(
        first_name: String,
        last_name: String,
        active_group_id: UUID,
        group_title: String,
        group_powers: crate::GroupPowers,
        group_name: String,
    ) -> Result<Self, Error> {
        Ok(Self {
            first_name,
            last_name,
            active_group_id,
            group_title,
            group_powers,
            group_name,
        })
    }

    #[must_use]
    pub fn first_name(&self) -> String {
        self.first_name.clone()
    }
    #[must_use]
    pub fn last_name(&self) -> String {
        self.last_name.clone()
    }
    #[must_use]
    pub const fn active_group_id(&self) -> UUID {
        self.active_group_id
    }
    #[must_use]
    pub fn group_title(&self) -> String {
        self.group_title.clone()
    }
    #[must_use]
    pub const fn group_powers(&self) -> crate::GroupPowers {
        self.group_powers
    }
    #[must_use]
    pub fn group_name(&self) -> String {
        self.group_name.clone()
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AnimationsChangedEventArgs {
    animations: HashMap<UUID, i32>,
}

impl Clone for crate::messages::linden::NavMeshStatusUpdateMessage {
    fn clone(&self) -> Self {
        Self {
            raw_data: self.raw_data.clone(),
            region_id: self.region_id,
            status: self.status.clone(),
            version: self.version,
        }
    }
}

#[derive(Clone)]
pub struct NavMeshStatusUpdateEventArgs {
    message: crate::messages::linden::NavMeshStatusUpdateMessage,
    simulator: crate::Simulator,
}

impl NavMeshStatusUpdateEventArgs {
    pub const fn new(
        message: crate::messages::linden::NavMeshStatusUpdateMessage,
        simulator: crate::Simulator,
    ) -> Result<Self, Error> {
        Ok(Self { message, simulator })
    }

    #[must_use]
    pub fn message(&self) -> crate::messages::linden::NavMeshStatusUpdateMessage {
        self.message.clone()
    }

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

impl AnimationsChangedEventArgs {
    pub fn new(animations: HashMap<UUID, i32>) -> Result<Self, Error> {
        Ok(Self { animations })
    }

    #[must_use]
    pub fn animations(&self) -> HashMap<UUID, i32> {
        self.animations.clone()
    }
}

/// A participant and their current moderation state in a group-chat session.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct ChatSessionMember {
    pub avatar_key: UUID,
    pub can_voice_chat: bool,
    pub is_moderator: bool,
    pub mute_text: bool,
    pub mute_voice: bool,
}

impl ChatSessionMember {
    #[must_use]
    pub fn equals_with_chat_session_member(&self, other: Self) -> bool {
        self.avatar_key == other.avatar_key
            && self.can_voice_chat == other.can_voice_chat
            && self.is_moderator == other.is_moderator
            && self.mute_text == other.mute_text
            && self.mute_voice == other.mute_voice
    }

    /// `System.Object` cannot carry arbitrary boxed compatibility structs, so
    /// this overload can only reject the supported primitive object variants.
    #[must_use]
    pub fn equals_with_object(&self, _obj: Option<Object>) -> bool {
        false
    }

    /// Matches the unchecked C# hash composition used by `ChatSessionMember`.
    #[must_use]
    pub fn get_hash_code(&self) -> i32 {
        let mut hash = self.avatar_key.get_hash_code();
        for value in [
            self.can_voice_chat,
            self.is_moderator,
            self.mute_text,
            self.mute_voice,
        ] {
            hash = hash.wrapping_mul(397) ^ i32::from(value);
        }
        hash
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChatSessionMemberAddedEventArgs {
    session_id: UUID,
    agent_id: UUID,
}

impl ChatSessionMemberAddedEventArgs {
    pub const fn new(session_id: UUID, agent_id: UUID) -> Result<Self, Error> {
        Ok(Self {
            session_id,
            agent_id,
        })
    }

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

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

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChatSessionMemberLeftEventArgs {
    session_id: UUID,
    agent_id: UUID,
}

impl ChatSessionMemberLeftEventArgs {
    pub const fn new(session_id: UUID, agent_id: UUID) -> Result<Self, Error> {
        Ok(Self {
            session_id,
            agent_id,
        })
    }

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

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

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GroupChatJoinedEventArgs {
    session_id: UUID,
    session_name: String,
    tmp_session_id: UUID,
    success: bool,
}

impl GroupChatJoinedEventArgs {
    pub const fn new(
        group_chat_session_id: UUID,
        session_name: String,
        tmp_session_id: UUID,
        success: bool,
    ) -> Result<Self, Error> {
        Ok(Self {
            session_id: group_chat_session_id,
            session_name,
            tmp_session_id,
            success,
        })
    }

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

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

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

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

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SetDisplayNameReplyEventArgs {
    status: i32,
    reason: String,
    display_name: crate::AgentDisplayName,
}

impl SetDisplayNameReplyEventArgs {
    pub const fn new(
        status: i32,
        reason: String,
        display_name: crate::AgentDisplayName,
    ) -> Result<Self, Error> {
        Ok(Self {
            status,
            reason,
            display_name,
        })
    }

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

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

    #[must_use]
    pub fn display_name(&self) -> crate::AgentDisplayName {
        self.display_name.clone()
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BalanceEventArgs {
    balance: i32,
}

impl BalanceEventArgs {
    pub const fn new(balance: i32) -> Result<Self, Error> {
        Ok(Self { balance })
    }
    #[must_use]
    pub const fn balance(&self) -> i32 {
        self.balance
    }
}

impl Clone for crate::TransactionInfo {
    fn clone(&self) -> Self {
        Self {
            amount: self.amount,
            dest_id: self.dest_id,
            is_dest_group: self.is_dest_group,
            is_source_group: self.is_source_group,
            item_description: self.item_description.clone(),
            source_id: self.source_id,
            transaction_type: self.transaction_type,
        }
    }
}

#[derive(Clone)]
pub struct MoneyBalanceReplyEventArgs {
    transaction_id: UUID,
    success: bool,
    balance: i32,
    meters_credit: i32,
    meters_committed: i32,
    description: String,
    transaction_info: crate::TransactionInfo,
}

impl MoneyBalanceReplyEventArgs {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        transaction_id: UUID,
        transaction_success: bool,
        balance: i32,
        meters_credit: i32,
        meters_committed: i32,
        description: String,
        transaction_info: crate::TransactionInfo,
    ) -> Result<Self, Error> {
        Ok(Self {
            transaction_id,
            success: transaction_success,
            balance,
            meters_credit,
            meters_committed,
            description,
            transaction_info,
        })
    }

    #[must_use]
    pub const fn transaction_id(&self) -> UUID {
        self.transaction_id
    }
    #[must_use]
    pub const fn success(&self) -> bool {
        self.success
    }
    #[must_use]
    pub const fn balance(&self) -> i32 {
        self.balance
    }
    #[must_use]
    pub const fn meters_credit(&self) -> i32 {
        self.meters_credit
    }
    #[must_use]
    pub const fn meters_committed(&self) -> i32 {
        self.meters_committed
    }
    #[must_use]
    pub fn description(&self) -> String {
        self.description.clone()
    }
    #[must_use]
    pub fn transaction_info(&self) -> crate::TransactionInfo {
        self.transaction_info.clone()
    }
}

fn string(map: &OSDMap, key: &str) -> Result<String, Error> {
    map.get(key)
        .map_or(Ok(String::new()), |value| value.as_string())
}

fn uuid(map: &OSDMap, key: &str) -> Result<UUID, Error> {
    map.get(key)
        .map_or(Ok(UUID::zero()), |value| value.as_uuid())
}

fn empty_map() -> OSDMap {
    match OSDMap::new_with_dictionary(HashMap::new()) {
        Ok(map) => map,
        Err(_) => unreachable!("an empty OSD map is always valid"),
    }
}

fn decode_ids(map: &OSDMap, key: &str) -> Result<Vec<UUID>, Error> {
    let Some(OSD::Array(values)) = map.get(key) else {
        return Ok(Vec::new());
    };
    values.iter().map(OSD::as_uuid).collect()
}

fn encode_ids(values: &[UUID]) -> OSD {
    OSD::Array(values.iter().copied().map(OSD::UUID).collect())
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ExperienceListMessage {
    pub experience_i_ds: Vec<UUID>,
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.experience_i_ds = decode_ids(&map, "experience_ids")?;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_dictionary(HashMap::from([(
            "experience_ids".into(),
            encode_ids(&self.experience_i_ds),
        )]))
    }
}

impl IMessage for ExperienceListMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ExperiencePreferencesMessage {
    pub allowed: Vec<UUID>,
    pub blocked: Vec<UUID>,
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.allowed = decode_ids(&map, "experiences")?;
        self.blocked = decode_ids(&map, "blocked")?;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_dictionary(HashMap::from([
            ("experiences".into(), encode_ids(&self.allowed)),
            ("blocked".into(), encode_ids(&self.blocked)),
        ]))
    }
}

impl IMessage for ExperiencePreferencesMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RegionExperiencesMessage {
    pub allowed: Vec<UUID>,
    pub blocked: Vec<UUID>,
    pub default: UUID,
    pub trusted: Vec<UUID>,
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.blocked = decode_ids(&map, "blocked")?;
        self.trusted = decode_ids(&map, "trusted")?;
        self.allowed = decode_ids(&map, "allowed")?;
        self.default = map
            .get("default")
            .map_or(Ok(UUID::zero()), |value| value.as_uuid())?;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_dictionary(HashMap::from([
            ("blocked".into(), encode_ids(&self.blocked)),
            ("trusted".into(), encode_ids(&self.trusted)),
            ("allowed".into(), encode_ids(&self.allowed)),
            ("default".into(), OSD::UUID(self.default)),
        ]))
    }
}

impl IMessage for RegionExperiencesMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

#[derive(Clone)]
pub struct AgentPreferencesMessage {
    pub hover_height: f32,
    pub raw_data: OSDMap,
}

impl Default for AgentPreferencesMessage {
    fn default() -> Self {
        Self {
            hover_height: 0.0,
            raw_data: empty_map(),
        }
    }
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.hover_height = map
            .get("hover_height")
            .map_or(Ok(0.0), |value| value.as_real())? as f32;
        self.raw_data = map;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_dictionary(HashMap::from([(
            "hover_height".into(),
            OSD::Real(f64::from(self.hover_height)),
        )]))
    }
}

impl IMessage for AgentPreferencesMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AvatarRenderInfoMessageAvatarInfo {
    pub too_complex: bool,
    pub weight: i32,
}

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

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AvatarRenderInfoMessage {
    pub agents: HashMap<UUID, AvatarRenderInfoMessageAvatarInfo>,
    pub over_limit: i32,
    pub reporting_limit: i32,
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.agents.clear();
        if let Some(OSD::Map(agents)) = map.get("agents") {
            for (id, value) in agents {
                let Ok(id) = UUID::new_with_string(id.clone()) else {
                    continue;
                };
                let OSD::Map(info) = value else {
                    continue;
                };
                self.agents.insert(
                    id,
                    AvatarRenderInfoMessageAvatarInfo {
                        weight: info.get("weight").map_or(Ok(0), OSD::as_integer)?,
                        too_complex: info.get("tooComplex").map_or(Ok(false), OSD::as_boolean)?,
                    },
                );
            }
        }
        self.over_limit = integer(&map, "overlimit")?;
        self.reporting_limit = integer(&map, "reportinglimit")?;
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        let agents = self
            .agents
            .iter()
            .map(|(id, info)| {
                (
                    id.to_string(),
                    OSD::Map(HashMap::from([
                        ("weight".into(), OSD::Integer(info.weight)),
                        ("tooComplex".into(), OSD::Boolean(info.too_complex)),
                    ])),
                )
            })
            .collect();
        OSDMap::new_with_dictionary(HashMap::from([("agents".into(), OSD::Map(agents))]))
    }
}

impl IMessage for AvatarRenderInfoMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ViewerBenefitsMessageBenefitPackage {
    pub animated_object_limit: i32,
    pub animation_upload_cost: i32,
    pub attachment_limit: i32,
    pub create_group_cost: i32,
    pub group_membership_limit: i32,
    pub large_texture_upload_cost: Vec<i32>,
    pub picks_limit: i32,
    pub sound_upload_cost: i32,
    pub texture_upload_cost: i32,
}

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

    fn deserialize(map: &OSDMap) -> Result<Self, Error> {
        Ok(Self {
            animated_object_limit: integer(map, "animated_object_limit")?,
            animation_upload_cost: integer(map, "animation_upload_cost")?,
            attachment_limit: integer(map, "attachment_limit")?,
            create_group_cost: integer(map, "create_group_cost")?,
            group_membership_limit: integer(map, "group_membership_limit")?,
            large_texture_upload_cost: match map.get("large_texture_upload_cost") {
                Some(OSD::Array(values)) => values
                    .iter()
                    .map(OSD::as_integer)
                    .collect::<Result<_, _>>()?,
                _ => Vec::new(),
            },
            picks_limit: integer(map, "picks_limit")?,
            sound_upload_cost: integer(map, "sound_upload_cost")?,
            texture_upload_cost: integer(map, "texture_upload_cost")?,
        })
    }

    fn serialize(&self) -> OSD {
        let mut values = HashMap::from([
            (
                "animated_object_limit".into(),
                OSD::Integer(self.animated_object_limit),
            ),
            (
                "animation_upload_cost".into(),
                OSD::Integer(self.animation_upload_cost),
            ),
            (
                "attachment_limit".into(),
                OSD::Integer(self.attachment_limit),
            ),
            (
                "create_group_cost".into(),
                OSD::Integer(self.create_group_cost),
            ),
            (
                "group_membership_limit".into(),
                OSD::Integer(self.group_membership_limit),
            ),
            ("picks_limit".into(), OSD::Integer(self.picks_limit)),
            (
                "sound_upload_cost".into(),
                OSD::Integer(self.sound_upload_cost),
            ),
            (
                "texture_upload_cost".into(),
                OSD::Integer(self.texture_upload_cost),
            ),
        ]);
        if !self.large_texture_upload_cost.is_empty() {
            values.insert(
                "large_texture_upload_cost".into(),
                OSD::Array(
                    self.large_texture_upload_cost
                        .iter()
                        .copied()
                        .map(OSD::Integer)
                        .collect(),
                ),
            );
        }
        OSD::Map(values)
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ViewerBenefitsMessage {
    pub account_level_benefits: ViewerBenefitsMessageBenefitPackage,
    pub account_type: String,
    pub premium_packages: HashMap<String, ViewerBenefitsMessageBenefitPackage>,
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.account_type = string(&map, "account_type")?;
        self.account_level_benefits = match map.get("account_level_benefits") {
            Some(OSD::Map(values)) => ViewerBenefitsMessageBenefitPackage::deserialize(
                &OSDMap::new_with_dictionary(values.clone())?,
            )?,
            _ => ViewerBenefitsMessageBenefitPackage::default(),
        };
        self.premium_packages.clear();
        if let Some(OSD::Map(packages)) = map.get("premium_packages") {
            for (name, value) in packages {
                let OSD::Map(entry) = value else { continue };
                let Some(OSD::Map(benefits)) = entry.get("benefits") else {
                    continue;
                };
                let benefits = OSDMap::new_with_dictionary(benefits.clone())?;
                self.premium_packages.insert(
                    name.clone(),
                    ViewerBenefitsMessageBenefitPackage::deserialize(&benefits)?,
                );
            }
        }
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        let packages = self
            .premium_packages
            .iter()
            .map(|(name, benefits)| {
                (
                    name.clone(),
                    OSD::Map(HashMap::from([("benefits".into(), benefits.serialize())])),
                )
            })
            .collect();
        OSDMap::new_with_dictionary(HashMap::from([
            (
                "account_type".into(),
                OSD::String(self.account_type.clone()),
            ),
            (
                "account_level_benefits".into(),
                self.account_level_benefits.serialize(),
            ),
            ("premium_packages".into(), OSD::Map(packages)),
        ]))
    }
}

impl IMessage for ViewerBenefitsMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExperienceInfo {
    pub agent_id: UUID,
    pub description: String,
    pub experience_id: UUID,
    pub extended_metadata: String,
    pub flags: ExperienceFlags,
    pub group_id: UUID,
    pub image_id: UUID,
    pub marketplace: String,
    pub maturity: i32,
    pub name: String,
    pub public_id: UUID,
    pub quota: i32,
    pub slurl: String,
}

impl Default for ExperienceInfo {
    fn default() -> Self {
        Self {
            agent_id: UUID::zero(),
            description: String::new(),
            experience_id: UUID::zero(),
            extended_metadata: String::new(),
            flags: ExperienceFlags::NONE,
            group_id: UUID::zero(),
            image_id: UUID::zero(),
            marketplace: String::new(),
            maturity: 0,
            name: String::new(),
            public_id: UUID::zero(),
            quota: 0,
            slurl: String::new(),
        }
    }
}

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

    pub fn from_osd(map: OSDMap) -> Result<Self, Error> {
        Ok(Self {
            agent_id: uuid(&map, "agent_id")?,
            description: string(&map, "description")?,
            experience_id: uuid(&map, "experience_id")?,
            extended_metadata: string(&map, "extended_metadata")?,
            flags: ExperienceFlags(integer(&map, "properties")?),
            group_id: uuid(&map, "group_id")?,
            image_id: uuid(&map, "image_id")?,
            marketplace: string(&map, "marketplace")?,
            maturity: integer(&map, "maturity")?,
            name: string(&map, "name")?,
            public_id: uuid(&map, "public_id")?,
            quota: integer(&map, "quota")?,
            slurl: string(&map, "slurl")?,
        })
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_dictionary(HashMap::from([
            ("experience_id".into(), OSD::UUID(self.experience_id)),
            ("public_id".into(), OSD::UUID(self.public_id)),
            ("name".into(), OSD::String(self.name.clone())),
            ("description".into(), OSD::String(self.description.clone())),
            ("properties".into(), OSD::Integer(self.flags.0)),
            ("group_id".into(), OSD::UUID(self.group_id)),
            ("agent_id".into(), OSD::UUID(self.agent_id)),
            ("quota".into(), OSD::Integer(self.quota)),
            ("maturity".into(), OSD::Integer(self.maturity)),
            ("image_id".into(), OSD::UUID(self.image_id)),
            ("marketplace".into(), OSD::String(self.marketplace.clone())),
            (
                "extended_metadata".into(),
                OSD::String(self.extended_metadata.clone()),
            ),
            ("slurl".into(), OSD::String(self.slurl.clone())),
        ]))
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ExperienceInfoMessage {
    pub experiences: Vec<ExperienceInfo>,
}

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

    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        self.experiences.clear();
        if let Some(OSD::Array(values)) = map.get("experience_keys") {
            for value in values {
                if let OSD::Map(value) = value {
                    self.experiences
                        .push(ExperienceInfo::from_osd(OSDMap::new_with_dictionary(
                            value.clone(),
                        )?)?);
                }
            }
        }
        Ok(())
    }

    pub fn serialize(&self) -> Result<OSDMap, Error> {
        let values = self
            .experiences
            .iter()
            .map(|value| value.serialize().map(|map| OSD::Map(map.snapshot())))
            .collect::<Result<Vec<_>, _>>()?;
        OSDMap::new_with_dictionary(HashMap::from([(
            "experience_keys".into(),
            OSD::Array(values),
        )]))
    }
}

impl IMessage for ExperienceInfoMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        Self::deserialize(self, map)
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        Self::serialize(self)
    }
}

macro_rules! message_event_args {
    ($name:ident, $field:ident, $message:ty, $getter:ident) => {
        #[derive(Clone)]
        pub struct $name {
            $field: $message,
        }

        impl $name {
            pub fn new($field: $message) -> Result<Self, Error> {
                Ok(Self { $field })
            }

            #[must_use]
            pub fn $getter(&self) -> $message {
                self.$field.clone()
            }
        }
    };
}

message_event_args!(
    AgentExperiencesEventArgs,
    agent_experiences,
    ExperienceListMessage,
    agent_experiences
);
message_event_args!(
    ExperiencePreferencesEventArgs,
    preferences,
    ExperiencePreferencesMessage,
    preferences
);
message_event_args!(
    RegionExperiencesEventArgs,
    region_experiences,
    RegionExperiencesMessage,
    region_experiences
);
message_event_args!(
    ExperienceInfoEventArgs,
    experience_info,
    ExperienceInfoMessage,
    experience_info
);
message_event_args!(
    AgentPreferencesEventArgs,
    preferences,
    AgentPreferencesMessage,
    preferences
);
message_event_args!(
    AvatarRenderInfoEventArgs,
    render_info,
    AvatarRenderInfoMessage,
    render_info
);
message_event_args!(
    ViewerBenefitsEventArgs,
    benefits,
    ViewerBenefitsMessage,
    benefits
);

message_event_args!(
    ProductInfoEventArgs,
    product_info,
    crate::agent_manager::ProductInfoRequestMessage,
    product_info
);

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AgentAccessEventArgs {
    success: bool,
    new_level: String,
}

impl AgentAccessEventArgs {
    pub fn new(success: bool, new_level: String) -> Result<Self, Error> {
        Ok(Self { success, new_level })
    }

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

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