#[cfg(feature = "sync")]
use std::sync;
#[cfg(not(feature = "sync"))]
use std::{cell, rc};

use std::{
    collections::{BTreeMap, BTreeSet},
    fmt::Display,
    hash::Hash,
    mem,
};

use itertools::{Itertools, Position};

use crate::{ContextWorker, Contextualizer, Escaper, Value};

#[cfg(feature = "sync")]
#[derive(Debug, Clone)]
pub enum MaybeWeak<T> {
    Weak(sync::Weak<sync::RwLock<T>>),
    Strong(sync::Arc<sync::RwLock<T>>),
}

#[derive(Debug, Clone)]
#[cfg(not(feature = "sync"))]
pub enum MaybeWeak<T> {
    Weak(rc::Weak<cell::RefCell<T>>),
    Strong(rc::Rc<cell::RefCell<T>>),
}

impl<T> MaybeWeak<T> {
    pub(crate) fn is_strong(&self) -> bool {
        matches!(self, Self::Strong(_))
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn new(value: T) -> MaybeWeak<T> {
        MaybeWeak::Strong(rc::Rc::new(cell::RefCell::new(value)))
    }

    #[cfg(feature = "sync")]
    pub(crate) fn new(value: T) -> MaybeWeak<T> {
        MaybeWeak::Strong(sync::Arc::new(sync::RwLock::new(value)))
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn replace(&self, value: T) -> T {
        mem::replace(&mut self.strong().borrow_mut(), value)
    }

    #[cfg(feature = "sync")]
    pub(crate) fn replace(&self, value: T) -> T {
        mem::replace(&mut self.strong().write().unwrap(), value)
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn strong(&self) -> rc::Rc<cell::RefCell<T>> {
        match self {
            Self::Strong(strong) => strong.clone(),
            Self::Weak(weak) => weak.upgrade().unwrap(),
        }
    }

    #[cfg(feature = "sync")]
    pub(crate) fn strong(&self) -> sync::Arc<sync::RwLock<T>> {
        match self {
            Self::Strong(strong) => strong.clone(),
            Self::Weak(weak) => weak.upgrade().unwrap(),
        }
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn weak(&self) -> MaybeWeak<T> {
        MaybeWeak::Weak(match self {
            Self::Strong(strong) => rc::Rc::downgrade(strong),
            Self::Weak(weak) => weak.clone(),
        })
    }

    #[cfg(feature = "sync")]
    pub(crate) fn weak(&self) -> MaybeWeak<T> {
        MaybeWeak::Weak(match self {
            Self::Strong(strong) => sync::Arc::downgrade(strong),
            Self::Weak(weak) => weak.clone(),
        })
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn ptr(&self) -> usize {
        self.strong().as_ptr() as usize
    }

    #[cfg(feature = "sync")]
    pub(crate) fn ptr(&self) -> usize {
        sync::Arc::as_ptr(&self.strong()) as usize
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        f(&*self.strong().borrow())
    }

    #[cfg(feature = "sync")]
    pub(crate) fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        f(&*self.strong().read().unwrap())
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
        let this_strong = self.strong();
        let other_strong = other.strong();

        rc::Rc::ptr_eq(&this_strong, &other_strong)
    }

    #[cfg(feature = "sync")]
    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
        let this_strong = self.strong();
        let other_strong = other.strong();

        sync::Arc::ptr_eq(&this_strong, &other_strong)
    }

    #[cfg(not(feature = "sync"))]
    pub(crate) fn clone_inner(&self) -> T
    where
        T: Clone,
    {
        self.strong().borrow().clone()
    }

    #[cfg(feature = "sync")]
    pub(crate) fn clone_inner(&self) -> T
    where
        T: Clone,
    {
        self.strong().read().unwrap().clone()
    }
}

impl<T> PartialEq for MaybeWeak<T> {
    fn eq(&self, other: &Self) -> bool {
        self.ptr_eq(other)
    }
}

impl<T> Eq for MaybeWeak<T> {}

impl<T> Hash for MaybeWeak<T> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        state.write_usize(self.ptr());
    }
}

impl<T: PartialOrd> PartialOrd for MaybeWeak<T> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.ptr().partial_cmp(&other.ptr())
    }
}

impl<T: Ord> Ord for MaybeWeak<T> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.ptr().cmp(&other.ptr())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
/// The type of a cookie cutter [`crate::Value`]. Since this is used in earlier stages than the
/// value more information is still available. For example the text type or whether it is a tuple
/// or an array.
pub enum Type {
    /// A text with a text type (see [`crate::Contextualizer`] for more information). Mirrors [`crate::Value::Text`].
    Text(String),
    /// A 64 bit integer. Mirrors [`crate::Value::Int`].
    Int,
    /// A 64 bit floating point number. Mirrors [`crate::Value::Float`].
    Float,
    /// A boolean. Mirrors [`crate::Value::Bool`].
    Bool,
    /// An array of values. The inner type may or may not be known (for example due to an empty
    /// array literal). Mirrors [`crate::Value::ArrayOrTuple`].
    Array(Option<Box<Type>>),
    /// A tuple of values. Mirrors [`crate::Value::ArrayOrTuple`].
    Tuple(Vec<Type>),
    /// A struct (key value pair) of values. Mirrors [`crate::Value::Struct`].
    Struct(BTreeMap<String, Type>),
    /// One of several posibilities.
    Sum(BTreeSet<Type>),
    /// A potentially recursive type (that can contain itself)
    Recursive {
        /// The actual recursive type
        payload: MaybeWeak<Type>,
        /// The name of this recursive type
        name: String,
    },
}

impl Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Text(s) => f.write_str(s),
            Self::Int => write!(f, "int"),
            Self::Float => write!(f, "float"),
            Self::Bool => write!(f, "bool"),
            Self::Array(None) => write!(f, "[?]"),
            Self::Array(Some(inner)) => write!(f, "[{inner}]"),
            Self::Recursive { payload, name } => {
                let is_strong = payload.is_strong();
                payload.with_ref(|payload| {
                    if is_strong {
                        write!(f, "<{name}>{payload}")
                    } else {
                        write!(f, "<{name}>")
                    }
                })
            }
            Self::Sum(options) => {
                for (pos, option) in options.iter().with_position() {
                    write!(f, "{option}")?;

                    if matches!(pos, Position::First | Position::Middle) {
                        f.write_str(" | ")?;
                    }
                }

                Ok(())
            }
            Self::Tuple(inner) => {
                f.write_str("(")?;

                for (pos, ty) in inner.iter().with_position() {
                    write!(f, "{ty}")?;

                    if matches!(pos, Position::First | Position::Middle) {
                        f.write_str(", ")?;
                    }
                }

                f.write_str(")")
            }
            Self::Struct(inner) => {
                f.write_str("{")?;

                for (pos, (key, ty)) in inner.iter().with_position() {
                    write!(f, "{key}: {ty}")?;

                    if matches!(pos, Position::First | Position::Middle) {
                        f.write_str(", ")?;
                    }
                }

                f.write_str("}")
            }
        }
    }
}

impl Type {
    /// Tries to unify this `Type` with another (returning `Ok` if successfull) and mutates this
    /// `Type` to be more specific based on the source type.
    /// Does not mutate `self` if `Err` is returned.
    pub(crate) fn unify<Ctx: Contextualizer<Esc, Wrk>, Esc: Escaper, Wrk: ContextWorker<Esc>>(
        &mut self,
        source: &Type,
        ctxer: &Ctx,
    ) -> Result<Option<Esc>, ()> {
        match (self, source) {
            (Type::Text(a), Type::Text(b)) => {
                if a == b {
                    return Ok(None);
                }

                ctxer.escaper(a, b).map(Some).map_err(|_| ())
            }
            (Type::Recursive { name: a, .. }, Type::Recursive { name: b, .. }) => {
                (a == b).then_some(None).ok_or(())
            }
            (Type::Sum(self_options), Type::Sum(source_options)) => {
                // the key may be mutible but you can't change it's PartialEq or Ord
                #[allow(clippy::mutable_key_type)]
                let unified = self_options.clone();

                let Ok(unified) = unified
                    .into_iter()
                    .zip(source_options.iter())
                    .map(|(mut self_option, source_option)| {
                        // we only permit escaping at the top level, if there is escaping
                        // required at a deeper level we abort
                        matches!(self_option.unify(source_option, ctxer), Ok(None))
                            .then_some(self_option)
                            .ok_or(())
                    })
                    .collect()
                else {
                    return Err(());
                };

                *self_options = unified;
                Ok(None)
            }
            (Type::Float, Type::Float)
            | (Type::Bool, Type::Bool)
            | (Type::Int, Type::Int)
            | (Type::Array(None | Some(_)), Type::Array(None)) => Ok(None),
            (Type::Array(Some(ref mut self_inner)), Type::Array(Some(ref source_inner))) => {
                self_inner.unify(source_inner, ctxer)
            }
            (Type::Array(ref mut self_inner @ None), Type::Array(Some(ref source_inner))) => {
                *self_inner = Some(source_inner.clone());
                Ok(None)
            }
            (Type::Struct(ref mut self_inner), Type::Struct(source_inner)) => {
                let mut unified = self_inner.clone();
                if unified
                    .iter_mut()
                    .zip(source_inner.iter())
                    .all(|(self_key, source_key)| {
                        self_key.0 == source_key.0
                                // we only permit escaping at the top level, if there is escaping
                                // required at a deeper level we abort
                            && matches!(self_key
                                .1
                                .unify(source_key.1, ctxer), Ok(None))
                    })
                {
                    *self_inner = unified;
                    Ok(None)
                } else {
                    Err(())
                }
            }
            (Type::Tuple(ref mut self_inner), Type::Tuple(ref source_inner)) => {
                let mut unified = self_inner.clone();
                if unified
                    .iter_mut()
                    .zip(source_inner.iter())
                    .all(|(ty_self, ty_source)| matches!(ty_self.unify(ty_source, ctxer), Ok(None)))
                {
                    *self_inner = unified;
                    Ok(None)
                } else {
                    Err(())
                }
            }
            _ => Err(()),
        }
    }

    /// substitutes a recusive type for its inner value once
    pub(crate) fn substitute_recursive_once(&mut self) {
        let Type::Recursive { payload, .. } = self else {
            return;
        };
        *self = payload.clone_inner();
    }

    pub(crate) fn matches(&self, value: &Value) -> bool {
        let mut this = self.clone();

        this.substitute_recursive_once();

        match (&this, value) {
            (&Self::Bool, &Value::Bool(_))
            | (&Self::Float, &Value::Float(_))
            | (&Self::Int, &Value::Int(_))
            | (&Self::Text(_), &Value::Text(_))
            | (&Self::Array(None), &Value::ArrayOrTuple(_)) => true,
            (Self::Sum(this_options), value) => {
                this_options.iter().any(|option| option.matches(value))
            }
            (&Self::Array(Some(ref ty)), Value::ArrayOrTuple(inner)) => {
                inner.iter().all(|value| ty.matches(value))
            }
            (Self::Tuple(inner), Value::ArrayOrTuple(values)) => {
                inner.len() == values.len()
                    && inner
                        .iter()
                        .zip(values)
                        .all(|(ty, value)| ty.matches(value))
            }
            (Self::Struct(types), Value::Struct(values)) => {
                types.len() == values.len()
                    && types
                        .iter()
                        .zip(values)
                        .all(|((ty_name, ty), (value_name, value))| {
                            ty_name == value_name && ty.matches(value)
                        })
            }
            _ => false,
        }
    }

    /// checks whether the type is printable and if so which text type it is if that information is
    /// available
    pub fn to_printable(&self) -> Option<Option<String>> {
        Some(match self {
            Type::Text(inner) => Some(inner.clone()),
            Type::Bool | Type::Float | Type::Int | Type::Recursive { .. } => None,
            Type::Array(inner) => return inner.as_deref().and_then(Type::to_printable),
            _ => return None,
        })
    }
}
