//! Native asset value models and their format boundaries.

#![allow(
    clippy::format_push_string,
    clippy::missing_errors_doc,
    clippy::must_use_candidate,
    clippy::needless_pass_by_value,
    clippy::struct_field_names
)]

use crate::{Error, Permissions};
#[cfg(feature = "jpeg2000")]
use libremetaverse_imaging::J2kCodec as TextureJ2kCodec;
use libremetaverse_imaging::ManagedImage;
#[cfg(all(not(feature = "jpeg2000"), feature = "rust-j2k"))]
use libremetaverse_imaging::RustJ2kCodec as TextureJ2kCodec;
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::{AssetType, SaleType, UUID, Vector3, WearableType};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// Maximum accepted in-memory asset payload. Network limits are enforced before
/// this boundary too, but constructors are public and must defend themselves.
pub const MAX_ASSET_BYTES: usize = 64 * 1024 * 1024;

fn validate_bytes(bytes: &[u8]) -> Result<(), Error> {
    if bytes.len() > MAX_ASSET_BYTES {
        Err(Error::Argument)
    } else {
        Ok(())
    }
}

#[derive(Clone, Debug)]
pub struct Asset {
    pub asset_data: Vec<u8>,
    pub temporary: bool,
    asset_id: UUID,
    asset_type: AssetType,
}

impl Asset {
    pub(crate) fn native_new(
        asset_type: AssetType,
        asset_id: UUID,
        data: Vec<u8>,
    ) -> Result<Self, Error> {
        validate_bytes(&data)?;
        Ok(Self {
            asset_data: data,
            temporary: false,
            asset_id,
            asset_type,
        })
    }

    pub fn decode(&self) -> Result<bool, Error> {
        validate_bytes(&self.asset_data)?;
        Ok(true)
    }

    pub fn encode(&self) -> Result<(), Error> {
        validate_bytes(&self.asset_data)
    }

    pub const fn asset_id(&self) -> UUID {
        self.asset_id
    }
    pub fn set_asset_id(&mut self, value: UUID) {
        self.asset_id = value;
    }
    pub const fn asset_type(&self) -> AssetType {
        self.asset_type
    }
}

#[derive(Clone, Debug)]
struct RawAsset(Arc<RwLock<Asset>>);

impl RawAsset {
    fn empty(asset_type: AssetType) -> Result<Self, Error> {
        Ok(Self(Arc::new(RwLock::new(Asset::native_new(
            asset_type,
            UUID::zero(),
            Vec::new(),
        )?))))
    }

    fn with_data(asset_type: AssetType, id: UUID, data: Vec<u8>) -> Result<Self, Error> {
        Ok(Self(Arc::new(RwLock::new(Asset::native_new(
            asset_type, id, data,
        )?))))
    }

    fn decode(&self) -> Result<bool, Error> {
        self.0
            .read()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .decode()
    }

    fn encode(&self) -> Result<(), Error> {
        self.0
            .read()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .encode()
    }

    fn bytes(&self) -> Vec<u8> {
        self.0
            .read()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .asset_data
            .clone()
    }

    fn replace(&self, bytes: Vec<u8>) -> Result<(), Error> {
        validate_bytes(&bytes)?;
        self.0
            .write()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .asset_data = bytes;
        Ok(())
    }
}

macro_rules! raw_asset {
    ($name:ident, $kind:expr) => {
        #[derive(Clone, Debug)]
        pub struct $name {
            raw: RawAsset,
        }
        impl $name {
            pub fn new_with_constructor() -> Result<Self, Error> {
                Ok(Self {
                    raw: RawAsset::empty($kind)?,
                })
            }
            pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
                Ok(Self {
                    raw: RawAsset::with_data($kind, asset_id, asset_data)?,
                })
            }
            pub fn decode(&self) -> Result<bool, Error> {
                self.raw.decode()
            }
            pub fn encode(&self) -> Result<(), Error> {
                self.raw.encode()
            }
            pub const fn asset_type(&self) -> AssetType {
                $kind
            }
        }
    };
}

raw_asset!(AssetAnimation, AssetType::Animation);
raw_asset!(AssetScriptBinary, AssetType::LSLBytecode);
raw_asset!(AssetSound, AssetType::Sound);

impl AssetSound {
    #[cfg(feature = "vorbis")]
    pub fn pcm_to_ogg(
        pcm_data: Vec<u8>,
        sample_rate: i32,
        channels: i32,
        bits_per_sample: Option<i32>,
    ) -> Result<Vec<u8>, Error> {
        validate_pcm(&pcm_data, sample_rate, channels, bits_per_sample)?;
        let bits = bits_per_sample.unwrap_or(16);
        let bytes_per_sample = usize::try_from(bits / 8).map_err(|_| Error::Argument)?;
        let channels = usize::try_from(channels).map_err(|_| Error::Argument)?;
        let frame_size = bytes_per_sample
            .checked_mul(channels)
            .ok_or(Error::Argument)?;
        if !pcm_data.len().is_multiple_of(frame_size) {
            return Err(Error::Argument);
        }
        let frequency =
            std::num::NonZeroU32::new(u32::try_from(sample_rate).map_err(|_| Error::Argument)?)
                .ok_or(Error::Argument)?;
        let channel_count =
            std::num::NonZeroU8::new(u8::try_from(channels).map_err(|_| Error::Argument)?)
                .ok_or(Error::Argument)?;
        let mut builder = vorbis_rs::VorbisEncoderBuilder::new_with_serial(
            frequency,
            channel_count,
            Vec::new(),
            0x4d43_4154,
        );
        let mut encoder = builder.build().map_err(|_| Error::InvalidOperation)?;
        let frame_count = pcm_data.len() / frame_size;
        for first_frame in (0..frame_count).step_by(1024) {
            let end_frame = first_frame.saturating_add(1024).min(frame_count);
            let mut planar = vec![Vec::with_capacity(end_frame - first_frame); channels];
            for frame in first_frame..end_frame {
                for (channel, samples) in planar.iter_mut().enumerate() {
                    let offset = frame * frame_size + channel * bytes_per_sample;
                    let sample = if bits == 8 {
                        (f32::from(pcm_data[offset]) - 128.0) / 128.0
                    } else {
                        f32::from(i16::from_le_bytes([pcm_data[offset], pcm_data[offset + 1]]))
                            / 32768.0
                    };
                    samples.push(sample);
                }
            }
            encoder
                .encode_audio_block(&planar)
                .map_err(|_| Error::InvalidOperation)?;
        }
        let ogg_data = encoder.finish().map_err(|_| Error::InvalidOperation)?;
        validate_bytes(&ogg_data)?;
        Ok(ogg_data)
    }

    #[cfg(not(feature = "vorbis"))]
    pub fn pcm_to_ogg(
        pcm_data: Vec<u8>,
        sample_rate: i32,
        channels: i32,
        bits_per_sample: Option<i32>,
    ) -> Result<Vec<u8>, Error> {
        validate_pcm(&pcm_data, sample_rate, channels, bits_per_sample)?;
        Err(Error::InvalidOperation)
    }
}

fn validate_pcm(
    pcm_data: &[u8],
    sample_rate: i32,
    channels: i32,
    bits_per_sample: Option<i32>,
) -> Result<(), Error> {
    validate_bytes(pcm_data)?;
    if pcm_data.is_empty()
        || sample_rate <= 0
        || !matches!(channels, 1 | 2)
        || !matches!(bits_per_sample.unwrap_or(16), 8 | 16)
    {
        return Err(Error::Argument);
    }
    let bytes_per_sample =
        usize::try_from(bits_per_sample.unwrap_or(16) / 8).map_err(|_| Error::Argument)?;
    let channels = usize::try_from(channels).map_err(|_| Error::Argument)?;
    let frame_size = bytes_per_sample
        .checked_mul(channels)
        .ok_or(Error::Argument)?;
    if !pcm_data.len().is_multiple_of(frame_size) {
        return Err(Error::Argument);
    }
    Ok(())
}

#[derive(Clone, Debug)]
pub struct AssetMutable {
    raw: RawAsset,
    pub current_type: AssetType,
}
impl AssetMutable {
    pub fn new_with_asset_type(type_: AssetType) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(type_)?,
            current_type: type_,
        })
    }
    pub fn new_with_asset_type_uuid_bytes(
        type_: AssetType,
        asset_id: UUID,
        asset_data: Vec<u8>,
    ) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::with_data(type_, asset_id, asset_data)?,
            current_type: type_,
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        self.raw.decode()
    }
    pub fn encode(&self) -> Result<(), Error> {
        self.raw.encode()
    }
    pub const fn asset_type(&self) -> AssetType {
        self.current_type
    }
}

#[derive(Clone, Debug)]
pub struct AssetCallingCard {
    raw: RawAsset,
    pub avatar_id: UUID,
}
impl AssetCallingCard {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Self::new_with_uuid(UUID::zero())
    }
    pub fn new_with_uuid(avatar_id: UUID) -> Result<Self, Error> {
        let value = Self {
            raw: RawAsset::empty(AssetType::CallingCard)?,
            avatar_id,
        };
        value.encode()?;
        Ok(value)
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let raw = RawAsset::with_data(AssetType::CallingCard, asset_id, asset_data)?;
        let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?;
        let id = text
            .lines()
            .find_map(|line| line.strip_prefix("avatar_id "))
            .ok_or(Error::Argument)
            .and_then(|id| UUID::new_with_string(id.trim().to_owned()))?;
        Ok(Self { raw, avatar_id: id })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        let text = String::from_utf8(self.raw.bytes()).map_err(|_| Error::Argument)?;
        Ok(text.lines().any(|line| {
            line.strip_prefix("avatar_id ")
                .and_then(|id| UUID::new_with_string(id.trim().to_owned()).ok())
                == Some(self.avatar_id)
        }))
    }
    pub fn encode(&self) -> Result<(), Error> {
        self.raw.replace(
            format!(
                "Linden text version 2\n{{\navatar_id {}\n}}\n",
                self.avatar_id
            )
            .into_bytes(),
        )
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::CallingCard
    }
}

#[derive(Clone, Debug)]
pub struct AssetLandmark {
    raw: RawAsset,
    pub position: Vector3,
    pub region_id: UUID,
}
impl AssetLandmark {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::Landmark)?,
            position: Vector3::zero(),
            region_id: UUID::zero(),
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let raw = RawAsset::with_data(AssetType::Landmark, asset_id, asset_data)?;
        let text = String::from_utf8(raw.bytes()).map_err(|_| Error::Argument)?;
        let region = text
            .lines()
            .find_map(|line| line.strip_prefix("region_id "))
            .ok_or(Error::Argument)
            .and_then(|id| UUID::new_with_string(id.trim().to_owned()))?;
        let values: Vec<f32> = text
            .lines()
            .find_map(|line| line.strip_prefix("local_pos "))
            .ok_or(Error::Argument)?
            .split_whitespace()
            .map(str::parse)
            .collect::<Result<_, _>>()
            .map_err(|_| Error::Argument)?;
        if values.len() != 3 {
            return Err(Error::Argument);
        }
        Ok(Self {
            raw,
            region_id: region,
            position: Vector3 {
                x: values[0],
                y: values[1],
                z: values[2],
            },
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok())
    }
    pub fn encode(&self) -> Result<(), Error> {
        self.raw.replace(
            format!(
                "Landmark version 2\nregion_id {}\nlocal_pos {} {} {}\n",
                self.region_id, self.position.x, self.position.y, self.position.z
            )
            .into_bytes(),
        )
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Landmark
    }
}

#[derive(Clone, Debug)]
pub struct AssetScriptText {
    raw: RawAsset,
    pub source: Option<String>,
}
impl AssetScriptText {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::LSLText)?,
            source: None,
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let source = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?;
        Ok(Self {
            raw: RawAsset::with_data(AssetType::LSLText, asset_id, asset_data)?,
            source: Some(source),
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        Ok(String::from_utf8(self.raw.bytes()).is_ok())
    }
    pub fn encode(&self) -> Result<(), Error> {
        self.raw
            .replace(self.source.clone().unwrap_or_default().into_bytes())
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::LSLText
    }
}

#[derive(Clone, Debug)]
pub struct AssetSettings {
    raw: RawAsset,
    pub settings: Option<OSD>,
}
impl AssetSettings {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::Settings)?,
            settings: None,
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?;
        let settings = OSDParser::deserialize_with_string(text)?;
        Ok(Self {
            raw: RawAsset::with_data(AssetType::Settings, asset_id, asset_data)?,
            settings: Some(settings),
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        Ok(OSDParser::deserialize_with_bytes(self.raw.bytes()).is_ok())
    }
    pub fn encode(&self) -> Result<(), Error> {
        let value = self.settings.clone().unwrap_or_default();
        self.raw.replace(value.as_string()?.into_bytes())
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Settings
    }
}

pub struct AssetMesh {
    raw: RawAsset,
    pub mesh_data: OSDMap,
}
impl AssetMesh {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::Mesh)?,
            mesh_data: OSDMap::new_with_constructor()?,
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::with_data(AssetType::Mesh, asset_id, asset_data)?,
            mesh_data: OSDMap::new_with_constructor()?,
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        self.mesh_data.clear()?;
        let Ok(decoded) = crate::mesh_asset::decode_asset_map(&self.raw.bytes(), UUID::zero())
        else {
            return Ok(false);
        };
        for entry in decoded.snapshot() {
            self.mesh_data.add_with_key_value_pair(entry)?;
        }
        Ok(true)
    }
    pub fn encode(&self) -> Result<(), Error> {
        // LLMesh encoding from arbitrary decoded domains is intentionally not
        // defined by the pinned API. Preserve the original asset bytes, just
        // as the reference implementation's sealed no-op encoder does.
        Ok(())
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Mesh
    }

    pub(crate) fn native_bytes(&self) -> Vec<u8> {
        self.raw.bytes()
    }
}

pub struct AssetTexture {
    raw: RawAsset,
    pub components: i32,
    pub image: Option<ManagedImage>,
}
impl AssetTexture {
    pub(crate) fn native_bytes(&self) -> Vec<u8> {
        self.raw.bytes()
    }

    pub(crate) fn native_deep_clone(&self) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::with_data(
                AssetType::Texture,
                self.raw
                    .0
                    .read()
                    .unwrap_or_else(std::sync::PoisonError::into_inner)
                    .asset_id(),
                self.raw.bytes(),
            )?,
            components: self.components,
            image: self.image.as_ref().map(ManagedImage::clone).transpose()?,
        })
    }

    pub(crate) fn native_decode_image(&mut self) -> Result<bool, Error> {
        if self.image.is_some() {
            return Ok(true);
        }
        #[cfg(any(feature = "jpeg2000", feature = "rust-j2k"))]
        {
            self.image = Some(TextureJ2kCodec::decode_bytes(
                &self.raw.bytes(),
                libremetaverse_imaging::J2kDecodeOptions::default(),
            )?);
            self.components = if self.image.as_ref().is_some_and(|image| {
                image
                    .channels
                    .contains(libremetaverse_imaging::ManagedImageImageChannels::ALPHA)
            }) {
                4
            } else {
                3
            };
            Ok(true)
        }
        #[cfg(not(any(feature = "jpeg2000", feature = "rust-j2k")))]
        {
            Err(Error::InvalidOperation)
        }
    }
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::Texture)?,
            components: 0,
            image: None,
        })
    }
    pub fn new_with_managed_image(image: ManagedImage) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::Texture)?,
            components: 0,
            image: Some(image),
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::with_data(AssetType::Texture, asset_id, asset_data)?,
            components: 0,
            image: None,
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        #[cfg(any(feature = "jpeg2000", feature = "rust-j2k"))]
        {
            Ok(TextureJ2kCodec::decode_bytes(
                &self.raw.bytes(),
                libremetaverse_imaging::J2kDecodeOptions::default(),
            )
            .is_ok())
        }
        #[cfg(not(any(feature = "jpeg2000", feature = "rust-j2k")))]
        {
            validate_bytes(&self.raw.bytes())?;
            Err(Error::InvalidOperation)
        }
    }
    pub fn encode(&self) -> Result<(), Error> {
        let image = self.image.as_ref().ok_or(Error::InvalidOperation)?;
        #[cfg(any(feature = "jpeg2000", feature = "rust-j2k"))]
        {
            let encoded = TextureJ2kCodec::encode(
                image,
                libremetaverse_imaging::J2kEncodeOptions::default(),
            )?;
            self.raw.replace(encoded)
        }
        #[cfg(not(any(feature = "jpeg2000", feature = "rust-j2k")))]
        {
            let _ = image;
            Err(Error::InvalidOperation)
        }
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Texture
    }
}

#[derive(Clone, Debug)]
pub struct AssetWearable {
    raw: RawAsset,
    pub creator: UUID,
    pub description: String,
    pub for_sale: SaleType,
    pub group: UUID,
    pub group_owned: bool,
    pub last_owner: UUID,
    pub name: String,
    pub owner: UUID,
    pub params: HashMap<i32, f32>,
    pub permissions: Permissions,
    pub sale_price: i32,
    pub textures: HashMap<crate::AvatarTextureIndex, UUID>,
    pub wearable_type: WearableType,
}
impl AssetWearable {
    fn empty(kind: AssetType) -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(kind)?,
            creator: UUID::zero(),
            description: String::new(),
            for_sale: SaleType::Not,
            group: UUID::zero(),
            group_owned: false,
            last_owner: UUID::zero(),
            name: String::new(),
            owner: UUID::zero(),
            params: HashMap::new(),
            permissions: Permissions::default(),
            sale_price: 0,
            textures: HashMap::new(),
            wearable_type: WearableType::Invalid,
        })
    }

    pub(crate) fn native_from_asset(
        asset: &Asset,
        wearable_type: WearableType,
    ) -> Result<Self, Error> {
        if !matches!(
            asset.asset_type(),
            AssetType::Bodypart | AssetType::Clothing
        ) {
            return Err(Error::Argument);
        }
        let text = std::str::from_utf8(&asset.asset_data).map_err(|_| Error::Argument)?;
        let mut result = Self::empty(asset.asset_type())?;
        result.raw = RawAsset::with_data(
            asset.asset_type(),
            asset.asset_id(),
            asset.asset_data.clone(),
        )?;
        result.wearable_type = wearable_type;
        let lines: Vec<_> = text.lines().collect();
        let mut index = 0;
        while index < lines.len() {
            let line = lines[index].trim();
            if let Some(count) = line
                .strip_prefix("parameters ")
                .and_then(|value| value.parse::<usize>().ok())
            {
                for parameter in lines.iter().skip(index + 1).take(count) {
                    let mut values = parameter.split_whitespace();
                    if let (Some(id), Some(value)) = (values.next(), values.next())
                        && let (Ok(id), Ok(value)) = (id.parse::<i32>(), value.parse::<f32>())
                    {
                        result.params.insert(id, value);
                    }
                }
                index = index.saturating_add(count);
            } else if let Some(count) = line
                .strip_prefix("textures ")
                .and_then(|value| value.parse::<usize>().ok())
            {
                for texture in lines.iter().skip(index + 1).take(count) {
                    let mut values = texture.split_whitespace();
                    if let (Some(slot), Some(id)) = (values.next(), values.next())
                        && let (Ok(slot), Ok(id)) =
                            (slot.parse::<i32>(), UUID::new_with_string(id.to_owned()))
                        && let Some(slot) =
                            crate::appearance_manager::avatar_texture_index_from_i32(slot)
                    {
                        result.textures.insert(slot, id);
                    }
                }
                index = index.saturating_add(count);
            }
            index += 1;
        }
        Ok(result)
    }
    pub fn decode(&self) -> Result<bool, Error> {
        Ok(std::str::from_utf8(&self.raw.bytes()).is_ok())
    }
    pub fn encode(&self) -> Result<(), Error> {
        let mut text = format!(
            "LLWearable version 22\n{}\n{}\ntype {}\nparameters {}\n",
            self.name,
            self.description,
            self.wearable_type as i32,
            self.params.len()
        );
        let mut params: Vec<_> = self.params.iter().collect();
        params.sort_unstable_by_key(|(id, _)| **id);
        for (id, value) in params {
            text.push_str(&format!("{id} {value}\n"));
        }
        text.push_str(&format!("textures {}\n", self.textures.len()));
        self.raw.replace(text.into_bytes())
    }
}

pub struct AssetBodypart {
    wearable: AssetWearable,
}
impl AssetBodypart {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            wearable: AssetWearable::empty(AssetType::Bodypart)?,
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let mut wearable = AssetWearable::empty(AssetType::Bodypart)?;
        wearable.raw = RawAsset::with_data(AssetType::Bodypart, asset_id, asset_data)?;
        Ok(Self { wearable })
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Bodypart
    }
    pub fn decode(&self) -> Result<bool, Error> {
        self.wearable.decode()
    }
    pub fn encode(&self) -> Result<(), Error> {
        self.wearable.encode()
    }
}
pub struct AssetClothing {
    wearable: AssetWearable,
}
impl AssetClothing {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            wearable: AssetWearable::empty(AssetType::Clothing)?,
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let mut wearable = AssetWearable::empty(AssetType::Clothing)?;
        wearable.raw = RawAsset::with_data(AssetType::Clothing, asset_id, asset_data)?;
        Ok(Self { wearable })
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Clothing
    }
    pub fn decode(&self) -> Result<bool, Error> {
        self.wearable.decode()
    }
    pub fn encode(&self) -> Result<(), Error> {
        self.wearable.encode()
    }
}

pub struct AssetNotecard {
    raw: RawAsset,
    pub body_text: String,
    pub embedded_items: Vec<crate::InventoryItem>,
}
impl AssetNotecard {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            raw: RawAsset::empty(AssetType::Notecard)?,
            body_text: String::new(),
            embedded_items: Vec::new(),
        })
    }
    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        let text = String::from_utf8(asset_data.clone()).map_err(|_| Error::Argument)?;
        let marker = "Text length ";
        let start = text.find(marker).ok_or(Error::Argument)? + marker.len();
        let line_end = text[start..].find('\n').ok_or(Error::Argument)? + start;
        let length: usize = text[start..line_end]
            .trim()
            .parse()
            .map_err(|_| Error::Argument)?;
        let body_start = line_end + 1;
        let body_end = body_start.checked_add(length).ok_or(Error::Argument)?;
        let body_text = text
            .get(body_start..body_end)
            .ok_or(Error::Argument)?
            .to_owned();
        Ok(Self {
            raw: RawAsset::with_data(AssetType::Notecard, asset_id, asset_data)?,
            body_text,
            embedded_items: Vec::new(),
        })
    }
    pub fn decode(&self) -> Result<bool, Error> {
        Ok(Self::new_with_uuid_bytes(UUID::zero(), self.raw.bytes()).is_ok())
    }
    pub fn encode(&self) -> Result<(), Error> {
        if !self.embedded_items.is_empty() {
            return Err(Error::InvalidOperation);
        }
        let bytes = self.body_text.as_bytes();
        self.raw.replace(format!("Linden text version 2\n{{\nLLEmbeddedItems version 1\n{{\ncount 0\n}}\nText length {}\n{}\n}}\n", bytes.len(), self.body_text).into_bytes())
    }
    pub const fn asset_type(&self) -> AssetType {
        AssetType::Notecard
    }
}

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

    #[test]
    fn wearable_parser_preserves_parameters_and_texture_indices() {
        let texture_id = UUID::new_with_u_int64(42).unwrap();
        let bytes = format!(
            "LLWearable version 22\nshirt\ndescription\ntype 4\nparameters 2\n33 0.75\n108 -0.25\ntextures 2\n1 {texture_id}\n13 {texture_id}\n"
        )
        .into_bytes();
        let asset_id = UUID::new_with_u_int64(41).unwrap();
        let asset = Asset::native_new(AssetType::Clothing, asset_id, bytes).unwrap();
        let wearable = AssetWearable::native_from_asset(&asset, WearableType::Shirt).unwrap();
        assert_eq!(wearable.params.get(&33), Some(&0.75));
        assert_eq!(wearable.params.get(&108), Some(&-0.25));
        assert_eq!(
            wearable
                .textures
                .get(&crate::AvatarTextureIndex::UpperShirt),
            Some(&texture_id)
        );
        assert_eq!(
            wearable
                .textures
                .get(&crate::AvatarTextureIndex::UpperJacket),
            Some(&texture_id)
        );
        assert_eq!(wearable.wearable_type, WearableType::Shirt);
        assert_eq!(wearable.raw.0.read().unwrap().asset_id(), asset_id);
    }

    #[test]
    fn wearable_parser_rejects_non_wearable_assets() {
        let asset = Asset::native_new(AssetType::Texture, UUID::zero(), Vec::new()).unwrap();
        assert!(matches!(
            AssetWearable::native_from_asset(&asset, WearableType::Shirt),
            Err(Error::Argument)
        ));
    }
}
