use std::collections::BTreeMap;

use log::debug;
use log::warn;
use shvproto::MetaMap;
use shvproto::RpcValue;
use shvproto::Map as RpcMap;
use crate::metamethod::AccessLevel;
use crate::metamethod::DirAttribute;
use crate::util::join_path;

pub const KEY_DEVICE_TYPE: &str = "deviceType";
pub const KEY_TYPE_NAME: &str = "typeName";
pub const KEY_LABEL: &str = "label";
pub const KEY_DESCRIPTION: &str = "description";
pub const KEY_UNIT: &str = "unit";
pub const KEY_NAME: &str = "name";
pub const KEY_TYPE: &str = "type";
pub const KEY_VALUE: &str = "value";
pub const KEY_FIELDS: &str = "fields";
pub const KEY_SAMPLE_TYPE: &str = "sampleType";
pub const KEY_RESTRICTION_OF_DEVICE: &str = "restrictionOfDevice";
pub const KEY_RESTRICTION_OF_TYPE: &str = "restrictionOfType";
pub const KEY_SITE_SPECIFIC_LOCALIZATION: &str = "siteSpecificLocalization";
pub const KEY_TAGS: &str = "tags";
pub const KEY_METHODS: &str = "methods";
pub const KEY_BLACKLIST: &str = "blacklist";
pub const KEY_DEC_PLACES: &str = "decPlaces";
pub const KEY_VISUAL_STYLE: &str = "visualStyle";
pub const KEY_ALARM: &str = "alarm";
pub const KEY_STATE_ALARM: &str = "stateAlarm";
pub const KEY_ALARM_LEVEL: &str = "alarmLevel";

const METH_LS: &str = "ls";
const METH_DIR: &str = "dir";

fn merge_tags(mut map: RpcMap) -> RpcMap {
    if let Some(rv) = map.remove(KEY_TAGS)
        && let shvproto::Value::Map(tags) = rv.value {
            map.extend(*tags);
    }
    map
}

fn set_data_value(descr: &mut (impl TypeDescriptionMethods + ?Sized), key: impl Into<String>, value: impl Into<RpcValue>) {
    descr.data_mut().insert(key.into(), value.into());
}

pub trait TypeDescriptionMethods {
    fn data(&self) -> &RpcMap;
    fn data_mut(&mut self) -> &mut RpcMap;

    fn data_value(&self, key: impl AsRef<str>) -> Option<&RpcValue> {
        self.data().get(key.as_ref())
    }

    fn data_value_into<'a, T>(&'a self, key: impl AsRef<str>) -> Option<T>
    where
        T: TryFrom<&'a RpcValue>,
    {
        <T>::try_from(self.data_value(key)?).ok()
    }

    fn is_valid(&self) -> bool {
        !self.data().is_empty()
    }

    fn type_id(&self) -> Option<Type> {
        self.data_value_into::<i32>(KEY_TYPE).and_then(Type::try_from_i32)
    }

    fn set_type_id(&mut self, type_id: Type) {
        set_data_value(self, KEY_TYPE, type_id as i32);
    }

    fn type_name(&self) -> Option<&str> {
        if let Some(tn) = self.data_value_into::<&str>(KEY_TYPE_NAME) && !tn.is_empty() {
            Some(tn)
        } else {
            self.type_id().as_ref().map(Type::as_str)
        }
    }

    fn set_type_name(&mut self, type_name: impl AsRef<str>) {
        set_data_value(self, KEY_TYPE_NAME, type_name.as_ref());
        if self.type_id().is_none() && let Some(new_type_id) = Type::try_from_str(type_name.as_ref()) {
            set_data_value(self, KEY_TYPE, new_type_id as i32);
        }
    }

    fn sample_type(&self) -> Option<SampleType> {
        self.data_value_into::<i32>(KEY_SAMPLE_TYPE).and_then(SampleType::try_from_i32)
    }

    fn set_sample_type(&mut self, sample_type_id: SampleType) {
        set_data_value(self, KEY_SAMPLE_TYPE, sample_type_id as i32);
    }

    fn fields(&self) -> Vec<FieldDescription> {
        self.data_value_into::<Vec<FieldDescription>>(KEY_FIELDS).unwrap_or_default()
    }

    fn set_fields(&mut self, fields: Vec<FieldDescription>) {
        set_data_value(self, KEY_FIELDS, fields);
    }

    fn bit_range(&self) -> Option<&RpcValue> {
        self.data_value(KEY_VALUE)
    }

    fn bit_range_pair(&self) -> (u64, u64) {
        let Some(rv) = self.bit_range() else {
            return (0, 0);
        };

        if rv.is_list() {
            let mut it = Vec::<u64>::try_from(rv).unwrap_or_default().into_iter();
            let (b1, b2) = (it.next().unwrap_or_default(), it.next().unwrap_or_default());
            if b2 < b1 {
                warn!("Invalid bit specification: {val}", val = rv.to_cpon());
                (b1, b1)

            } else {
                (b1, b2)
            }
        } else {
            let val = rv.as_u64();
            (val, val)
        }
    }

    fn bitfield_value(&self, val: u64) -> u64 {
        if !self.is_valid() {
            return 0;
        }
        let (b1, b2) = self.bit_range_pair();
        let mask = if b2 >= 63 {
            u64::MAX
        } else {
            (1u64 << (b2 + 1)) - 1
        };

        debug!("bits: {b1} {b2} val: {val:#x} mask: {mask:#x}");

        let new_val = if b1 > 63 {
            0
        } else {
            (val & mask) >> b1
        };
        debug!("val masked and rotated right by: {b1} bits, new_val: {new_val:#x}");

        if b1 == b2 {
            (new_val != 0).into()
        } else {
            new_val
        }
    }

    fn set_bitfield_value(&self, bitfield: u64, val: u64) -> u64 {
        if !self.is_valid() {
            return val;
        }

        let (b1, b2) = self.bit_range_pair();

        // Width of the field in bits
        let width = b2.saturating_sub(b1) + 1;

        // Safe mask: width == 64 → all ones, else (1 << width) - 1
        let mask = if width >= 64 {
            u64::MAX
        } else {
            (1u64 << width) - 1
        };

        // Keep only the bits that fit into the field
        let val = val & mask;

        // Shift mask and value into correct position
        let shifted_mask = if b1 >= 64 {
            0 // shifting by 64 would overflow
        } else {
            mask << b1
        };

        let shifted_val = if b1 >= 64 {
            0
        } else {
            val << b1
        };

        // Clear the field and insert new value
        let mut bitfield = bitfield & !shifted_mask;
        bitfield |= shifted_val;
        bitfield
    }

    fn decimal_places(&self) -> Option<i64> {
        self.data_value_into(KEY_DEC_PLACES)
    }

    fn set_decimal_places(&mut self, n: i64) {
        set_data_value(self, KEY_DEC_PLACES, n);
    }

    fn unit(&self) -> Option<&str> {
        self.data_value_into(KEY_UNIT)
    }

    fn set_unit(&mut self, unit: impl AsRef<str>) {
        set_data_value(self, KEY_UNIT, unit.as_ref());
    }

    fn label(&self) -> Option<&str> {
        self.data_value_into(KEY_LABEL)
    }

    fn set_label(&mut self, label: impl AsRef<str>) {
        set_data_value(self, KEY_LABEL, label.as_ref());
    }

    fn description(&self) -> Option<&str> {
        self.data_value_into(KEY_DESCRIPTION)
    }

    fn set_description(&mut self, description: impl AsRef<str>) {
        set_data_value(self, KEY_DESCRIPTION, description.as_ref());
    }

    fn visual_style_name(&self) -> Option<&str> {
        self.data_value_into(KEY_VISUAL_STYLE)
    }

    fn set_visual_style_name(&mut self, visual_style_name: impl AsRef<str>) {
        set_data_value(self, KEY_VISUAL_STYLE, visual_style_name.as_ref());
    }

    fn restriction_of_type(&self) -> Option<&str> {
        self.data_value_into(KEY_RESTRICTION_OF_TYPE)
    }

    fn is_site_specific_localization(&self) -> bool {
        self.data_value_into(KEY_SITE_SPECIFIC_LOCALIZATION).unwrap_or_default()
    }

    fn field(&self, field_name: impl AsRef<str>) -> Option<FieldDescription> {
        self.data_value_into::<shvproto::List>(KEY_FIELDS)
            .unwrap_or_default()
            .into_iter()
            .filter_map(|rpc_value| FieldDescription::try_from(rpc_value).ok())
            .find(|descr| descr.name() == field_name.as_ref())
    }

    fn field_value(&self, val: impl Into<RpcValue>, field_name: impl AsRef<str>) -> Option<RpcValue> {
        let val = val.into();
        match self.type_id()? {
            Type::BitField => {
                self.field(field_name).map(|descr| descr.bitfield_value(val.as_u64()).into())
            }
            Type::Map => {
                if let shvproto::Value::Map(map) = &val.value {
                    map.get(field_name.as_ref()).cloned()
                } else {
                    None
                }
            }
            Type::Enum => {
                let descr = self.field(field_name)?;
                let v = descr.bit_range()?;
                <i64>::try_from(v).is_ok().then(|| v.clone())
            }
            _ => None,
        }
    }
}

pub trait FieldDescriptionMethods: TypeDescriptionMethods {
    fn name(&self) -> &str {
        self.data_value(KEY_NAME).map_or("", RpcValue::as_str)
    }
    fn set_name(&mut self, name: impl AsRef<str>) {
        set_data_value(self, KEY_NAME, name.as_ref());
    }
    fn alarm(&self) -> Option<&str> {
        self.data_value(KEY_ALARM).map(RpcValue::as_str)
    }
    fn set_alarm(&mut self, alarm: impl AsRef<str>) {
        set_data_value(self, KEY_ALARM, alarm.as_ref());
    }
    fn state_alarm(&self) -> Option<&str> {
        self.data_value(KEY_STATE_ALARM).map(RpcValue::as_str)
    }
    fn set_state_alarm(&mut self, alarm: impl AsRef<str>) {
        set_data_value(self, KEY_STATE_ALARM, alarm.as_ref());
    }
    fn alarm_level(&self) -> Option<i32> {
        self.data_value(KEY_ALARM_LEVEL).map(RpcValue::as_i32)
    }
}

// impl<T: FieldDescriptionMethods> TypeDescriptionMethods for T { }

#[derive(PartialEq)]
pub enum Type {
    BitField = 1,
    Enum,
    Bool,
    UInt,
    Int,
    Decimal,
    Double,
    String,
    DateTime,
    List,
    Map,
    IMap,
}

impl Type {
    pub fn as_str(&self) -> &'static str {
        match self {
            Type::BitField  => "BitField",
            Type::Enum      => "Enum",
            Type::Bool      => "Bool",
            Type::UInt      => "UInt",
            Type::Int       => "Int",
            Type::Decimal   => "Decimal",
            Type::Double    => "Double",
            Type::String    => "String",
            Type::DateTime  => "DateTime",
            Type::List      => "List",
            Type::Map       => "Map",
            Type::IMap      => "IMap",
        }
    }

    pub fn try_from_i32(value: i32) -> Option<Self> {
        match value {
            v if v == Self::BitField as _ => Some(Self::BitField),
            v if v == Self::Enum as _ => Some(Self::Enum),
            v if v == Self::Bool as _ => Some(Self::Bool),
            v if v == Self::UInt as _ => Some(Self::UInt),
            v if v == Self::Int as _ => Some(Self::Int),
            v if v == Self::Decimal as _ => Some(Self::Decimal),
            v if v == Self::Double as _ => Some(Self::Double),
            v if v == Self::String as _ => Some(Self::String),
            v if v == Self::DateTime as _ => Some(Self::DateTime),
            v if v == Self::List as _ => Some(Self::List),
            v if v == Self::Map as _ => Some(Self::Map),
            v if v == Self::IMap as _ => Some(Self::IMap),
            _ => None,
        }
    }

    pub fn try_from_str(s: &str) -> Option<Self> {
        match s {
            "BitField" => Some(Self::BitField),
            "Enum" => Some(Self::Enum),
            "Bool" => Some(Self::Bool),
            "UInt" => Some(Self::UInt),
            "Int" => Some(Self::Int),
            "Decimal" => Some(Self::Decimal),
            "Double" => Some(Self::Double),
            "String" => Some(Self::String),
            "DateTime" => Some(Self::DateTime),
            "List" => Some(Self::List),
            "Map" => Some(Self::Map),
            "IMap" => Some(Self::IMap),
            _ => None,
        }
    }
}

pub enum SampleType {
    Continuous = 1,
    Discrete
}

impl SampleType {
    pub fn as_str(&self) -> &'static str {
        match self {
            SampleType::Continuous => "Continuos",
            SampleType::Discrete => "Discrete",
        }
    }

    pub fn try_from_i32(value: i32) -> Option<Self> {
        match value {
            v if v == Self::Continuous as _ => Some(Self::Continuous),
            v if v == Self::Discrete as _ => Some(Self::Discrete),
            _ => None,
        }
    }

    pub fn try_from_str(s: &str) -> Option<Self> {
        match s {
            "Continuous" => Some(Self::Continuous),
            "Discrete" | "discrete" | "D" | "2" => Some(Self::Discrete),
            _ => None,
        }
    }
}

#[derive(Debug)]
pub struct TypeDescription {
    data: RpcMap,
}

impl TypeDescription {
    pub fn from_rpc_map(mut map: RpcMap) -> Self {
        // --- Extract fields and merge tags ---
        let src_fields: Vec<RpcValue> = map.remove("fields")
            .and_then(|v| v.try_into().ok())
            .unwrap_or_default();

        let fields = src_fields
            .into_iter()
            .map(|v| merge_tags(RpcMap::try_from(v).unwrap_or_default()))
            .collect::<Vec<_>>();

        let mut map = merge_tags(map);
        map.insert(KEY_FIELDS.into(),fields.into());
        let mut ret = Self { data: map };

        // --- Type name from KEY_TYPE_NAME ---
        if let Some(s) = ret.data_value_into::<String>(KEY_TYPE_NAME) {
            ret.set_type_name(s);
        }

        // --- Obsolete fallbacks ---
        if ret.type_id().is_none()
            && let Some(s) = ret.data_value_into::<String>(KEY_NAME)
        {
            ret.set_type_name(s);
        }
        if ret.type_id().is_none()
            && let Some(s) = ret.data_value_into::<String>(KEY_TYPE)
        {
            ret.set_type_name(s);
        }

        // --- Sample type ---
        if let Some(s) = ret.data_value_into::<&str>(KEY_SAMPLE_TYPE)
            && let Some(sample_type) = SampleType::try_from_str(s) {
                ret.set_sample_type(sample_type);
        }

        if ret.is_valid() && ret.sample_type().is_none() {
            ret.set_sample_type(SampleType::Continuous);
        }

        ret
    }

    pub fn new(t: Type, fields: Vec<FieldDescription>, sample_type: SampleType, tags: RpcMap) -> Self {
        let mut ret = Self { data: tags };
        ret.set_type_id(t);
        ret.set_fields(fields);
        ret.set_sample_type(sample_type);
        ret
    }
}

impl TypeDescriptionMethods for TypeDescription {
    fn data(&self) -> &RpcMap {
        &self.data
    }
    fn data_mut(&mut self) -> &mut RpcMap {
        &mut self.data
    }
}

impl TryFrom<RpcValue> for TypeDescription {
    type Error = String;

    fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
        let map: RpcMap = value.try_into()?;
        Ok(Self::from_rpc_map(map))
    }
}

impl TryFrom<&RpcValue> for TypeDescription {
    type Error = String;

    fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
        value.clone().try_into()
    }
}

// impl From<TypeDescription> for RpcValue {
//     fn from(value: TypeDescription) -> Self {
//         value.into_rpc_map().into()
//     }
// }

#[derive(Clone, Default, Debug, PartialEq)]
pub struct FieldDescription {
    data: RpcMap,
}

impl FieldDescription {
    pub fn from_rpc_map(map: RpcMap) -> Self {
        Self { data: merge_tags(map) }
    }

    pub fn into_rpc_map(self) -> RpcMap {
        let is_descr_empty = self.description().is_none_or(str::is_empty);
        let mut ret = self.data;
        if is_descr_empty {
            ret.remove(KEY_DESCRIPTION);
        }
        ret
    }
}

impl From<FieldDescription> for RpcValue {
    fn from(value: FieldDescription) -> Self {
        value.into_rpc_map().into()
    }
}

impl TryFrom<RpcValue> for FieldDescription {
    type Error = String;

    fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
        value.try_into().map(Self::from_rpc_map)
    }
}

impl TryFrom<&RpcValue> for FieldDescription {
    type Error = String;

    fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
        value.to_owned().try_into()
    }
}

impl TypeDescriptionMethods for FieldDescription {
    fn data(&self) -> &RpcMap {
        &self.data
    }
    fn data_mut(&mut self) -> &mut RpcMap {
        &mut self.data
    }
}
impl FieldDescriptionMethods for FieldDescription { }

#[derive(Clone, Default, Debug, PartialEq)]
pub struct PropertyDescription {
    base: FieldDescription,
}

impl PropertyDescription {
    // returns (Self, extra tags)
    pub fn from_rpc_map(mut map: RpcMap) -> (Self, RpcMap) {
        const KNOWN_TAGS: &[&str] = &[
            KEY_DEVICE_TYPE,
            // KEY_SUPER_DEVICE_TYPE,
            KEY_NAME,
            KEY_TYPE_NAME,
            KEY_LABEL,
            KEY_DESCRIPTION,
            KEY_UNIT,
            KEY_METHODS,
            "autoload",
            "autorefresh",
            "monitored",
            "monitorOptions",
            KEY_SAMPLE_TYPE,
            KEY_ALARM,
            KEY_STATE_ALARM,
            KEY_ALARM_LEVEL,
        ];

        let node_map = KNOWN_TAGS
            .iter()
            .filter_map(|tag| map.remove(*tag).map(|val| (tag.to_string(), val)))
            .filter(|(tag, _val)| tag != KEY_DEVICE_TYPE)
            .collect::<RpcMap>();

        (Self { base: FieldDescription { data: merge_tags(node_map) }}, map)
    }

    pub fn into_rpc_map(self) -> RpcMap {
        let methods = self.methods();
        let mut res = self.base.into_rpc_map();
        if !methods.is_empty() {
            res.insert(KEY_METHODS.into(), methods.into());
        }
        res
    }

    pub fn methods(&self) -> Vec<MethodDescription> {
        self.data_value_into(KEY_METHODS).unwrap_or_default()
    }

    pub fn method(&self, method_name: impl AsRef<str>) -> Option<MethodDescription> {
        self.data_value_into::<shvproto::List>(KEY_METHODS)
            .unwrap_or_default()
            .into_iter()
            .filter_map(|rpc_value| MethodDescription::try_from(rpc_value).ok())
            .find(|descr| descr.name == method_name.as_ref())
    }

    // pub fn add_method(&mut self, descr: MethodDescription) { }
    // pub fn set_method(&mut self, descr: MethodDescription) { }
}

impl TryFrom<RpcValue> for PropertyDescription {
    type Error = String;

    fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
        let map: RpcMap = value.try_into()?;
        let (res, _) = Self::from_rpc_map(map);
        Ok(res)
    }
}

impl TryFrom<&RpcValue> for PropertyDescription {
    type Error = String;

    fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
        value.clone().try_into()
    }
}

impl From<PropertyDescription> for RpcValue {
    fn from(value: PropertyDescription) -> Self {
        value.into_rpc_map().into()
    }
}

impl TypeDescriptionMethods for PropertyDescription {
    fn data(&self) -> &RpcMap {
        self.base.data()
    }
    fn data_mut(&mut self) -> &mut RpcMap {
        self.base.data_mut()
    }
}

impl FieldDescriptionMethods for PropertyDescription { }

#[derive(Clone, Debug)]
pub struct MethodDescription {
    pub name: String,
    pub flags: u32,
    pub access: AccessLevel,
    pub param: String,
    pub result: String,
    pub signals: Vec::<(String, Option<String>)>,
    pub description: String,
    pub extra: Option<RpcMap>,
}

impl Default for MethodDescription {
    fn default() -> Self {
        Self {
            name: String::default(),
            flags: u32::default(),
            access: AccessLevel::Browse,
            param: String::default(),
            result: String::default(),
            signals: Vec::default(),
            description: String::default(),
            extra: None,
        }
    }
}

const IKEY_EXTRA: i32 = (DirAttribute::Signals as i32) + 1;

impl TryFrom<RpcValue> for MethodDescription {
    type Error = String;

    fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
        match value.value {
            shvproto::Value::Map(map) => {
                Ok(Self {
                    name: map.get(DirAttribute::Name.into()).map(|rv| rv.as_str().to_owned()).unwrap_or_default(),
                    flags: map.get(DirAttribute::Flags.into()).map(shvproto::RpcValue::as_u32).unwrap_or_default(),
                    param: map.get(DirAttribute::Param.into()).map(|rv| rv.as_str().to_owned()).unwrap_or_default(),
                    result: map.get(DirAttribute::Result.into()).map(|rv| rv.as_str().to_owned()).unwrap_or_default(),
                    access: AccessLevel::try_from(
                            map.get(DirAttribute::AccessLevel.into())
                            .map(shvproto::RpcValue::as_i32)
                            .unwrap_or_default()
                        )
                        .unwrap_or(AccessLevel::Browse),
                    signals: {
                        let signals_map: RpcMap = map.get(DirAttribute::Signals.into()).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
                        signals_map.into_iter()
                            .map(|(k,v)| {
                                (k, v.try_into().ok())
                            })
                            .collect()
                    },
                    description: map.get(KEY_DESCRIPTION)
                        .map(|d| d.as_str().to_string())
                        .unwrap_or_default(),
                    extra: map.get(KEY_TAGS)
                        .map(RpcValue::as_map)
                        .cloned(),
                })
            }
            shvproto::Value::IMap(imap) => {
                let extra = imap.get(&IKEY_EXTRA);
                Ok(Self {
                    name: imap.get(&(DirAttribute::Name as _)).map(|rv| rv.as_str().to_string()).unwrap_or_default(),
                    flags: imap.get(&(DirAttribute::Flags as _)).map(RpcValue::as_u32).unwrap_or_default(),
                    param: imap.get(&(DirAttribute::Param as _)).map(|rv| rv.as_str().to_string()).unwrap_or_default(),
                    result: imap.get(&(DirAttribute::Result as _)).map(|rv| rv.as_str().to_string()).unwrap_or_default(),
                    access: AccessLevel::try_from(
                            imap.get(&(DirAttribute::AccessLevel as _))
                            .map(RpcValue::as_i32)
                            .unwrap_or_default()
                        )
                        .unwrap_or(AccessLevel::Browse),
                    signals: {
                        let signals_map: RpcMap = imap.get(&(DirAttribute::Signals as _)).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
                        signals_map.into_iter()
                            .map(|(k,v)| {
                                (k, v.try_into().ok())
                            })
                            .collect()
                    },
                    description: extra
                        .and_then(|extra| extra
                            .get("description")?.try_into().ok()
                        ).unwrap_or_default(),
                    extra: extra.map(RpcValue::as_map).cloned(),

                })
            }
            _ => Err(format!("Unexpected type: {type}", type = value.type_name())),
        }
    }
}


impl TryFrom<&RpcValue> for MethodDescription {
    type Error = String;

    fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
        value.clone().try_into()
    }
}

impl From<MethodDescription> for RpcValue {
    fn from(value: MethodDescription) -> Self {
        let mut res = shvproto::IMap::new();
        res.insert(DirAttribute::Name.into(), value.name.into());
        res.insert(DirAttribute::Flags.into(), value.flags.into());
        res.insert(DirAttribute::Param.into(), value.param.into());
        res.insert(DirAttribute::Result.into(), value.result.into());
        res.insert(DirAttribute::AccessLevel.into(), (value.access as i32).into());
        res.insert(DirAttribute::Signals.into(), value.signals
            .into_iter()
            .map(|(name, value)| (name, value.map_or_else(RpcValue::null, RpcValue::from)))
            .collect::<BTreeMap<_,_>>()
            .into()
        );
        let mut extra = shvproto::make_map!("description" => value.description);
        if let Some(extra_map) = value.extra {
            for (k, v) in extra_map {
                extra.entry(k).or_insert(v);
            }
        }
        res.insert(IKEY_EXTRA, extra.into());
        res.into()
    }
}

#[derive(Default, Debug, Clone)]
pub struct DeviceDescription {
    properties: Vec<PropertyDescription>,
    // restriction_of_device: String,
    // site_specific_localization: bool,
}

impl DeviceDescription {
    pub fn from_rpc_map(map: &RpcMap) -> Self {
        Self {
            properties: map.get("properties").and_then(|v| v.try_into().ok()).unwrap_or_default()
        }
    }

    pub fn into_rpc_map(self) -> RpcMap {
        shvproto::make_map!("properties" => RpcValue::from(self.properties))
    }

    pub fn find_property(&self, prop_name: impl AsRef<str>) -> Option<&PropertyDescription> {
        self.properties.iter().find(|prop| prop.name() == prop_name.as_ref())
    }

    pub fn find_longest_property_prefix(&self, path: impl AsRef<str>) -> Option<&PropertyDescription> {
        let path = path.as_ref();
        self.properties
            .iter()
            .filter(|prop| crate::util::starts_with_path(path, prop.name()))
            .max_by_key(|prop| prop.name().len())
    }

    pub fn set_property_description(&mut self, descr: PropertyDescription) {
        let prop_name = descr.name();
        if let Some(prop) = self.properties.iter_mut().find(|p| p.name() == prop_name) {
            *prop = descr;
        } else {
            self.properties.push(descr);
        }
    }

    pub fn remove_property_description(&mut self, prop_name: impl AsRef<str>) {
        let prop_name = prop_name.as_ref();
        self.properties.retain(|prop| prop.name() != prop_name);
    }

    pub fn properties(&self) -> &[PropertyDescription] {
        &self.properties
    }
}

impl TryFrom<RpcValue> for DeviceDescription {
    type Error = String;

    fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
        let map: RpcMap = value.try_into()?;
        Ok(Self::from_rpc_map(&map))
    }
}

impl TryFrom<&RpcValue> for DeviceDescription {
    type Error = String;

    fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
        let shvproto::Value::Map(map) = &value.value else {
            return Err(format!("Wrong type, expected a map, have {ty}", ty = value.type_name()))
        };
        Ok(Self::from_rpc_map(map))
    }
}

#[derive(Default, Debug)]
pub struct TypeInfo {
    /// type-name -> type-description
    types: BTreeMap<String, TypeDescription>,

    /// path -> device-type-name
    device_paths: BTreeMap<String, String>,

    /// device-type-name -> device-descr
    device_descriptions: BTreeMap<String, DeviceDescription>,

    /// shv-path -> tags
    extra_tags: BTreeMap<String, RpcValue>,

    /// shv-path-root -> system-path
    system_paths_roots: BTreeMap<String, String>,

    /// shv-path -> blacklist
    blacklisted_paths: BTreeMap<String, RpcValue>,

    /// should be empty, devices should not have different property descriptions for same device-type
    property_deviations: BTreeMap<String, PropertyDescription>,
}

#[derive(Default)]
pub struct PathInfo {
    pub device_path: String,
    pub device_type: String,
    pub property_description: PropertyDescription,
    pub field_path: String,
}

#[derive(Default)]
pub struct DeviceTypeInfo {
    pub device_path: String,
    pub device_type: String,
    pub property_path: String,
}

fn find_longest_prefix_entry<'a, V>(
    map: &'a BTreeMap<String, V>,
    path: &'a str,
) -> Option<(&'a str, &'a V)> {
    let mut path = path;
    loop {
        if let Some(val) = map.get(path) {
            return Some((path, val));
        }
        if path.is_empty() {
            break;
        }
        #[expect(clippy::string_slice, reason = "Assuming the path is an ASCII string")]
        if let Some(slash_ix) = path.rfind('/') {
            path = &path[..slash_ix];
        } else {
            path = "";
        }
    }
    None
}

impl TypeInfo {
    const VERSION: &str = "version";
    const PATHS: &str = "paths"; // SHV2
    const TYPES: &str = "types"; // SHV2 + SHV3
    const DEVICE_PATHS: &str = "devicePaths";
    const DEVICE_PROPERTIES: &str = "deviceProperties";
    const DEVICE_DESCRIPTIONS: &str = "deviceDescriptions";
    const PROPERTY_DEVIATIONS: &str = "propertyDeviations";
    const EXTRA_TAGS: &str = "extraTags";
    const SYSTEM_PATHS_ROOTS: &str = "systemPathsRoots";
    const BLACKLISTED_PATHS: &str = "blacklistedPaths";

    pub fn find_type_description(&self, type_name: impl AsRef<str>) -> Option<&TypeDescription> {
        self.types.get(type_name.as_ref())
    }

    pub fn find_property_description(
        &self,
        device_type: impl AsRef<str>,
        property_path: impl AsRef<str>,
    ) -> Option<(PropertyDescription, String)>
    {
        if let Some(dev_descr) = self.device_descriptions.get(device_type.as_ref()) {
            let property_path = property_path.as_ref();
            if let Some(prop_descr) = dev_descr.find_longest_property_prefix(property_path) {
                let own_property_path = prop_descr.name();
                let field_path = property_path.strip_prefix(own_property_path).unwrap_or_default();
                return Some((prop_descr.clone(), field_path.into()));
            }
        }
        None
    }

    pub fn find_device_type(&self, shv_path: impl AsRef<str>) -> Option<DeviceTypeInfo> {
        let shv_path = shv_path.as_ref();
        if let Some((device_path, device_type)) = find_longest_prefix_entry(&self.device_paths, shv_path) {
            let property_path = if device_path.is_empty() {
                shv_path.to_string()
            } else {
                shv_path.get(device_path.len() + 1 ..).unwrap_or("").to_string()
            };
            Some(DeviceTypeInfo {
                device_path: device_path.into(),
                device_type: device_type.into(),
                property_path
            })
        } else {
            None
        }
    }

    pub fn path_info(&self, shv_path: impl AsRef<str>) -> PathInfo {
        let mut ret = PathInfo::default();
        let mut deviation_found = false;
        let shv_path = shv_path.as_ref();

        if let Some((own_property_path, prop_descr)) = find_longest_prefix_entry(&self.property_deviations, shv_path) {
            deviation_found = true;
            ret.field_path = shv_path.strip_prefix(own_property_path).unwrap_or_default().into();
            ret.property_description = prop_descr.clone();
        }

        let DeviceTypeInfo { device_path, device_type, property_path } = self.find_device_type(shv_path).unwrap_or_default();
        ret.device_path = device_path;
        ret.device_type.clone_from(&device_type);

        if deviation_found {
            if ret.property_description.is_valid() {
                let name = shv_path
                    .strip_prefix(&ret.device_path)
                    .and_then(|s| s.strip_suffix(&ret.field_path))
                    .unwrap_or_default();
                ret.property_description.set_name(name);
            }
        } else {
            let (property_descr, field_path) = self.find_property_description(&device_type, &property_path).unwrap_or_default();
            if property_descr.is_valid() {
                ret.field_path = field_path;
                ret.property_description = property_descr;
            }
        }

        ret
    }

    pub fn type_description_for_path(&self, shv_path: impl AsRef<str>) -> Option<&TypeDescription> {
        let PathInfo { property_description, .. } = self.path_info(shv_path);
        self.find_type_description(property_description.type_name()?)
    }

    // NOTE: Users should just use path_info and extract only values field_path and property_description
    // pub fn property_description_for_path(shv_path: impl AsRef<str>, std::string *p_field_name) const

    pub fn set_blacklist(&mut self, shv_path: impl AsRef<str>, blacklist: &RpcValue) {
        let shv_path = shv_path.as_ref();
        match &blacklist.value {
            shvproto::Value::List(list) => {
                for path in list.as_ref() {
                    self.blacklisted_paths.insert(join_path(shv_path, path.as_str()), RpcValue::null());
                }
            }
            shvproto::Value::Map(map) => {
                for (path, val) in map.as_ref() {
                    self.blacklisted_paths.insert(join_path(shv_path, path), val.into());
                }
            }
            _ => { }
        }
    }

    pub fn types(&self) -> &BTreeMap<String, TypeDescription> {
        &self.types
    }

    pub fn device_paths(&self) -> &BTreeMap<String, String> {
        &self.device_paths
    }

    pub fn device_descriptions(&self) -> &BTreeMap<String, DeviceDescription> {
        &self.device_descriptions
    }

    pub fn extra_tags(&self) -> &BTreeMap<String, RpcValue> {
        &self.extra_tags
    }

    pub fn system_paths_roots(&self) -> &BTreeMap<String, String> {
        &self.system_paths_roots
    }

    pub fn blacklisted_paths(&self) -> &BTreeMap<String, RpcValue> {
        &self.blacklisted_paths
    }

    pub fn property_deviations(&self) -> &BTreeMap<String, PropertyDescription> {
        &self.property_deviations
    }
}

impl TryFrom<&RpcValue> for TypeInfo {
    type Error = String;

    fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
        let version = value.meta
            .as_ref()
            .and_then(|m| m.get(Self::VERSION).map(RpcValue::as_i64))
            .unwrap_or_default();

        let map: RpcMap = value.try_into()?;

        let mut res = Self::default();

        if version == 3 || version == 4 {
            res.types = map.get(Self::TYPES).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            res.device_paths = map.get(Self::DEVICE_PATHS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();

            if version == 3 {
                let m: RpcMap = map.get(Self::DEVICE_PROPERTIES).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
                for(device_type, prop_map) in m {
                    let pm = prop_map.as_map();
                    let dev_descr = res.device_descriptions.entry(device_type).or_insert_with(DeviceDescription::default);
                    for (property_path, property_descr_rv) in pm {
                        let mut property_descr: PropertyDescription = property_descr_rv.try_into().unwrap_or_default();
                        property_descr.set_name(property_path);
                        dev_descr.properties.push(property_descr);
                    }
                }
            }
            else {
                res.device_descriptions = map.get(Self::DEVICE_DESCRIPTIONS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            }
            res.extra_tags = map.get(Self::EXTRA_TAGS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            res.system_paths_roots = map.get(Self::SYSTEM_PATHS_ROOTS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            res.blacklisted_paths = map.get(Self::BLACKLISTED_PATHS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            res.property_deviations = map.get(Self::PROPERTY_DEVIATIONS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
        } else if map.contains_key(Self::PATHS) && map.contains_key(Self::TYPES) {
            // version 2
            res.types = map.get(Self::TYPES).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            let dev_descr = res.device_descriptions.entry("".into()).or_insert_with(DeviceDescription::default);
            let m: RpcMap = map.get(Self::PATHS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
            for (path, val) in m {
                let val_map: RpcMap = val.try_into().unwrap_or_default();
                let (mut descr, _) = PropertyDescription::from_rpc_map(val_map.clone());
                descr.set_type_name(val_map.get("type").map(RpcValue::as_str).unwrap_or_default());
                descr.set_name(path);
                dev_descr.properties.push(descr);
            }
        }
        else {
            let types: RpcMap = value.meta
                .as_ref()
                .and_then(|m| m.get("typeInfo"))
                .and_then(|ty| ty.as_map().get(Self::TYPES))
                .and_then(|rv| rv.try_into().ok())
                .unwrap_or_default();
            for (name, descr) in types {
                let mut descr = TypeDescription::from_rpc_map(descr.try_into().unwrap_or_default());
                if descr.type_name().is_none_or(str::is_empty) {
                    descr.set_type_name(&name);
                }
                res.types.insert(name, descr);
            }
            let node_types: RpcMap = value.meta
                .as_ref()
                .and_then(|m| m.get("nodeTypes"))
                .and_then(|rv| rv.try_into().ok())
                .unwrap_or_default();

            from_nodes_tree_helper(&node_types, value, Default::default(), Default::default(), Default::default(), None, &mut res);
        }

        if res.system_paths_roots.is_empty() {
            res.system_paths_roots.insert("".into(), "system".into());
        }

        Ok(res)
    }
}

impl TryFrom<RpcValue> for TypeInfo {
    type Error = String;
    fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
        (&value).try_into()
    }
}

fn from_nodes_tree_helper(
    node_types: &RpcMap,
    node: &RpcValue,
    device_type: &str,
    device_path: &str,
    property_path: &str,
    device_description: Option<&mut DeviceDescription>,
    out: &mut TypeInfo,
) {
    let node_map: RpcMap = node.try_into().unwrap_or_default();
    let meta_map_opt = node.meta.as_ref().map(Box::as_ref);

    // If both meta is none and map empty, treat the node as "invalid".
    if node_map.is_empty() && meta_map_opt.is_none_or(MetaMap::is_empty) {
        return;
    }

    if let Some(ref_str) = meta_map_opt.and_then(|m| m.get("nodeTypeRef").map(RpcValue::as_str))
        && !ref_str.is_empty()
        && let Some(ref_node) = node_types.get(ref_str)
    {
        from_nodes_tree_helper(
            node_types,
            ref_node,
            device_type,
            device_path,
            property_path,
            device_description,
            out,
        );
        return;
    }

    // current state
    let mut current_device_type = device_type.to_string();
    let mut current_device_path = device_path.to_string();
    let mut current_property_path = property_path.to_string();

    let mut new_device_type_entered = device_description.is_none();
    let mut new_device_description = DeviceDescription::default();
    let mut current_device_description = device_description.unwrap_or(&mut new_device_description);

    // collect property description tags merged from metadata tags and methods
    let mut property_descr_map = RpcMap::new();
    use shvproto::rpcvalue::List as RpcList;
    let mut property_methods = RpcList::new();

    // gather methods from node.meta.methods (skip ls/dir)
    if let Some(methods) = meta_map_opt.and_then(|m| m.get(KEY_METHODS).map(RpcValue::as_list)) {
        for mm in methods {
            let Ok(mm) = MethodDescription::try_from(mm) else {
                continue
            };
            if mm.name == METH_LS || mm.name == METH_DIR {
                continue
            }
            property_methods.push(RpcValue::from(mm.clone()));
            // if method has extra tags, stash them into extra_tags with path "property_path/method/<methodname>"
            if let Some(extra) = mm.extra && !extra.is_empty() {
                let key = crate::join_path!(property_path, "method", mm.name);
                out.extra_tags.insert(key, extra.into());
            }
        }
    }
    let node_tags = meta_map_opt.and_then(|m| m.get(KEY_TAGS).map(RpcValue::as_map).cloned()).unwrap_or_default();
    if !node_tags.is_empty() {
        if let Some(device_type) = node_tags.get(KEY_DEVICE_TYPE).map(RpcValue::as_str) && !device_type.is_empty() {
            current_device_type = device_type.into();
            current_device_path = join_path(device_path, property_path);
            current_property_path = "".into();
            current_device_description = &mut new_device_description;
            new_device_type_entered = true;
        }
        for (k, v) in &node_tags {
            property_descr_map.entry(k.into()).or_insert_with(|| v.into());
        }
    }
    // Erase shvgate obsolete tag
    property_descr_map.remove("createFromTypeName");
    if !property_methods.is_empty() {
        property_descr_map.insert(KEY_METHODS.to_string(), property_methods.into());
    }
    let mut property_descr = PropertyDescription::default();
    if !property_descr_map.is_empty() {
        let (descr, mut extra_tags) = PropertyDescription::from_rpc_map(property_descr_map);
        property_descr = descr;
        if property_descr.is_valid() {
            property_descr.set_name(&current_property_path);
            current_device_description.properties.push(property_descr.clone());
        }
        let system_path = extra_tags.remove("systemPath").and_then(|v| String::try_from(v).ok());
        if let Some(system_path) = system_path && !system_path.is_empty() {
            let shv_path = join_path(&current_device_path, &current_property_path);
            out.system_paths_roots.insert(shv_path, system_path);
        }
        let blacklist = extra_tags.remove(KEY_BLACKLIST);
        if let Some(blacklist) = blacklist {
            let shv_path = join_path(&current_device_path, &current_property_path);
            out.set_blacklist(shv_path, &blacklist);
        }
        if !extra_tags.is_empty() {
            let shv_path = join_path(&current_device_path, &current_property_path);
            out.extra_tags.insert(shv_path, extra_tags.into());
        }
    }
    if property_descr.type_name().is_none_or(str::is_empty) && !node_tags.contains_key("createFromTypeName") {
        // property with type-name defined cannot have child properties
        for (child_name, child_node) in &node_map {
            if child_name.is_empty() {
                continue;
            }
            let child_property_path = join_path(&current_property_path, child_name);
            from_nodes_tree_helper(node_types, child_node, &current_device_type, &current_device_path, &child_property_path, Some(current_device_description), out);
        }
    }

    // handle finished device type: if we entered a new device type, register device path and device description
    if new_device_type_entered {
        // set device path
        out.device_paths.insert(current_device_path.clone(), current_device_type.clone());

        // merge new_device_description into out.device_descriptions, checking for deviations
        if let Some(existing_device_descr) = out.device_descriptions.get_mut(&current_device_type) {
            // compare existing properties with new ones
            // for each existing prop, if not in new -> add empty deviation; if in both compare and set deviation if different
            for existing_property_descr in &existing_device_descr.properties {
                if let Some(new_property_descr) = new_device_description.find_property(existing_property_descr.name()) {
                    // defined in both definitions, compare them
                    if new_property_descr != existing_property_descr {
                        // they are not the same, create deviation
                        let shv_path = join_path(&current_device_path, existing_property_descr.name());
                        out.property_deviations.insert(shv_path, new_property_descr.clone());
                    }
                    let new_property_descr = new_property_descr.clone();
                    new_device_description.properties.retain(|descr| new_property_descr != *descr);
                } else {
					// property defined only in previous definition, add it as empty to deviations
                    let shv_path = join_path(&current_device_path, existing_property_descr.name());
                    out.property_deviations.insert(shv_path, PropertyDescription::default());
                }
            }
			// check remaining property paths in new definition
            for new_property_descr in &new_device_description.properties {
                let shv_path = join_path(&current_device_path, new_property_descr.name());
                out.property_deviations.insert(shv_path, new_property_descr.clone());
            }
        } else {
            // device type defined first time
            out.device_descriptions.insert(current_device_type, new_device_description);
        }
    }
}

#[cfg(test)]
mod tests {
    use shvproto::RpcValue;

    use crate::typeinfo::{FieldDescriptionMethods, SampleType, Type, TypeDescriptionMethods, TypeInfo};
    use std::collections::BTreeMap;

    const TYPE_INFO: &str = r#"
<"version":4>{
    "deviceDescriptions":{
        "device1":{
            "properties":[
                {
                    "name":"status1",
                    "typeName":"BitField"
                },
                {
                    "name":"status2",
                    "typeName":"Map"
                },
                {
                    "name":"status3",
                    "typeName":"Enum"
                },
            ]
        },
    },
    "devicePaths":{
        "foo/bar":"device1",
    },
    "types":{
        "BitField":{
            "fields":[
                {"alarm":"warning", "description":"Alarm 1", "label":"Alarm 1 label", "name":"field1", "value": [0,7] },
                {"alarm":"error", "description":"Alarm 2", "label":"Alarm 2 label", "name":"field2", "value": 24 },
                {"name":"field3", "value": [25, 26] },
                {"stateAlarm":"error", "description":"State alarm 1", "label":"State alarm 1 label", "name":"field4", "value": 27 },
            ],
            "typeName":"BitField"
        },
        "Map":{
            "fields":[
                {"description":"Description 1", "label":"Label 1", "name":"mapField1", "typeName":"Int"},
                {"description":"Description 2", "label":"Label 2", "name":"mapField2", "typeName":"String"},
            ],
            "typeName":"Map",
            "sampleType":"Discrete"
        },
        "Enum":{
            "fields":[
                {"description":"", "label":"", "name":"Unknown", "value":0},
                {"description":"", "label":"", "name":"Normal", "value":1},
                {
                    "alarm":"warning",
                    "alarmLevel":50,
                    "description":"",
                    "label":"",
                    "name":"Warning",
                    "value":2
                },
                {
                    "alarm":"error",
                    "stateAlarm":"error",
                    "alarmLevel":100,
                    "description":"",
                    "label":"",
                    "name":"Error",
                    "value":3
                }
            ],
            "typeName":"Enum"
        },
    }
}
"#;

    const NODES_TREE: &str = r#"
<
	"nodeTypes":{
        "Device1@0":<
            "tags": {"deviceType": "device1"}
        >{
            "status1":<
                "tags":{"description":"A bitfield", "typeName": "BitField"},
				"methods":[
					{"access":"rd", "name":"chng", "signature":"VoidParam"},
					{"access":"rd", "name":"get", "signature":"RetParam"}
				]
            >{},
            "status2":<
                "tags":{"description":"An enum", "typeName": "Enum"},
				"methods":[
					{"access":"rd", "name":"chng", "signature":"VoidParam"},
					{"access":"rd", "name":"get", "signature":"RetParam"}
				]
            >{},
        }
    },
	"tags":{"systemPath":"system/xyz"},
	"typeInfo":{
        "types":{
            "BitField":{
                "fields":[
                    {"tags":{"alarm":"warning", "description":"Alarm 1", "label":"Alarm 1 label"}, "name":"field1", "value": [0,7] },
                    {"tags":{"alarm":"error", "description":"Alarm 2", "label":"Alarm 2 label"}, "name":"field2", "value": 24 },
                    {"name":"field3", "value": [25, 26] },
                ],
                "type":"BitField"
            },
            "Map":{
                "fields":[
                    {"description":"Description 1", "label":"Label 1", "name":"mapField1", "typeName":"Int"},
                    {"description":"Description 2", "label":"Label 2", "name":"mapField2", "typeName":"String"},
                ],
                "type":"Map",
            },
            "Enum":{
                "fields":[
                    {"tags":{"description":"", "label":""}, "name":"Unknown", "value":0},
                    {"tags":{"description":"", "label":""}, "name":"Normal", "value":1},
                    {
                        "tags":{
                            "alarm":"warning",
                            "alarmLevel":50,
                            "description":"",
                            "label":""
                        },
                        "name":"Warning",
                        "value":2
                    },
                    {
                        "tags":{
                            "alarm":"error",
                            "alarmLevel":100,
                            "description":"",
                            "label":""
                        },
                        "name":"Error",
                        "value":3
                    }
                ],
                "type":"Enum"
            },
        }
    }
>{
	"foo":{
		"bar":<"nodeTypeRef":"Device1@0">{},
		"baz":<"nodeTypeRef":"Device1@0">{},
		//"bar":<"tags":{"deviceType":"device1"}>{},
		//"baz":<"tags":{"deviceType":"device1"}>{},
	}
}
"#;

    #[test]
    fn parse_type_info() {
        let rv = RpcValue::from_cpon(TYPE_INFO).unwrap_or_else(|e| panic!("Cannot parse typeInfo: {e}"));
        let type_info = TypeInfo::try_from(&rv).unwrap_or_else(|e| panic!("Cannot convert RpcValue to TypeInfo: {e}"));

        println!("type info: {type_info:#?}");

        let type_descr = type_info.type_description_for_path("foo/bar/status1").unwrap();
        assert!(matches!(type_descr.type_id(), Some(Type::BitField)));
        assert!(type_descr.field_value(0x1234, "field1").is_some_and(|v| v.as_u32() == 0x34));

        let bitfield_type_descr = type_info.find_type_description("BitField").unwrap();
        assert!(bitfield_type_descr.is_valid());
        assert!(matches!(bitfield_type_descr.type_id(), Some(Type::BitField)));
        assert!(bitfield_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Continuous)));
        assert_eq!(bitfield_type_descr.field_value(0xfffa, "field1").unwrap().as_u32(), 0xfa);
        assert_eq!(bitfield_type_descr.field_value(0x7fff_ffff, "field2").unwrap().as_u32(), 1);
        assert_eq!(bitfield_type_descr.field_value(0x7eff_ffff, "field2").unwrap().as_u32(), 0);
        assert_eq!(bitfield_type_descr.field_value(0x1cff_ffff, "field3").unwrap().as_u32(), 2);

        assert_eq!(bitfield_type_descr.field("field4").as_ref().and_then(FieldDescriptionMethods::state_alarm), Some("error"));

        let map_type_descr = type_info.find_type_description("Map").unwrap();
        assert!(matches!(map_type_descr.type_id(), Some(Type::Map)));
        assert!(map_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Discrete)));
        let vehicle_data: RpcValue = shvproto::make_map!("mapField1" => 123).into();
        assert_eq!(map_type_descr.field_value(&vehicle_data, "mapField1").unwrap().as_int(), 123);
        assert!(map_type_descr.field_value(&vehicle_data, "noMapField").is_none());

        let enum_type_descr = type_info.find_type_description("Enum").unwrap();
        assert!(matches!(enum_type_descr.type_id(), Some(Type::Enum)));
        assert!(enum_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Continuous)));
        assert_eq!(enum_type_descr.field_value((), "Warning").unwrap().as_u32(), 2);
        let field_alarms = BTreeMap::from([
            ("Unknown", (None, None)),
            ("Normal", (None, None)),
            ("Warning", (Some("warning"), Some(50))),
            ("Error", (Some("error"), Some(100))),
        ]);
        for fld in &enum_type_descr.fields() {
            assert!(field_alarms.get(fld.name()).is_some_and(|(alarm, level)| alarm == &fld.alarm() && level == &fld.alarm_level()));
        }

        assert_eq!(enum_type_descr.field("Error").as_ref().and_then(FieldDescriptionMethods::state_alarm), Some("error"));
    }

    #[test]
    fn parse_nodes_tree() {
        let rv = RpcValue::from_cpon(NODES_TREE).unwrap_or_else(|e| panic!("Cannot parse typeInfo: {e}"));
        let type_info = TypeInfo::try_from(&rv).unwrap_or_else(|e| panic!("Cannot convert RpcValue to TypeInfo: {e}"));

        println!("type info from nodes tree: {type_info:#?}");

        let type_descr = type_info.type_description_for_path("foo/bar/status1").unwrap();
        assert!(matches!(type_descr.type_id(), Some(Type::BitField)));
        assert!(type_descr.field_value(0x1234, "field1").is_some_and(|v| v.as_u32() == 0x34));

        let type_descr = type_info.type_description_for_path("foo/baz/status1").unwrap();
        assert!(matches!(type_descr.type_id(), Some(Type::BitField)));
        assert!(type_descr.field_value(0x1234, "field1").is_some_and(|v| v.as_u32() == 0x34));

        let enum_type_descr = type_info.type_description_for_path("foo/baz/status2").unwrap();
        // let enum_type_descr = type_info.find_type_description("Enum").unwrap();
        assert!(matches!(enum_type_descr.type_id(), Some(Type::Enum)));
        assert!(enum_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Continuous)));
        assert_eq!(enum_type_descr.field_value((), "Warning").unwrap().as_u32(), 2);
        let field_alarms = BTreeMap::from([
            ("Unknown", (None, None)),
            ("Normal", (None, None)),
            ("Warning", (Some("warning"), Some(50))),
            ("Error", (Some("error"), Some(100))),
        ]);
        for fld in &enum_type_descr.fields() {
            assert!(field_alarms.get(fld.name()).is_some_and(|(alarm, level)| alarm == &fld.alarm() && level == &fld.alarm_level()));
        }
    }
}
