//! Avatar inheritance, naming, and LLSD persistence.

use crate::{
    AgentManagerControlFlags, Animation, AppearanceFlags, AvatarAttachment, AvatarAvatarProperties,
    AvatarInterests, AvatarStatistics, Error, NameValue, NameValueValueType, Primitive,
};
use libremetaverse_structured_data::OSD;
use libremetaverse_types::{UUID, Vector3};
use std::collections::HashMap;

pub(crate) fn statistics_get_osd(value: &AvatarStatistics) -> Result<OSD, Error> {
    Ok(OSD::Map(HashMap::from([
        (
            "behavior_positive".into(),
            OSD::Integer(value.behavior_positive),
        ),
        (
            "behavior_negative".into(),
            OSD::Integer(value.behavior_negative),
        ),
        (
            "appearance_positive".into(),
            OSD::Integer(value.appearance_positive),
        ),
        (
            "appearance_negative".into(),
            OSD::Integer(value.appearance_negative),
        ),
        (
            "buildings_positive".into(),
            OSD::Integer(value.building_positive),
        ),
        (
            "buildings_negative".into(),
            OSD::Integer(value.building_negative),
        ),
        ("given_positive".into(), OSD::Integer(value.given_positive)),
        ("given_negative".into(), OSD::Integer(value.given_negative)),
    ])))
}

pub(crate) fn statistics_from_osd(value: OSD) -> Result<AvatarStatistics, Error> {
    let OSD::Map(map) = value else {
        return Err(Error::Argument);
    };
    let integer = |key: &str| map.get(key).ok_or(Error::Argument)?.as_integer();
    Ok(AvatarStatistics {
        appearance_negative: integer("appearance_negative")?,
        appearance_positive: integer("appearance_positive")?,
        // Retain the pinned C# decoder's observable assignment behavior.
        behavior_negative: 0,
        behavior_positive: integer("behavior_positive")?,
        building_negative: integer("buildings_negative")?,
        building_positive: integer("buildings_positive")?,
        given_negative: integer("given_negative")?,
        given_positive: integer("given_positive")?,
    })
}

#[derive(Clone)]
pub struct Avatar {
    pub base: Primitive,
    pub animations: Vec<Animation>,
    pub appearance_flags: AppearanceFlags,
    pub appearance_version: u8,
    pub attachments: Vec<AvatarAttachment>,
    pub cof_version: i32,
    pub control_flags: AgentManagerControlFlags,
    pub groups: Vec<UUID>,
    pub hover_height: Vector3,
    pub profile_interests: AvatarInterests,
    pub profile_properties: AvatarAvatarProperties,
    pub profile_statistics: AvatarStatistics,
    pub visual_parameters: Vec<u8>,
}

impl std::ops::Deref for Avatar {
    type Target = Primitive;

    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl std::ops::DerefMut for Avatar {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

impl Avatar {
    pub fn new() -> Result<Self, Error> {
        crate::visual_catalog::new_avatar()
    }

    pub fn decode_visual_params(&self) -> Result<HashMap<i32, f32>, Error> {
        Ok(crate::visual_catalog::decode_visual_params(
            &self.visual_parameters,
        ))
    }

    #[must_use]
    pub fn first_name(&self) -> String {
        self.name_value("FirstName")
    }

    #[must_use]
    pub fn last_name(&self) -> String {
        self.name_value("LastName")
    }

    #[must_use]
    pub fn name(&self) -> String {
        let first = self.first_name();
        let last = self.last_name();
        if first.is_empty() || last.is_empty() {
            String::new()
        } else {
            format!("{first} {last}")
        }
    }

    #[must_use]
    pub fn group_name(&self) -> String {
        self.name_value("Title")
    }

    fn name_value(&self, name: &str) -> String {
        self.base
            .name_values
            .as_ref()
            .and_then(|values| {
                values
                    .iter()
                    .find(|value| value.name == name && value.type_ == NameValueValueType::String)
            })
            .and_then(|value| value.value.as_ref())
            .and_then(|value| match value {
                libremetaverse_types::compat::Object::String(value) => Some(value.clone()),
                _ => None,
            })
            .unwrap_or_default()
    }

    pub fn get_osd(&self) -> Result<OSD, Error> {
        let OSD::Map(mut map) = self.base.get_osd()? else {
            return Err(Error::InvalidOperation);
        };
        map.insert(
            "groups".into(),
            OSD::Array(self.groups.iter().copied().map(OSD::UUID).collect()),
        );
        map.insert(
            "profile_statistics".into(),
            self.profile_statistics.get_osd()?,
        );
        map.insert(
            "profile_properties".into(),
            self.profile_properties.get_osd()?,
        );
        map.insert("profile_interest".into(), self.profile_interests.get_osd()?);
        map.insert("control_flags".into(), OSD::Integer(self.control_flags.0));
        map.insert(
            "visual_parameters".into(),
            OSD::Array(
                self.visual_parameters
                    .iter()
                    .map(|value| OSD::Integer(i32::from(*value)))
                    .collect(),
            ),
        );
        map.insert("first_name".into(), OSD::String(self.first_name()));
        map.insert("last_name".into(), OSD::String(self.last_name()));
        map.insert("group_name".into(), OSD::String(self.group_name()));
        Ok(OSD::Map(map))
    }

    pub fn from_osd(osd: OSD) -> Result<Self, Error> {
        let OSD::Map(map) = &osd else {
            return Err(Error::Argument);
        };
        let mut avatar = Self::new()?;
        avatar.base = Primitive::from_osd(osd.clone())?;
        avatar.groups = osd_array(map.get("groups"))
            .iter()
            .filter_map(|value| value.as_uuid().ok())
            .collect();
        if let Some(value) = map.get("profile_statistics") {
            avatar.profile_statistics = AvatarStatistics::from_osd(value.clone())?;
        }
        if let Some(value) = map.get("profile_properties") {
            avatar.profile_properties = AvatarAvatarProperties::from_osd(value.clone())?;
        }
        if let Some(value) = map.get("profile_interest") {
            avatar.profile_interests = AvatarInterests::from_osd(value.clone())?;
        }
        avatar.control_flags =
            AgentManagerControlFlags(map.get("control_flags").map_or(Ok(0), OSD::as_integer)?);
        avatar.visual_parameters = osd_array(map.get("visual_parameters"))
            .iter()
            .map(|value| value.as_integer().map(|number| number.clamp(0, 255) as u8))
            .collect::<Result<_, _>>()?;
        avatar.base.name_values = Some(vec![
            string_name_value("FirstName", map.get("first_name"))?,
            string_name_value("LastName", map.get("last_name"))?,
            string_name_value("Title", map.get("group_name"))?,
        ]);
        Ok(avatar)
    }
}

fn osd_array(value: Option<&OSD>) -> &[OSD] {
    match value {
        Some(OSD::Array(values)) => values,
        _ => &[],
    }
}

fn string_name_value(name: &str, value: Option<&OSD>) -> Result<NameValue, Error> {
    NameValue::new_with_string_value_type_class_type_sendto_type_object(
        name.into(),
        NameValueValueType::String,
        crate::NameValueClassType::ReadOnly,
        crate::NameValueSendtoType::Sim,
        libremetaverse_types::compat::Object::String(
            value.map_or(Ok(String::new()), OSD::as_string)?,
        ),
    )
}
