use std::borrow::Cow;

use indexmap::IndexMap;
use serde::de::DeserializeOwned;

use crate::{Error, Name, Result, Upload, Value};

/// A value accessor
pub struct ValueAccessor<'a>(&'a Value);

impl<'a> ValueAccessor<'a> {
    /// Returns `true` if the value is null, otherwise returns `false`
    #[inline]
    pub fn is_null(&self) -> bool {
        matches!(self.0, Value::Null)
    }

    /// Returns the boolean
    pub fn boolean(&self) -> Result<bool> {
        match self.0 {
            Value::Boolean(b) => Ok(*b),
            _ => Err(Error::new("internal: not a boolean")),
        }
    }

    /// Returns the enum name
    pub fn enum_name(&self) -> Result<&str> {
        match self.0 {
            Value::Enum(s) => Ok(s),
            Value::String(s) => Ok(s.as_str()),
            _ => Err(Error::new("internal: not an enum name")),
        }
    }

    /// Returns the number as `i64`
    pub fn i64(&self) -> Result<i64> {
        if let Value::Number(number) = self.0
            && let Some(value) = number.as_i64()
        {
            return Ok(value);
        }
        Err(Error::new("internal: not an signed integer"))
    }

    /// Returns the number as `u64`
    pub fn u64(&self) -> Result<u64> {
        if let Value::Number(number) = self.0
            && let Some(value) = number.as_u64()
        {
            return Ok(value);
        }
        Err(Error::new("internal: not an unsigned integer"))
    }

    /// Returns the number as `f32`
    pub fn f32(&self) -> Result<f32> {
        if let Value::Number(number) = self.0
            && let Some(value) = number.as_f64()
        {
            return Ok(value as f32);
        }
        Err(Error::new("internal: not a float"))
    }

    /// Returns the number as `f64`
    pub fn f64(&self) -> Result<f64> {
        if let Value::Number(number) = self.0
            && let Some(value) = number.as_f64()
        {
            return Ok(value);
        }
        Err(Error::new("internal: not a float"))
    }

    /// Returns the string value
    pub fn string(&self) -> Result<&'a str> {
        if let Value::String(value) = self.0 {
            Ok(value)
        } else {
            Err(Error::new("internal: not a string"))
        }
    }

    /// Returns the object accessor
    pub fn object(&self) -> Result<ObjectAccessor<'a>> {
        if let Value::Object(obj) = self.0 {
            Ok(ObjectAccessor(Cow::Borrowed(obj)))
        } else {
            Err(Error::new("internal: not an object"))
        }
    }

    /// Returns the list accessor
    pub fn list(&self) -> Result<ListAccessor<'a>> {
        if let Value::List(list) = self.0 {
            Ok(ListAccessor(list))
        } else {
            Err(Error::new("internal: not a list"))
        }
    }

    /// Deserialize the value to `T`
    pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T> {
        T::deserialize(self.0.clone()).map_err(|err| format!("internal: {}", err).into())
    }

    /// Returns a reference to the underlying `Value`
    #[inline]
    pub fn as_value(&self) -> &'a Value {
        self.0
    }

    /// Returns a upload object
    pub fn upload(&self) -> Result<Upload> {
        <Upload as crate::InputType>::parse(Some(self.0.clone()))
            .map_err(|_| Error::new("internal: not a upload"))
    }
}

/// A object accessor
pub struct ObjectAccessor<'a>(pub(crate) Cow<'a, IndexMap<Name, Value>>);

impl<'a> ObjectAccessor<'a> {
    /// Return a reference to the value stored for `key`, if it is present,
    /// else `None`.
    #[inline]
    pub fn get(&self, name: &str) -> Option<ValueAccessor<'_>> {
        self.0.get(name).map(ValueAccessor)
    }

    /// Like [`ObjectAccessor::get`], returns `Err` if the index does not exist
    #[inline]
    pub fn try_get(&self, name: &str) -> Result<ValueAccessor<'_>> {
        self.0
            .get(name)
            .map(ValueAccessor)
            .ok_or_else(|| Error::new(format!("internal: key \"{}\" not found", name)))
    }

    /// Return an iterator over the key-value pairs of the object, in their
    /// order
    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = (&Name, ValueAccessor<'_>)> + '_ {
        self.0
            .iter()
            .map(|(name, value)| (name, ValueAccessor(value)))
    }

    /// Return an iterator over the keys of the object, in their order
    #[inline]
    pub fn keys(&self) -> impl Iterator<Item = &Name> + '_ {
        self.0.keys()
    }

    /// Return an iterator over the values of the object, in their order
    #[inline]
    pub fn values(&self) -> impl Iterator<Item = ValueAccessor<'_>> + '_ {
        self.0.values().map(ValueAccessor)
    }

    /// Returns the number of elements in the object
    #[inline]
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Returns `true` if the object has no members
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a reference to the underlying IndexMap
    #[inline]
    pub fn as_index_map(&'a self) -> &'a IndexMap<Name, Value> {
        &self.0
    }
}

/// A list accessor
pub struct ListAccessor<'a>(pub(crate) &'a [Value]);

impl<'a> ListAccessor<'a> {
    /// Returns the number of elements in the list
    #[inline]
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Returns `true` if the list has a length of 0
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Returns an iterator over the list
    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = ValueAccessor<'_>> + '_ {
        self.0.iter().map(ValueAccessor)
    }

    /// Returns a reference to an element depending on the index
    #[inline]
    pub fn get(&self, idx: usize) -> Option<ValueAccessor<'_>> {
        self.0.get(idx).map(ValueAccessor)
    }

    /// Like [`ListAccessor::get`], returns `Err` if the index does not exist
    #[inline]
    pub fn try_get(&self, idx: usize) -> Result<ValueAccessor<'_>> {
        self.get(idx)
            .ok_or_else(|| Error::new(format!("internal: index \"{}\" not found", idx)))
    }

    /// Returns a new ListAccessor that represents a slice of the original
    #[inline]
    pub fn as_slice(&self, start: usize, end: usize) -> Result<ListAccessor<'a>> {
        if start <= end && end <= self.len() {
            Ok(ListAccessor(&self.0[start..end]))
        } else {
            Err(Error::new("internal: invalid slice indices"))
        }
    }

    /// Returns a reference to the underlying `&[Value]`
    #[inline]
    pub fn as_values_slice(&self) -> &'a [Value] {
        self.0
    }
}
