//! Native gesture asset parsing and step types.

#![allow(clippy::inherent_to_string)] // Public names mirror C# ToString mappings.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API.

use crate::{Error, assets::GestureStepType};
use libremetaverse_types::{AssetType, UUID};
use std::fmt::Write as _;

#[derive(Clone, Debug, PartialEq)]
pub enum GestureStep {
    Animation(GestureStepAnimation),
    Sound(GestureStepSound),
    Chat(GestureStepChat),
    Wait(GestureStepWait),
    EOF(GestureStepEOF),
}

impl GestureStep {
    #[must_use]
    pub const fn gesture_step_type(&self) -> GestureStepType {
        match self {
            Self::Animation(_) => GestureStepType::Animation,
            Self::Sound(_) => GestureStepType::Sound,
            Self::Chat(_) => GestureStepType::Chat,
            Self::Wait(_) => GestureStepType::Wait,
            Self::EOF(_) => GestureStepType::EOF,
        }
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GestureStepAnimation {
    pub animation_start: bool,
    pub id: UUID,
    pub name: String,
}

impl GestureStepAnimation {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            animation_start: true,
            ..Self::default()
        })
    }

    #[must_use]
    pub fn to_string(&self) -> String {
        format!(
            "{} animation: {}",
            if self.animation_start {
                "Start"
            } else {
                "Stop"
            },
            self.name
        )
    }

    #[must_use]
    pub const fn gesture_step_type(&self) -> GestureStepType {
        GestureStepType::Animation
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GestureStepSound {
    pub id: UUID,
    pub name: String,
}

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

    #[must_use]
    pub fn to_string(&self) -> String {
        format!("Sound: {}", self.name)
    }

    #[must_use]
    pub const fn gesture_step_type(&self) -> GestureStepType {
        GestureStepType::Sound
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GestureStepChat {
    pub text: String,
}

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

    #[must_use]
    pub fn to_string(&self) -> String {
        format!("Chat: {}", self.text)
    }

    #[must_use]
    pub const fn gesture_step_type(&self) -> GestureStepType {
        GestureStepType::Chat
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GestureStepWait {
    pub wait_for_animation: bool,
    pub wait_for_time: bool,
    pub wait_time: f32,
}

impl GestureStepWait {
    pub const fn new() -> Result<Self, Error> {
        Ok(Self {
            wait_for_animation: false,
            wait_for_time: false,
            wait_time: 0.0,
        })
    }

    #[must_use]
    pub fn to_string(&self) -> String {
        let mut result = String::from("-- Wait for: ");
        if self.wait_for_animation {
            result.push_str("(animations to finish) ");
        }
        if self.wait_for_time {
            let _ = write!(result, "(time {:.1}s)", self.wait_time);
        }
        result
    }

    #[must_use]
    pub const fn gesture_step_type(&self) -> GestureStepType {
        GestureStepType::Wait
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct GestureStepEOF;

impl GestureStepEOF {
    pub const fn new() -> Result<Self, Error> {
        Ok(Self)
    }

    #[must_use]
    pub fn to_string(&self) -> String {
        "End of gesture sequence".into()
    }

    #[must_use]
    pub const fn gesture_step_type(&self) -> GestureStepType {
        GestureStepType::EOF
    }
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct AssetGesture {
    pub replace_with: String,
    pub sequence: Vec<GestureStep>,
    pub trigger: String,
    pub trigger_key: u8,
    pub trigger_key_mask: u32,
    asset_id: UUID,
    asset_data: Vec<u8>,
}

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

    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        Ok(Self {
            asset_id,
            asset_data,
            ..Self::default()
        })
    }

    #[must_use]
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Gesture
    }

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

    #[must_use]
    pub fn asset_data(&self) -> &[u8] {
        &self.asset_data
    }

    pub fn decode(&mut self) -> Result<bool, Error> {
        Ok(self.decode_inner().is_ok())
    }

    fn decode_inner(&mut self) -> Result<(), Error> {
        let text = String::from_utf8(self.asset_data.clone()).map_err(|_| Error::Argument)?;
        let mut lines = text.split('\n');
        if parse_line::<i32>(&mut lines)? != 2 {
            return Err(Error::Argument);
        }
        self.trigger_key = parse_line(&mut lines)?;
        self.trigger_key_mask = parse_line(&mut lines)?;
        next_line(&mut lines)?.clone_into(&mut self.trigger);
        next_line(&mut lines)?.clone_into(&mut self.replace_with);
        let count = parse_line::<i32>(&mut lines)?;
        if count < 0 {
            return Err(Error::Argument);
        }
        let mut sequence = Vec::with_capacity(usize::try_from(count).map_err(|_| Error::Argument)?);
        for _ in 0..count {
            match parse_line::<i32>(&mut lines)? {
                0 => {
                    let name = next_line(&mut lines)?.to_owned();
                    let id = UUID::new_with_string(
                        next_line(&mut lines)?.trim_end_matches('\r').into(),
                    )?;
                    let flags = parse_line::<i32>(&mut lines)?;
                    sequence.push(GestureStep::Animation(GestureStepAnimation {
                        animation_start: flags == 0,
                        id,
                        name,
                    }));
                }
                1 => {
                    let name = next_line(&mut lines)?.replace('\r', "");
                    let id = UUID::new_with_string(
                        next_line(&mut lines)?.trim_end_matches('\r').into(),
                    )?;
                    let _flags = parse_line::<i32>(&mut lines)?;
                    sequence.push(GestureStep::Sound(GestureStepSound { id, name }));
                }
                2 => {
                    let text = next_line(&mut lines)?.to_owned();
                    let _flags = parse_line::<i32>(&mut lines)?;
                    sequence.push(GestureStep::Chat(GestureStepChat { text }));
                }
                3 => {
                    let wait_time = parse_line::<f32>(&mut lines)?;
                    let flags = parse_line::<i32>(&mut lines)?;
                    sequence.push(GestureStep::Wait(GestureStepWait {
                        wait_for_animation: flags & 0x02 != 0,
                        wait_for_time: flags & 0x01 != 0,
                        wait_time,
                    }));
                }
                4 => {
                    sequence.push(GestureStep::EOF(GestureStepEOF));
                    break;
                }
                // The reference ignores unknown step discriminants.
                _ => {}
            }
        }
        self.sequence = sequence;
        Ok(())
    }

    pub fn encode(&mut self) -> Result<(), Error> {
        let mut text = format!(
            "2\n{}\n{}\n{}\n{}\n{}\n",
            self.trigger_key,
            self.trigger_key_mask,
            self.trigger,
            self.replace_with,
            self.sequence.len()
        );
        for step in &self.sequence {
            match step {
                GestureStep::Animation(step) => {
                    let _ = write!(
                        text,
                        "0\n{}\n{}\n{}\n",
                        step.name,
                        step.id,
                        i32::from(!step.animation_start)
                    );
                }
                GestureStep::Sound(step) => {
                    let _ = write!(text, "1\n{}\n{}\n0\n", step.name, step.id);
                }
                GestureStep::Chat(step) => {
                    let _ = write!(text, "2\n{}\n0\n", step.text);
                }
                GestureStep::Wait(step) => {
                    let flags =
                        i32::from(step.wait_for_time) | (i32::from(step.wait_for_animation) << 1);
                    let _ = write!(text, "3\n{:.6}\n{}\n", step.wait_time, flags);
                }
                GestureStep::EOF(_) => {
                    text.push_str("4\n");
                    break;
                }
            }
        }
        self.asset_data = text.into_bytes();
        Ok(())
    }
}

fn next_line<'a>(lines: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, Error> {
    lines.next().ok_or(Error::Argument)
}

fn parse_line<'a, T: std::str::FromStr>(
    lines: &mut impl Iterator<Item = &'a str>,
) -> Result<T, Error> {
    next_line(lines)?
        .trim_end_matches('\r')
        .parse()
        .map_err(|_| Error::Argument)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn gesture_v2_roundtrip_preserves_all_step_payloads() {
        let animation = UUID::random().unwrap();
        let sound = UUID::random().unwrap();
        let mut gesture = AssetGesture::new_with_constructor().unwrap();
        gesture.trigger_key = 7;
        gesture.trigger_key_mask = 3;
        gesture.trigger = "/wave".into();
        gesture.replace_with = "waves".into();
        gesture.sequence = vec![
            GestureStep::Chat(GestureStepChat {
                text: "/5 hello".into(),
            }),
            GestureStep::Animation(GestureStepAnimation {
                animation_start: true,
                id: animation,
                name: "Wave".into(),
            }),
            GestureStep::Sound(GestureStepSound {
                id: sound,
                name: "Bell".into(),
            }),
            GestureStep::Wait(GestureStepWait {
                wait_for_animation: true,
                wait_for_time: true,
                wait_time: 1.25,
            }),
            GestureStep::EOF(GestureStepEOF),
        ];
        gesture.encode().unwrap();
        let mut decoded =
            AssetGesture::new_with_uuid_bytes(UUID::random().unwrap(), gesture.asset_data.clone())
                .unwrap();
        assert!(decoded.decode().unwrap());
        assert_eq!(decoded.trigger_key, gesture.trigger_key);
        assert_eq!(decoded.trigger_key_mask, gesture.trigger_key_mask);
        assert_eq!(decoded.trigger, gesture.trigger);
        assert_eq!(decoded.replace_with, gesture.replace_with);
        assert_eq!(decoded.sequence, gesture.sequence);
    }

    #[test]
    fn malformed_gesture_returns_false_without_partial_sequence() {
        let mut gesture =
            AssetGesture::new_with_uuid_bytes(UUID::zero(), b"1\n0\n".to_vec()).unwrap();
        assert!(!gesture.decode().unwrap());
        assert!(gesture.sequence.is_empty());
    }
}
