//! Native protocol conversion and utility helpers.

#![allow(clippy::approx_constant)] // Decimal constants exactly preserve the reference protocol API.
#![allow(clippy::cast_lossless)] // Explicit casts document the C# underlying enum widths.
#![allow(clippy::cast_possible_truncation)] // C# numeric conversions deliberately narrow and wrap.
#![allow(clippy::cast_possible_wrap)] // C# time conversions deliberately reinterpret overflow.
#![allow(clippy::cast_precision_loss)] // Random.NextDouble-style construction uses 53 random bits.
#![allow(clippy::cast_sign_loss)] // C# signed timestamp overload casts to uint before conversion.
#![allow(clippy::float_cmp)] // Exact Hermite endpoints are required reference behavior.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the public compatibility map.
#![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped C# surface.
#![allow(clippy::needless_pass_by_value)] // Owned parameters preserve the generated API mapping.

use std::net::{IpAddr, ToSocketAddrs};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use base64::Engine as _;
use md5::{Digest as _, Md5};
use sha1::Sha1;
use sha2::{Digest as _, Sha256};

use crate::compat::{CultureInfo, EnumValue};
use crate::{
    AssetType, AttachmentPoint, EnumValueMetadata, FolderType, InventoryType, SaleType,
    UtilsPlatform,
};

const ASSET_TYPE_NAMES: [&str; 50] = [
    "texture", "sound", "callcard", "landmark", "script", "clothing", "object", "notecard",
    "category", "", "lsltext", "lslbyte", "txtr_tga", "bodypart", "", "", "", "snd_wav", "img_tga",
    "jpeg", "animatn", "gesture", "simstate", "", "link", "link_f", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "mesh",
];

const FOLDER_TYPE_NAMES: [&str; 55] = [
    "texture", "sound", "callcard", "landmark", "", "clothing", "object", "notecard", "root_inv",
    "", "lsltext", "", "", "bodypart", "trash", "snapshot", "lstndfnd", "", "", "", "animatn",
    "gesture", "", "favorite", "", "", "ensemble", "ensemble", "ensemble", "ensemble", "ensemble",
    "ensemble", "ensemble", "ensemble", "ensemble", "ensemble", "ensemble", "ensemble", "ensemble",
    "ensemble", "ensemble", "ensemble", "ensemble", "ensemble", "ensemble", "ensemble", "current",
    "outfit", "my_otfts", "mesh", "inbox", "outbox", "basic_rt", "merchant", "stock",
];

const INVENTORY_TYPE_NAMES: [&str; 23] = [
    "texture",
    "sound",
    "callcard",
    "landmark",
    "",
    "",
    "object",
    "notecard",
    "category",
    "root",
    "script",
    "",
    "",
    "",
    "",
    "snapshot",
    "",
    "attach",
    "wearable",
    "animation",
    "gesture",
    "",
    "mesh",
];
const SALE_TYPE_NAMES: [&str; 4] = ["not", "orig", "copy", "cntn"];
const ATTACHMENT_POINT_NAMES: [&str; 56] = [
    "",
    "ATTACH_CHEST",
    "ATTACH_HEAD",
    "ATTACH_LSHOULDER",
    "ATTACH_RSHOULDER",
    "ATTACH_LHAND",
    "ATTACH_RHAND",
    "ATTACH_LFOOT",
    "ATTACH_RFOOT",
    "ATTACH_BACK",
    "ATTACH_PELVIS",
    "ATTACH_MOUTH",
    "ATTACH_CHIN",
    "ATTACH_LEAR",
    "ATTACH_REAR",
    "ATTACH_LEYE",
    "ATTACH_REYE",
    "ATTACH_NOSE",
    "ATTACH_RUARM",
    "ATTACH_RLARM",
    "ATTACH_LUARM",
    "ATTACH_LLARM",
    "ATTACH_RHIP",
    "ATTACH_RULEG",
    "ATTACH_RLLEG",
    "ATTACH_LHIP",
    "ATTACH_LULEG",
    "ATTACH_LLLEG",
    "ATTACH_BELLY",
    "ATTACH_RPEC",
    "ATTACH_LPEC",
    "ATTACH_HUD_CENTER_2",
    "ATTACH_HUD_TOP_RIGHT",
    "ATTACH_HUD_TOP_CENTER",
    "ATTACH_HUD_TOP_LEFT",
    "ATTACH_HUD_CENTER_1",
    "ATTACH_HUD_BOTTOM_LEFT",
    "ATTACH_HUD_BOTTOM",
    "ATTACH_HUD_BOTTOM_RIGHT",
    "ATTACH_NECK",
    "ATTACH_AVATAR_CENTER",
    "ATTACH_LHAND_RING1",
    "ATTACH_RHAND_RING1",
    "ATTACH_TAIL_BASE",
    "ATTACH_TAIL_TIP",
    "ATTACH_LWING",
    "ATTACH_RWING",
    "ATTACH_FACE_JAW",
    "ATTACH_FACE_LEAR",
    "ATTACH_FACE_REAR",
    "ATTACH_FACE_LEYE",
    "ATTACH_FACE_REYE",
    "ATTACH_FACE_TONGUE",
    "ATTACH_GROIN",
    "ATTACH_HIND_LFOOT",
    "ATTACH_HIND_RFOOT",
];

/// Cross-platform native implementation of the C# `Utils` API.
pub struct Utils;

impl Utils {
    pub const E: f32 = std::f32::consts::E;
    pub const LOG10_E: f32 = 0.434_294_5;
    pub const LOG2_E: f32 = std::f32::consts::LOG2_E;
    pub const PI: f32 = std::f32::consts::PI;
    pub const TWO_PI: f32 = std::f32::consts::TAU;
    pub const PI_OVER_TWO: f32 = std::f32::consts::FRAC_PI_2;
    pub const PI_OVER_FOUR: f32 = std::f32::consts::FRAC_PI_4;
    pub const DEG_TO_RAD: f32 = std::f32::consts::PI / 180.0;
    pub const RAD_TO_DEG: f32 = 180.0 / std::f32::consts::PI;

    #[must_use]
    pub fn en_us_culture() -> CultureInfo {
        CultureInfo("en-us".to_owned())
    }
    #[must_use]
    pub fn epoch() -> SystemTime {
        UNIX_EPOCH
    }
    #[must_use]
    pub fn empty_bytes() -> Vec<u8> {
        Vec::new()
    }
    #[must_use]
    pub const fn intern_strings() -> bool {
        false
    }

    pub fn clamp_with_single_single_single(
        mut value: f32,
        min: f32,
        max: f32,
    ) -> Result<f32, crate::Error> {
        if value > max {
            value = max;
        }
        if value < min {
            value = min;
        }
        Ok(value)
    }
    pub fn clamp_with_double_double_double(
        mut value: f64,
        min: f64,
        max: f64,
    ) -> Result<f64, crate::Error> {
        if value > max {
            value = max;
        }
        if value < min {
            value = min;
        }
        Ok(value)
    }
    pub fn clamp_with_int32_int32_int32(
        mut value: i32,
        min: i32,
        max: i32,
    ) -> Result<i32, crate::Error> {
        if value > max {
            value = max;
        }
        if value < min {
            value = min;
        }
        Ok(value)
    }
    pub fn round(val: f32) -> Result<i32, crate::Error> {
        Ok((val + 0.5).floor() as i32)
    }
    pub fn is_finite_with_single(value: f32) -> Result<bool, crate::Error> {
        Ok(value.is_finite())
    }
    pub fn is_finite_with_double(value: f64) -> Result<bool, crate::Error> {
        Ok(value.is_finite())
    }
    pub fn distance(value1: f32, value2: f32) -> Result<f32, crate::Error> {
        Ok((value1 - value2).abs())
    }

    pub fn hermite_with_single_single_single_single_single(
        value1: f32,
        tangent1: f32,
        value2: f32,
        tangent2: f32,
        amount: f32,
    ) -> Result<f32, crate::Error> {
        Ok(hermite_f64(
            f64::from(value1),
            f64::from(tangent1),
            f64::from(value2),
            f64::from(tangent2),
            f64::from(amount),
        ) as f32)
    }
    pub fn hermite_with_double_double_double_double_double(
        value1: f64,
        tangent1: f64,
        value2: f64,
        tangent2: f64,
        amount: f64,
    ) -> Result<f64, crate::Error> {
        Ok(hermite_f64(value1, tangent1, value2, tangent2, amount))
    }
    pub fn lerp_with_single_single_single(
        value1: f32,
        value2: f32,
        amount: f32,
    ) -> Result<f32, crate::Error> {
        Ok(value1 + (value2 - value1) * amount)
    }
    pub fn lerp_with_double_double_double(
        value1: f64,
        value2: f64,
        amount: f64,
    ) -> Result<f64, crate::Error> {
        Ok(value1 + (value2 - value1) * amount)
    }
    pub fn smooth_step_with_single_single_single(
        value1: f32,
        value2: f32,
        amount: f32,
    ) -> Result<f32, crate::Error> {
        let amount = Self::clamp_with_single_single_single(amount, 0.0, 1.0)?;
        Self::hermite_with_single_single_single_single_single(value1, 0.0, value2, 0.0, amount)
    }
    pub fn smooth_step_with_double_double_double(
        value1: f64,
        value2: f64,
        amount: f64,
    ) -> Result<f64, crate::Error> {
        let amount = Self::clamp_with_double_double_double(amount, 0.0, 1.0)?;
        Self::hermite_with_double_double_double_double_double(value1, 0.0, value2, 0.0, amount)
    }
    pub fn to_degrees(radians: f32) -> Result<f32, crate::Error> {
        Ok((f64::from(radians) * 57.295_779_513_082_32) as f32)
    }
    pub fn to_radians(degrees: f32) -> Result<f32, crate::Error> {
        Ok((f64::from(degrees) * 0.017_453_292_519_943_295) as f32)
    }

    pub fn md5_with_bytes(data: Vec<u8>) -> Result<Vec<u8>, crate::Error> {
        Ok(Md5::digest(data).to_vec())
    }
    pub fn md5_with_string(str: String) -> Result<String, crate::Error> {
        Ok(format!("$1${:x}", Md5::digest(str.as_bytes())))
    }
    pub fn md5_string(value: String) -> Result<String, crate::Error> {
        Ok(format!("{:x}", Md5::digest(value.as_bytes())))
    }
    pub fn sha1(data: Vec<u8>) -> Result<Vec<u8>, crate::Error> {
        Ok(Sha1::digest(data).to_vec())
    }
    pub fn sha1_string(value: String) -> Result<String, crate::Error> {
        Ok(lower_hex(&Sha1::digest(value.as_bytes())))
    }
    pub fn sha256(data: Vec<u8>) -> Result<Vec<u8>, crate::Error> {
        Ok(Sha256::digest(data).to_vec())
    }
    pub fn sha256_string(value: String) -> Result<String, crate::Error> {
        Ok(lower_hex(&Sha256::digest(value.as_bytes())))
    }
    pub fn pbkdf2(str: String) -> Result<String, crate::Error> {
        let mut salt = [0_u8; 32];
        getrandom::fill(&mut salt).map_err(|_| crate::Error::InvalidOperation)?;
        let hash = pbkdf2_sha1(str.as_bytes(), &salt, 10_000);
        let engine = base64::engine::general_purpose::STANDARD;
        Ok(format!("{}|{}", engine.encode(salt), engine.encode(hash)))
    }
    pub fn random_double() -> Result<f64, crate::Error> {
        let mut bytes = [0_u8; 8];
        getrandom::fill(&mut bytes).map_err(|_| crate::Error::InvalidOperation)?;
        Ok(((u64::from_le_bytes(bytes) >> 11) as f64) / ((1_u64 << 53) as f64))
    }

    pub fn get_running_platform() -> Result<UtilsPlatform, crate::Error> {
        #[cfg(target_os = "windows")]
        {
            return Ok(UtilsPlatform::Windows);
        }
        #[cfg(target_os = "macos")]
        {
            return Ok(UtilsPlatform::OSX);
        }
        #[cfg(target_os = "linux")]
        {
            return Ok(UtilsPlatform::Linux);
        }
        #[allow(unreachable_code)]
        Ok(UtilsPlatform::Unknown)
    }

    pub fn read_single_little_endian(src: &[u8], pos: i32) -> Result<f32, crate::Error> {
        crate::byte_order::read_single_little_endian(src, pos)
    }
    pub fn write_single_little_endian(
        dest: &mut [u8],
        pos: i32,
        value: f32,
    ) -> Result<(), crate::Error> {
        crate::byte_order::write_single_little_endian(dest, pos, value)
    }
    pub fn read_double_little_endian(src: &[u8], pos: i32) -> Result<f64, crate::Error> {
        crate::byte_order::read_double_little_endian(src, pos)
    }
    pub fn write_double_little_endian(
        dest: &mut [u8],
        pos: i32,
        value: f64,
    ) -> Result<(), crate::Error> {
        crate::byte_order::write_double_little_endian(dest, pos, value)
    }

    pub fn bytes_to_int16_with_bytes(bytes: Vec<u8>) -> Result<i16, crate::Error> {
        Self::bytes_to_int16_with_bytes_int32(bytes, 0)
    }
    pub fn bytes_to_int16_with_bytes_int32(bytes: Vec<u8>, pos: i32) -> Result<i16, crate::Error> {
        Ok(read_or_zero::<2>(&bytes, pos)?.map_or(0, i16::from_le_bytes))
    }
    pub fn bytes_to_int_with_bytes(bytes: Vec<u8>) -> Result<i32, crate::Error> {
        Self::bytes_to_int_with_bytes_int32(bytes, 0)
    }
    pub fn bytes_to_int_with_bytes_int32(bytes: Vec<u8>, pos: i32) -> Result<i32, crate::Error> {
        Ok(read_or_zero::<4>(&bytes, pos)?.map_or(0, i32::from_le_bytes))
    }
    pub fn bytes_to_int64_with_bytes(bytes: Vec<u8>) -> Result<i64, crate::Error> {
        Self::bytes_to_int64_with_bytes_int32(bytes, 0)
    }
    pub fn bytes_to_int64_with_bytes_int32(bytes: Vec<u8>, pos: i32) -> Result<i64, crate::Error> {
        Ok(read_or_zero::<8>(&bytes, pos)?.map_or(0, i64::from_le_bytes))
    }
    pub fn bytes_to_u_int16_with_bytes(bytes: Vec<u8>) -> Result<u16, crate::Error> {
        Self::bytes_to_u_int16_with_bytes_int32(bytes, 0)
    }
    pub fn bytes_to_u_int16_with_bytes_int32(
        bytes: Vec<u8>,
        pos: i32,
    ) -> Result<u16, crate::Error> {
        Ok(read_or_zero::<2>(&bytes, pos)?.map_or(0, u16::from_le_bytes))
    }
    pub fn bytes_to_u_int_with_bytes(bytes: Vec<u8>) -> Result<u32, crate::Error> {
        Self::bytes_to_u_int_with_bytes_int32(bytes, 0)
    }
    pub fn bytes_to_u_int_with_bytes_int32(bytes: Vec<u8>, pos: i32) -> Result<u32, crate::Error> {
        Ok(read_or_zero::<4>(&bytes, pos)?.map_or(0, u32::from_le_bytes))
    }
    pub fn bytes_to_u_int64(bytes: Vec<u8>) -> Result<u64, crate::Error> {
        Ok(read_or_zero::<8>(&bytes, 0)?.map_or(0, u64::from_le_bytes))
    }
    pub fn bytes_to_float(bytes: Vec<u8>, pos: i32) -> Result<f32, crate::Error> {
        Ok(f32::from_le_bytes(read_exact::<4>(&bytes, pos)?))
    }
    pub fn bytes_to_double(bytes: Vec<u8>, pos: i32) -> Result<f64, crate::Error> {
        Ok(f64::from_le_bytes(read_exact::<8>(&bytes, pos)?))
    }

    pub fn int16_to_bytes_with_int16(value: i16) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn int16_to_bytes_with_int16_bytes_int32(
        value: i16,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn u_int16_to_bytes_with_u_int16(value: u16) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn u_int16_to_bytes_with_u_int16_bytes_int32(
        value: u16,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn u_int16_to_bytes_big(value: u16, dest: &mut [u8], pos: i32) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_be_bytes())
    }
    pub fn int_to_bytes_with_int32(value: i32) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn int_to_bytes_big(value: i32) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_be_bytes().to_vec())
    }
    pub fn int_to_bytes_with_int32_bytes_int32(
        value: i32,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn u_int_to_bytes_with_u_int32(value: u32) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn u_int_to_bytes_with_u_int32_bytes_int32(
        value: u32,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn u_int_to_bytes_big(value: u32, dest: &mut [u8], pos: i32) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_be_bytes())
    }
    pub fn int64_to_bytes_with_int64(value: i64) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn int64_to_bytes_with_int64_bytes_int32(
        value: i64,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn u_int64_to_bytes_with_u_int64(value: u64) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn u_int64_to_bytes_big_with_u_int64(value: u64) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_be_bytes().to_vec())
    }
    pub fn u_int64_to_bytes_with_u_int64_bytes_int32(
        value: u64,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn u_int64_to_bytes_big_with_u_int64_bytes_int32(
        value: u64,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_be_bytes())
    }
    pub fn float_to_bytes_with_single(value: f32) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn float_to_bytes_with_single_bytes_int32(
        value: f32,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }
    pub fn double_to_bytes_with_double(value: f64) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_le_bytes().to_vec())
    }
    pub fn double_to_bytes_big(value: f64) -> Result<Vec<u8>, crate::Error> {
        Ok(value.to_be_bytes().to_vec())
    }
    pub fn double_to_bytes_with_double_bytes_int32(
        value: f64,
        dest: &mut [u8],
        pos: i32,
    ) -> Result<(), crate::Error> {
        write_at(dest, pos, &value.to_le_bytes())
    }

    pub fn u_int_to_hex_string(i: u32) -> Result<String, crate::Error> {
        Ok(format!("{i:08x}"))
    }
    pub fn bytes_to_string_with_bytes(mut bytes: Vec<u8>) -> Result<String, crate::Error> {
        if bytes.last() == Some(&0) {
            bytes.pop();
        }
        Ok(String::from_utf8_lossy(&bytes).into_owned())
    }
    pub fn bytes_to_string_with_bytes_int32_int32(
        bytes: Vec<u8>,
        index: i32,
        count: i32,
    ) -> Result<String, crate::Error> {
        let index = usize::try_from(index).map_err(|_| crate::Error::IndexOutOfRange)?;
        let count = usize::try_from(count).map_err(|_| crate::Error::Argument)?;
        let end = index.checked_add(count).ok_or(crate::Error::Argument)?;
        let mut count = count;
        if count == 0 && bytes.len() > index {
            return Err(crate::Error::IndexOutOfRange);
        }
        if bytes.len() > end && count > 0 && bytes[end - 1] == 0 {
            count -= 1;
        }
        let slice = bytes
            .get(index..index + count)
            .ok_or(crate::Error::Argument)?;
        Ok(String::from_utf8_lossy(slice).into_owned())
    }
    pub fn bytes_to_hex_string_with_bytes_string(
        bytes: Vec<u8>,
        field_name: String,
    ) -> Result<String, crate::Error> {
        Self::bytes_to_hex_string_with_bytes_int32_string(
            bytes.clone(),
            bytes.len() as i32,
            Some(field_name),
        )
    }
    pub fn bytes_to_hex_string_with_bytes_int32_string(
        bytes: Vec<u8>,
        length: i32,
        field_name: Option<String>,
    ) -> Result<String, crate::Error> {
        let length = usize::try_from(length).map_err(|_| crate::Error::Argument)?;
        if length > bytes.len() {
            return Err(crate::Error::Argument);
        }
        let mut output = String::new();
        for (line, chunk) in bytes[..length].chunks(16).enumerate() {
            if line > 0 {
                output.push('\n');
            }
            if let Some(name) = field_name.as_deref().filter(|name| !name.is_empty()) {
                output.push_str(name);
                output.push_str(": ");
            }
            for (index, byte) in chunk.iter().enumerate() {
                use std::fmt::Write as _;
                if index > 0 {
                    output.push(' ');
                }
                write!(&mut output, "{byte:02X}").expect("writing to String cannot fail");
            }
        }
        Ok(output)
    }
    pub fn string_to_bytes(str: String) -> Result<Vec<u8>, crate::Error> {
        if str.is_empty() {
            return Ok(Vec::new());
        }
        let mut bytes = str.into_bytes();
        if bytes.last() != Some(&0) {
            bytes.push(0);
        }
        Ok(bytes)
    }
    pub fn hex_string_to_bytes(
        hex_string: String,
        handle_dirty: bool,
    ) -> Result<Vec<u8>, crate::Error> {
        let cleaned = if handle_dirty {
            hex_string
                .chars()
                .filter(char::is_ascii_hexdigit)
                .collect::<String>()
        } else {
            hex_string
        };
        let usable = cleaned.len() & !1;
        (0..usable)
            .step_by(2)
            .map(|i| u8::from_str_radix(&cleaned[i..i + 2], 16).map_err(|_| crate::Error::Argument))
            .collect()
    }

    pub fn float_to_byte(val: f32, lower: f32, upper: f32) -> Result<u8, crate::Error> {
        let val = Self::clamp_with_single_single_single(val, lower, upper)?;
        Ok(((val - lower) / (upper - lower) * 255.0).floor() as u8)
    }
    pub fn byte_to_float_with_bytes_int32_single_single(
        bytes: Vec<u8>,
        pos: i32,
        lower: f32,
        upper: f32,
    ) -> Result<f32, crate::Error> {
        let Ok(pos) = usize::try_from(pos) else {
            return Err(crate::Error::IndexOutOfRange);
        };
        match bytes.get(pos) {
            Some(value) => Self::byte_to_float_with_byte_single_single(*value, lower, upper),
            None => Ok(0.0),
        }
    }
    pub fn byte_to_float_with_byte_single_single(
        val: u8,
        lower: f32,
        upper: f32,
    ) -> Result<f32, crate::Error> {
        let delta = upper - lower;
        let mut value = f32::from(val) * (1.0 / 255.0) * delta + lower;
        if value.abs() < delta * (1.0 / 255.0) {
            value = 0.0;
        }
        Ok(value)
    }
    pub fn u_int16_to_float_with_bytes_int32_single_single(
        bytes: Vec<u8>,
        pos: i32,
        lower: f32,
        upper: f32,
    ) -> Result<f32, crate::Error> {
        let value = Self::bytes_to_u_int16_with_bytes_int32(bytes, pos)?;
        Self::u_int16_to_float_with_u_int16_single_single(value, lower, upper)
    }
    pub fn u_int16_to_float_with_u_int16_single_single(
        val: u16,
        lower: f32,
        upper: f32,
    ) -> Result<f32, crate::Error> {
        let delta = upper - lower;
        let mut value = f32::from(val) * (1.0 / 65_535.0) * delta + lower;
        if value.abs() < delta * (1.0 / 65_535.0) {
            value = 0.0;
        }
        Ok(value)
    }
    pub fn float_to_u_int16(value: f32, lower: f32, upper: f32) -> Result<u16, crate::Error> {
        Ok(((value - lower) / (upper - lower) * 65_535.0) as u16)
    }

    pub fn try_parse_single(s: String, result: &mut f32) -> bool {
        if let Ok(value) = s.trim().parse() {
            *result = value;
            true
        } else {
            *result = 0.0;
            false
        }
    }
    pub fn try_parse_double(s: String, result: &mut f64) -> bool {
        if let Ok(value) = s.trim().parse() {
            *result = value;
            true
        } else {
            *result = 0.0;
            false
        }
    }
    pub fn try_parse_hex(s: String, result: &mut u32) -> bool {
        if let Ok(value) = u32::from_str_radix(s.trim(), 16) {
            *result = value;
            true
        } else {
            *result = 0;
            false
        }
    }
    pub fn enum_to_text(value: EnumValue) -> Result<String, crate::Error> {
        Ok(value.text.unwrap_or(value.name).to_owned())
    }
    pub fn enum_try_parse<T: EnumValueMetadata>(
        str_type: String,
        result: &mut Option<T>,
    ) -> Result<bool, crate::Error> {
        *result = T::parse_name(&str_type);
        Ok(result.is_some())
    }

    pub fn asset_type_to_string(type_: AssetType) -> Result<String, crate::Error> {
        indexed_name(&ASSET_TYPE_NAMES, type_ as i8 as i32)
    }
    pub fn string_to_asset_type(type_: String) -> Result<AssetType, crate::Error> {
        Ok(
            match ASSET_TYPE_NAMES.iter().position(|name| *name == type_) {
                Some(0) => AssetType::Texture,
                Some(1) => AssetType::Sound,
                Some(2) => AssetType::CallingCard,
                Some(3) => AssetType::Landmark,
                Some(4) => AssetType::Script,
                Some(5) => AssetType::Clothing,
                Some(6) => AssetType::Object,
                Some(7) => AssetType::Notecard,
                Some(8) => AssetType::Folder,
                Some(10) => AssetType::LSLText,
                Some(11) => AssetType::LSLBytecode,
                Some(12) => AssetType::TextureTGA,
                Some(13) => AssetType::Bodypart,
                Some(17) => AssetType::SoundWAV,
                Some(18) => AssetType::ImageTGA,
                Some(19) => AssetType::ImageJPEG,
                Some(20) => AssetType::Animation,
                Some(21) => AssetType::Gesture,
                Some(22) => AssetType::Simstate,
                Some(24) => AssetType::Link,
                Some(25) => AssetType::LinkFolder,
                Some(49) => AssetType::Mesh,
                _ => AssetType::Unknown,
            },
        )
    }
    pub fn folder_type_to_string(type_: FolderType) -> Result<String, crate::Error> {
        indexed_name(&FOLDER_TYPE_NAMES, type_ as i8 as i32)
    }
    pub fn string_to_folder_type(type_: String) -> Result<FolderType, crate::Error> {
        Ok(
            FolderType::parse_name(folder_variant_for_wire(&type_).unwrap_or("None"))
                .unwrap_or(FolderType::None),
        )
    }
    pub fn inventory_type_to_string(type_: InventoryType) -> Result<String, crate::Error> {
        indexed_name(&INVENTORY_TYPE_NAMES, i32::from(type_.raw()))
    }
    pub fn string_to_inventory_type(type_: String) -> Result<InventoryType, crate::Error> {
        Ok(INVENTORY_TYPE_NAMES
            .iter()
            .position(|name| *name == type_)
            .map_or(InventoryType::UNKNOWN, |index| {
                InventoryType::from_raw(index as i8)
            }))
    }
    pub fn sale_type_to_string(type_: SaleType) -> Result<String, crate::Error> {
        indexed_name(&SALE_TYPE_NAMES, type_ as u8 as i32)
    }
    pub fn string_to_sale_type(value: String) -> Result<SaleType, crate::Error> {
        Ok(
            match SALE_TYPE_NAMES.iter().position(|name| *name == value) {
                Some(1) => SaleType::Original,
                Some(2) => SaleType::Copy,
                Some(3) => SaleType::Contents,
                _ => SaleType::Not,
            },
        )
    }
    pub fn string_to_attachment_point(value: String) -> Result<AttachmentPoint, crate::Error> {
        let Some(index) = ATTACHMENT_POINT_NAMES
            .iter()
            .position(|name| *name == value)
        else {
            return Ok(AttachmentPoint::Default);
        };
        AttachmentPoint::parse_name(attachment_variant(index)).ok_or(crate::Error::Argument)
    }

    pub fn copy_bytes(bytes: Option<Vec<u8>>) -> Result<Option<Vec<u8>>, crate::Error> {
        Ok(bytes)
    }
    pub fn u_ints_to_long(a: u32, b: u32) -> Result<u64, crate::Error> {
        Ok((u64::from(a) << 32) | u64::from(b))
    }
    pub fn long_to_u_ints(a: u64, b: &mut u32, c: &mut u32) -> Result<(), crate::Error> {
        *b = (a >> 32) as u32;
        *c = a as u32;
        Ok(())
    }
    pub fn ip_to_u_int(address: IpAddr) -> Result<u32, crate::Error> {
        let bytes = match address {
            IpAddr::V4(ip) => ip.octets(),
            IpAddr::V6(ip) => {
                let octets = ip.octets();
                [octets[0], octets[1], octets[2], octets[3]]
            }
        };
        Ok(u32::from_le_bytes(bytes))
    }
    pub fn get_unix_time() -> Result<u32, crate::Error> {
        Ok(SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|_| crate::Error::InvalidOperation)?
            .as_secs() as u32)
    }
    pub fn unix_time_to_date_time_with_u_int32(timestamp: u32) -> Result<SystemTime, crate::Error> {
        Ok(UNIX_EPOCH + Duration::from_secs(u64::from(timestamp)))
    }
    pub fn unix_time_to_date_time_with_int32(timestamp: i32) -> Result<SystemTime, crate::Error> {
        Self::unix_time_to_date_time_with_u_int32(timestamp as u32)
    }
    pub fn date_time_to_unix_time(time: SystemTime) -> Result<u32, crate::Error> {
        Ok(match time.duration_since(UNIX_EPOCH) {
            Ok(duration) => duration.as_secs() as u32,
            Err(error) => (-(error.duration().as_secs() as i64)) as u32,
        })
    }
    pub fn swap<T>(lhs: &mut Option<T>, rhs: &mut Option<T>) -> Result<(), crate::Error> {
        std::mem::swap(lhs, rhs);
        Ok(())
    }
    pub fn swap_words(value: u8) -> Result<u8, crate::Error> {
        Ok(value.rotate_left(4))
    }
    pub fn hostname_to_i_pv4(hostname: String) -> Result<Option<IpAddr>, crate::Error> {
        if let Ok(ip) = hostname.parse() {
            return Ok(Some(ip));
        }
        let addresses = (hostname.as_str(), 0)
            .to_socket_addrs()
            .map_err(|_| crate::Error::Socket)?;
        Ok(addresses.map(|address| address.ip()).find(IpAddr::is_ipv4))
    }
}

fn hermite_f64(value1: f64, tangent1: f64, value2: f64, tangent2: f64, amount: f64) -> f64 {
    if amount == 0.0 {
        return value1;
    }
    if amount == 1.0 {
        return value2;
    }
    let cubed = amount * amount * amount;
    let squared = amount * amount;
    (2.0 * value1 - 2.0 * value2 + tangent2 + tangent1) * cubed
        + (3.0 * value2 - 3.0 * value1 - 2.0 * tangent1 - tangent2) * squared
        + tangent1 * amount
        + value1
}

fn read_or_zero<const N: usize>(bytes: &[u8], pos: i32) -> Result<Option<[u8; N]>, crate::Error> {
    let pos = usize::try_from(pos).map_err(|_| crate::Error::IndexOutOfRange)?;
    let Some(end) = pos.checked_add(N) else {
        return Ok(None);
    };
    Ok(bytes.get(pos..end).and_then(|slice| slice.try_into().ok()))
}

fn read_exact<const N: usize>(bytes: &[u8], pos: i32) -> Result<[u8; N], crate::Error> {
    read_or_zero(bytes, pos)?.ok_or(crate::Error::Argument)
}

fn write_at(dest: &mut [u8], pos: i32, source: &[u8]) -> Result<(), crate::Error> {
    let pos = usize::try_from(pos).map_err(|_| crate::Error::IndexOutOfRange)?;
    for (offset, byte) in source.iter().copied().enumerate() {
        *dest
            .get_mut(
                pos.checked_add(offset)
                    .ok_or(crate::Error::IndexOutOfRange)?,
            )
            .ok_or(crate::Error::IndexOutOfRange)? = byte;
    }
    Ok(())
}

fn indexed_name<const N: usize>(names: &[&str; N], index: i32) -> Result<String, crate::Error> {
    usize::try_from(index)
        .ok()
        .and_then(|index| names.get(index))
        .map(|name| (*name).to_owned())
        .ok_or(crate::Error::IndexOutOfRange)
}

fn lower_hex(bytes: &[u8]) -> String {
    use std::fmt::Write as _;
    let mut output = String::with_capacity(bytes.len() * 2);
    for byte in bytes {
        write!(&mut output, "{byte:02x}").expect("writing to String cannot fail");
    }
    output
}

fn hmac_sha1(key: &[u8], data: &[u8]) -> [u8; 20] {
    let mut block = [0_u8; 64];
    if key.len() > block.len() {
        block[..20].copy_from_slice(&Sha1::digest(key));
    } else {
        block[..key.len()].copy_from_slice(key);
    }
    let mut inner_pad = [0x36_u8; 64];
    let mut outer_pad = [0x5c_u8; 64];
    for index in 0..64 {
        inner_pad[index] ^= block[index];
        outer_pad[index] ^= block[index];
    }
    let mut inner = Sha1::new();
    inner.update(inner_pad);
    inner.update(data);
    let inner_hash = inner.finalize();
    let mut outer = Sha1::new();
    outer.update(outer_pad);
    outer.update(inner_hash);
    outer.finalize().into()
}

fn pbkdf2_sha1(password: &[u8], salt: &[u8], iterations: usize) -> [u8; 20] {
    let mut initial = Vec::with_capacity(salt.len() + 4);
    initial.extend_from_slice(salt);
    initial.extend_from_slice(&1_u32.to_be_bytes());
    let mut current = hmac_sha1(password, &initial);
    let mut result = current;
    for _ in 1..iterations {
        current = hmac_sha1(password, &current);
        for (result, byte) in result.iter_mut().zip(current) {
            *result ^= byte;
        }
    }
    result
}

fn folder_variant_for_wire(value: &str) -> Option<&'static str> {
    let index = FOLDER_TYPE_NAMES.iter().position(|name| *name == value)?;
    Some(match index {
        0 => "Texture",
        1 => "Sound",
        2 => "CallingCard",
        3 => "Landmark",
        5 => "Clothing",
        6 => "Object",
        7 => "Notecard",
        8 => "Root",
        10 => "LSLText",
        13 => "BodyPart",
        14 => "Trash",
        15 => "Snapshot",
        16 => "LostAndFound",
        20 => "Animation",
        21 => "Gesture",
        23 => "Favorites",
        26..=45 => {
            if index == 45 {
                "EnsembleEnd"
            } else {
                "EnsembleStart"
            }
        }
        46 => "CurrentOutfit",
        47 => "Outfit",
        48 => "MyOutfits",
        49 => "Mesh",
        50 => "Inbox",
        51 => "Outbox",
        52 => "BasicRoot",
        53 => "MarketplaceListings",
        54 => "MarketplaceStock",
        _ => return None,
    })
}

fn attachment_variant(index: usize) -> &'static str {
    const VARIANTS: [&str; 56] = [
        "Default",
        "Chest",
        "Skull",
        "LeftShoulder",
        "RightShoulder",
        "LeftHand",
        "RightHand",
        "LeftFoot",
        "RightFoot",
        "Spine",
        "Pelvis",
        "Mouth",
        "Chin",
        "LeftEar",
        "RightEar",
        "LeftEyeball",
        "RightEyeball",
        "Nose",
        "RightUpperArm",
        "RightForearm",
        "LeftUpperArm",
        "LeftForearm",
        "RightHip",
        "RightUpperLeg",
        "RightLowerLeg",
        "LeftHip",
        "LeftUpperLeg",
        "LeftLowerLeg",
        "Stomach",
        "LeftPec",
        "RightPec",
        "HUDCenter2",
        "HUDTopRight",
        "HUDTop",
        "HUDTopLeft",
        "HUDCenter",
        "HUDBottomLeft",
        "HUDBottom",
        "HUDBottomRight",
        "Neck",
        "Root",
        "LeftHandRing",
        "RightHandRing",
        "TailBase",
        "TailTip",
        "LeftWing",
        "RightWing",
        "Jaw",
        "AltLeftEar",
        "AltRightEar",
        "AltLeftEye",
        "AltRightEye",
        "Tongue",
        "Groin",
        "LeftHindFoot",
        "RightHindFoot",
    ];
    VARIANTS[index]
}

#[cfg(test)]
mod tests {
    use super::Utils;
    use crate::{AssetType, AttachmentPoint, EnumInfoExtensions, PrimFlags};

    #[test]
    fn byte_and_string_conversions_match_protocol_sentinels() {
        assert_eq!(Utils::bytes_to_int_with_bytes(vec![1, 2, 3]), Ok(0));
        assert_eq!(
            Utils::bytes_to_int_with_bytes(vec![0x78, 0x56, 0x34, 0x12]),
            Ok(0x1234_5678)
        );
        assert_eq!(
            Utils::string_to_bytes("hello".to_owned()),
            Ok(b"hello\0".to_vec())
        );
        assert_eq!(
            Utils::bytes_to_string_with_bytes(b"hello\0".to_vec()),
            Ok("hello".to_owned())
        );
        assert_eq!(
            Utils::hex_string_to_bytes("z01 2x3".to_owned(), true),
            Ok(vec![0x01, 0x23])
        );
        assert_eq!(
            Utils::bytes_to_int_with_bytes_int32(vec![0; 4], -1),
            Err(crate::Error::IndexOutOfRange)
        );
        assert_eq!(
            Utils::bytes_to_string_with_bytes_int32_int32(vec![1], 0, 0),
            Err(crate::Error::IndexOutOfRange)
        );
        let dump = Utils::bytes_to_hex_string_with_bytes_int32_string(
            (0..17).collect(),
            17,
            Some("field".to_owned()),
        );
        assert_eq!(
            dump,
            Ok("field: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\nfield: 10".to_owned())
        );
    }

    #[test]
    fn hashes_and_numeric_packing_match_known_vectors() {
        assert_eq!(
            Utils::md5_string("abc".to_owned()),
            Ok("900150983cd24fb0d6963f7d28e17f72".to_owned())
        );
        assert_eq!(
            Utils::sha1_string("abc".to_owned()),
            Ok("a9993e364706816aba3e25717850c26c9cd0d89d".to_owned())
        );
        assert_eq!(
            Utils::sha256_string("abc".to_owned()),
            Ok("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".to_owned())
        );
        assert_eq!(
            Utils::u_ints_to_long(0x1122_3344, 0x5566_7788),
            Ok(0x1122_3344_5566_7788)
        );
        assert_eq!(
            super::pbkdf2_sha1(b"password", b"salt", 1),
            [
                0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60,
                0x12, 0x06, 0x2f, 0xe0, 0x37, 0xa6,
            ]
        );
    }

    #[test]
    fn enum_conversion_and_metadata_match_golden_names() {
        assert_eq!(
            Utils::asset_type_to_string(AssetType::LSLText),
            Ok("lsltext".to_owned())
        );
        assert_eq!(
            Utils::string_to_asset_type("material".to_owned()),
            Ok(AssetType::Unknown)
        );
        assert_eq!(
            Utils::string_to_attachment_point("ATTACH_HEAD".to_owned()),
            Ok(AttachmentPoint::Skull)
        );
        assert_eq!(
            Utils::enum_to_text(AssetType::Texture.into()),
            Ok("texture".to_owned())
        );
        assert_eq!(
            EnumInfoExtensions::get_text(PrimFlags::PHYSICS.into()),
            Ok(String::new())
        );
    }

    #[test]
    fn destination_writes_are_real_mutations() {
        let mut bytes = [0_u8; 6];
        assert_eq!(
            Utils::u_int_to_bytes_with_u_int32_bytes_int32(0x1234_5678, &mut bytes, 1),
            Ok(())
        );
        assert_eq!(bytes, [0, 0x78, 0x56, 0x34, 0x12, 0]);
    }
}
