//! Capability-backed interest-list mode control.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructor is fallible.

use crate::{Error, GridClient, Simulator};
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::compat::CancellationToken;
use std::sync::{Arc, RwLock};

#[derive(Clone, Debug)]
pub struct InterestListMessage {
    mode: String,
    interest_list_mode: crate::messages::linden::InterestListMode,
}
impl InterestListMessage {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            mode: "default".into(),
            interest_list_mode: crate::messages::linden::InterestListMode::Default,
        })
    }
    pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        let mode = map
            .get("mode")
            .map(|value| value.as_string())
            .transpose()?
            .unwrap_or_else(|| "default".into());
        self.set_mode(mode);
        Ok(())
    }
    pub fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_dictionary(std::collections::HashMap::from([(
            "mode".into(),
            OSD::String(self.mode.clone()),
        )]))
    }
    pub fn interest_list_mode(&self) -> crate::messages::linden::InterestListMode {
        self.interest_list_mode
    }
    pub fn set_interest_list_mode(&mut self, value: crate::messages::linden::InterestListMode) {
        self.interest_list_mode = value;
        self.mode = mode_name(value).into();
    }
    pub fn mode(&self) -> String {
        self.mode.clone()
    }
    pub fn set_mode(&mut self, value: String) {
        self.interest_list_mode = if matches!(
            value.to_ascii_lowercase().as_str(),
            "360" | "panoramic360" | "panoramic_360"
        ) {
            crate::messages::linden::InterestListMode::Panoramic360
        } else {
            crate::messages::linden::InterestListMode::Default
        };
        self.mode = mode_name(self.interest_list_mode).into();
    }
}
impl crate::interfaces::IMessage for InterestListMessage {
    fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
        InterestListMessage::deserialize(self, map)
    }
    fn serialize(&self) -> Result<OSDMap, Error> {
        InterestListMessage::serialize(self)
    }
}
fn mode_name(mode: crate::messages::linden::InterestListMode) -> &'static str {
    match mode {
        crate::messages::linden::InterestListMode::Default => "default",
        crate::messages::linden::InterestListMode::Panoramic360 => "360",
    }
}

pub(crate) struct InterestListManagerInner {
    client: Arc<GridClient>,
    current_mode: RwLock<crate::messages::linden::InterestListMode>,
}
#[derive(Clone)]
pub struct InterestListManager {
    pub(crate) inner: Arc<InterestListManagerInner>,
}
impl InterestListManager {
    pub fn new(client: GridClient) -> Result<Self, Error> {
        Self::native_new(Arc::new(client))
    }
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        if let Some(inner) = client.cached_interest_list_manager_inner() {
            return Ok(Self { inner });
        }
        let inner = Arc::new(InterestListManagerInner {
            client: Arc::clone(&client),
            current_mode: RwLock::new(crate::messages::linden::InterestListMode::Default),
        });
        Ok(Self {
            inner: client.install_interest_list_manager_inner(inner),
        })
    }
    pub fn dispose(&self) -> Result<(), Error> {
        Ok(())
    }
    pub fn current_mode(&self) -> crate::messages::linden::InterestListMode {
        *self
            .inner
            .current_mode
            .read()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
    }
    pub async fn reset(&self, cancellation_token: Option<CancellationToken>) -> Result<(), Error> {
        self.set_mode(
            crate::messages::linden::InterestListMode::Default,
            cancellation_token,
        )
        .await
    }
    pub async fn set_mode(
        &self,
        mode: crate::messages::linden::InterestListMode,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<(), Error> {
        let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
        token.throw_if_cancellation_requested()?;
        let simulations = self.inner.client.network().simulators.snapshot();
        let mut accepted = false;
        for simulator in simulations {
            if self
                .set_mode_on_sim(simulator, mode, Some(token.clone()))
                .await?
            {
                accepted = true;
            }
        }
        if accepted || self.inner.client.network().simulators.is_empty() {
            *self
                .inner
                .current_mode
                .write()
                .unwrap_or_else(std::sync::PoisonError::into_inner) = mode;
        }
        Ok(())
    }
    pub async fn reset_on_sim(
        &self,
        simulator: Simulator,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        self.set_mode_on_sim(
            simulator,
            crate::messages::linden::InterestListMode::Default,
            cancellation_token,
        )
        .await
    }
    pub async fn set_mode_on_sim(
        &self,
        simulator: Simulator,
        mode: crate::messages::linden::InterestListMode,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
        token.throw_if_cancellation_requested()?;
        let Some(caps) = simulator.native_caps() else {
            return Ok(false);
        };
        let Some(uri) = caps.capability_uri("InterestList".into())? else {
            return Ok(false);
        };
        let mut message = InterestListMessage::new()?;
        message.set_interest_list_mode(mode);
        let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(message.serialize()?.snapshot()))?;
        let (response, bytes) = self
            .inner
            .client
            .native_http_caps_client()
            .post_with_uri_string_bytes_cancellation_token_i_progress(
                uri,
                "application/llsd+xml".into(),
                body,
                token.clone(),
                None,
            )
            .await?;
        token.throw_if_cancellation_requested()?;
        if bytes.len() > 1024 * 1024 {
            return Err(Error::Argument);
        }
        let ok = (200..300).contains(&response.status_code);
        if ok {
            *self
                .inner
                .current_mode
                .write()
                .unwrap_or_else(std::sync::PoisonError::into_inner) = mode;
        }
        Ok(ok)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use libremetaverse_types::compat::CancellationTokenSource;
    #[test]
    fn message_round_trip() {
        let mut value = InterestListMessage::new().unwrap();
        value.set_interest_list_mode(crate::messages::linden::InterestListMode::Panoramic360);
        let mut copy = InterestListMessage::new().unwrap();
        copy.deserialize(value.serialize().unwrap()).unwrap();
        assert_eq!(copy.mode(), "360");
        assert_eq!(
            copy.interest_list_mode(),
            crate::messages::linden::InterestListMode::Panoramic360
        );
    }
    #[tokio::test]
    async fn cancelled_mode_change_does_not_commit() {
        let client = GridClient::new().unwrap();
        let manager = InterestListManager::new(client).unwrap();
        let source = CancellationTokenSource::new();
        source.cancel();
        assert_eq!(
            manager
                .set_mode(
                    crate::messages::linden::InterestListMode::Panoramic360,
                    Some(source.token())
                )
                .await,
            Err(Error::Cancelled)
        );
        assert_eq!(
            manager.current_mode(),
            crate::messages::linden::InterestListMode::Default
        );
    }
}
