// Exact Rust translations of LibreMetaverse.Tests/AppearanceTests.cs.
// Source SHA-256: 3d1f6a43127ee2c678284fd68572c0e81d1825766cc88e17136111fe8b3b7a86

use libremetaverse::{
    AppearanceFlags, Avatar, AvatarAppearanceEventArgs, PrimitiveTextureEntryFace, VisualParam,
    VisualParams,
};
use libremetaverse_types::{UUID, Utils};
use std::collections::HashSet;

fn group0_params() -> Vec<VisualParam> {
    let mut params = VisualParams::params();
    VisualParams::group0_param_ids()
        .into_iter()
        .filter_map(|id| params.0.remove(&id))
        .collect()
}

fn appearance_args(visual_params: Vec<u8>) -> AvatarAppearanceEventArgs {
    AvatarAppearanceEventArgs::new(
        None,
        UUID::zero(),
        false,
        PrimitiveTextureEntryFace::new(None).unwrap(),
        Vec::new(),
        visual_params,
        1,
        0,
        AppearanceFlags::NONE,
        0,
    )
    .unwrap()
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_EmptyArray_ReturnsEmptyDictionary::test 644f412b90d429032a9273d24e89333a1694dc88eec3429a98acf00588315ec5 translated
#[test]
fn decode_visual_params_empty_array_returns_empty_dictionary() {
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = Vec::new();
    let decoded = avatar.decode_visual_params().unwrap();
    assert_eq!(decoded.len(), 0);
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_CountMatchesGroup0ParamCount::test 6a9acc7130e3394476392b86334a0ce1cb82253bea2b37ac33cf90a426c7af40 translated
#[test]
fn decode_visual_params_count_matches_group0_param_count() {
    let group0 = group0_params();
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = vec![0; group0.len()];
    let decoded = avatar.decode_visual_params().unwrap();
    assert!(decoded.len() >= group0.len());
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_RoundTrip_DefaultValues::test a0c0727a50b414522f875709240f4056547cb699c6ec1981fbef15488784daf5 translated
#[test]
fn decode_visual_params_round_trip_default_values() {
    let params = VisualParams::params();
    let ids = VisualParams::group0_param_ids();
    let bytes = ids
        .iter()
        .map(|id| {
            params.0.get(id).map_or(0, |vp| {
                Utils::float_to_byte(vp.default_value, vp.min_value, vp.max_value).unwrap()
            })
        })
        .collect();
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = bytes;
    let decoded = avatar.decode_visual_params().unwrap();
    let driven_targets: HashSet<i32> = params
        .0
        .values()
        .filter_map(|param| param.driven_params.as_ref())
        .flatten()
        .map(|driven| driven.param_id)
        .collect();

    for id in ids {
        let Some(vp) = params.0.get(&id) else {
            continue;
        };
        assert!(
            decoded.contains_key(&id),
            "ParamID {id} ({}) missing",
            vp.name
        );
        if driven_targets.contains(&id) {
            continue;
        }
        let encoded = Utils::float_to_byte(vp.default_value, vp.min_value, vp.max_value).unwrap();
        let expected =
            Utils::byte_to_float_with_byte_single_single(encoded, vp.min_value, vp.max_value)
                .unwrap();
        assert!(
            (decoded[&id] - expected).abs() <= 1e-5,
            "ParamID {id} ({})",
            vp.name
        );
    }
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_KeysAreGroup0ParamIDs::test fc4fd3059b01a319af2946ea0c289120a4c2609e7e9a96ac26c039c5042b8b4c translated
#[test]
fn decode_visual_params_keys_are_group0_param_ids() {
    let group0 = group0_params();
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = vec![0; group0.len()];
    let decoded = avatar.decode_visual_params().unwrap();
    let expected: HashSet<i32> = group0.iter().map(|param| param.param_id).collect();
    assert!(expected.iter().all(|id| decoded.contains_key(id)));
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_TruncatedArray_ReturnsPartialResult::test b017127549e186b3cf85b9fe0cc09811ea1d00efa44e3d863292033cb0691d13 translated
#[test]
fn decode_visual_params_truncated_array_returns_partial_result() {
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = vec![0; 10];
    let decoded = avatar.decode_visual_params().unwrap();
    assert!(decoded.len() >= 10);
    assert!(decoded.len() < VisualParams::group0_param_ids().len());
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_MinValue_DecodesNearParamMin::test 80e0fd97979f91c879add1fc3142a0b619a405e4977120db1d5f42a11a71f3f5 translated
#[test]
fn decode_visual_params_min_value_decodes_near_param_min() {
    let group0 = group0_params();
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = vec![0; group0.len()];
    let decoded = avatar.decode_visual_params().unwrap();
    for vp in group0 {
        let upper = vp.min_value + (vp.max_value - vp.min_value) / 255.0 + 1e-5;
        assert!(
            decoded[&vp.param_id] <= upper,
            "ParamID {} ({})",
            vp.param_id,
            vp.name
        );
    }
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.DecodeVisualParams_MaxValue_DecodesNearParamMax::test 31937bc9080b18be4aa98ea44bf73f929ba7862b621406b03c16a4651542f302 translated
#[test]
fn decode_visual_params_max_value_decodes_near_param_max() {
    let group0 = group0_params();
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = vec![0xff; group0.len()];
    let decoded = avatar.decode_visual_params().unwrap();
    for vp in group0 {
        let lower = vp.max_value - (vp.max_value - vp.min_value) / 255.0 - 1e-5;
        assert!(
            decoded[&vp.param_id] >= lower,
            "ParamID {} ({})",
            vp.param_id,
            vp.name
        );
    }
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_EmptyList_ReturnsEmptyDictionary::test af99909259d531024895ce7ea325b49927a12daa886a35eab3d52e3f8fc76921 translated
#[test]
fn appearance_event_args_decode_visual_params_empty_list_returns_empty_dictionary() {
    let decoded = appearance_args(Vec::new()).decode_visual_params().unwrap();
    assert_eq!(decoded.len(), 0);
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_RoundTrip_DefaultValues::test 0aec57fab35764525195e6c0e08a63ff10a467be684295381731c04e836539b5 translated
#[test]
fn appearance_event_args_decode_visual_params_round_trip_default_values() {
    let params = VisualParams::params();
    let ids = VisualParams::group0_param_ids();
    let bytes = ids
        .iter()
        .map(|id| {
            params.0.get(id).map_or(0, |vp| {
                Utils::float_to_byte(vp.default_value, vp.min_value, vp.max_value).unwrap()
            })
        })
        .collect();
    let decoded = appearance_args(bytes).decode_visual_params().unwrap();
    assert!(decoded.len() >= ids.len());
    for id in ids {
        if params.0.contains_key(&id) {
            assert!(decoded.contains_key(&id));
        }
    }
}

// parity-case: LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_MatchesAvatarDecodeVisualParams::test ff87dd3a506027ad248006b3b55d85a18b0810bc5b4e3a72eb81d31ebbe4184a translated
#[test]
fn appearance_event_args_decode_visual_params_matches_avatar_decode_visual_params() {
    let group0 = group0_params();
    let bytes: Vec<u8> = group0
        .iter()
        .map(|vp| Utils::float_to_byte(vp.default_value, vp.min_value, vp.max_value).unwrap())
        .collect();
    let mut avatar = Avatar::new().unwrap();
    avatar.visual_parameters = bytes.clone();
    let from_avatar = avatar.decode_visual_params().unwrap();
    let from_args = appearance_args(bytes).decode_visual_params().unwrap();
    assert_eq!(from_args.len(), from_avatar.len());
    for (id, expected) in from_avatar {
        assert!(from_args.contains_key(&id));
        assert!((from_args[&id] - expected).abs() <= 1e-5);
    }
}
