use std::collections::BTreeMap;

use serde_cbor::Value as CborValue;

#[extension(pub trait CborValueGetters)]
impl CborValue {
    fn as_array(&self) -> Option<&[CborValue]> {
        let Self::Array(it) = self else { return None };
        Some(it)
    }

    fn as_array_mut(&mut self) -> Option<&mut Vec<CborValue>> {
        let Self::Array(it) = self else { return None };
        Some(it)
    }

    fn as_bool(&self) -> Option<bool> {
        let &Self::Bool(it) = self else { return None };
        Some(it)
    }

    fn as_f64(&self) -> Option<f64> {
        let &Self::Float(it) = self else { return None };
        Some(it)
    }

    fn as_i64(&self) -> Option<i64> {
        let &Self::Integer(it) = self else {
            return None;
        };
        it.try_into().ok()
    }

    fn as_null(&self) -> Option<()> {
        matches!(self, Self::Null).then(|| ())
    }

    fn as_object(&self) -> Option<&BTreeMap<CborValue, CborValue>> {
        let Self::Map(it) = self else { return None };
        Some(it)
    }

    fn as_object_mut(&mut self) -> Option<&mut BTreeMap<CborValue, CborValue>> {
        let Self::Map(it) = self else { return None };
        Some(it)
    }

    fn as_str(&self) -> Option<&str> {
        let Self::Text(it) = self else { return None };
        Some(it)
    }

    fn as_u64(&self) -> Option<u64> {
        let &Self::Integer(it) = self else {
            return None;
        };
        it.try_into().ok()
    }

    fn get(&self, i: impl sealed::Index) -> Option<&CborValue> {
        i.get(self)
    }

    fn get_mut(&mut self, i: impl sealed::Index) -> Option<&mut CborValue> {
        i.get_mut(self)
    }
}

mod sealed {
    use super::*;

    pub trait Index {
        fn get(self, value: &CborValue) -> Option<&CborValue>;
        fn get_mut(self, value: &mut CborValue) -> Option<&mut CborValue>;
    }

    // The mandatory string-cloning isn't great: there is room for improvement here
    // (e.g., the `Q = dyn Key` trick, although it would require a whole CborValueRef<'_> type
    // with the same `PartialOrd` semantics).
    impl Index for &str {
        fn get(self, value: &CborValue) -> Option<&CborValue> {
            value.as_object()?.get(&CborValue::Text(self.into()))
        }

        fn get_mut(self, value: &mut CborValue) -> Option<&mut CborValue> {
            value
                .as_object_mut()?
                .get_mut(&CborValue::Text(self.into()))
        }
    }

    impl Index for &String {
        fn get(self, value: &CborValue) -> Option<&CborValue> {
            <&str as Index>::get(self, value)
        }

        fn get_mut(self, value: &mut CborValue) -> Option<&mut CborValue> {
            <&str as Index>::get_mut(self, value)
        }
    }

    impl Index for usize {
        fn get(self, value: &CborValue) -> Option<&CborValue> {
            value.as_array()?.get(self)
        }

        fn get_mut(self, value: &mut CborValue) -> Option<&mut CborValue> {
            value.as_array_mut()?.get_mut(self)
        }
    }
}
