use crate::{Class, ContextRef, Error, Value, ValueRef, ValueResult};

pub trait TryIntoValue {
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult;
}

impl TryIntoValue for u8 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_u32(self)
    }
}

impl TryIntoValue for u16 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_u32(self)
    }
}

impl TryIntoValue for u32 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_u32(self)
    }
}

impl TryIntoValue for u64 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_u64(self)
    }
}

impl TryIntoValue for i8 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_i32(self)
    }
}

impl TryIntoValue for i16 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_i32(self)
    }
}

impl TryIntoValue for i32 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_i32(self)
    }
}

impl TryIntoValue for i64 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_i64(self)
    }
}

impl TryIntoValue for f32 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_f64(self)
    }
}

impl TryIntoValue for f64 {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_f64(self)
    }
}

impl TryIntoValue for bool {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        let result = ctx.new_bool(self);
        // eprintln!("{self} -> {result:?}");
        result
    }
}

impl TryIntoValue for &str {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        ctx.new_string(self)
    }
}

impl TryIntoValue for &ValueRef {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        Ok(self.dup(ctx))
    }
}

impl TryIntoValue for Value {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        Ok(self.dup(ctx))
    }
}

impl TryIntoValue for Error {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        match self {
            Error::TooManyClasses => Err(Error::TooManyClasses),
            Error::WrongClass(c) => Err(Error::WrongClass(c)),
            Error::UnexpectedNul => Err(Error::UnexpectedNul),
            Error::DifferentRuntime => Err(Error::DifferentRuntime),
            Error::InvalidType { expected, found } => Err(Error::InvalidType { expected, found }),
            Error::ArgumentCountMismatch { expected, received } => {
                Err(Error::ArgumentCountMismatch { expected, received })
            }
            Error::ConversionError(e) => Err(Error::ConversionError(e)),
            Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
            Error::Exception(v, d, s) => Err(Error::Exception(v.dup(ctx), d, s)),
            Error::OutOfMemory => Err(Error::OutOfMemory),
            Error::IOError(e) => Err(Error::IOError(e)),
            Error::ParseError(name, err) => Err(Error::ParseError(name, err)),
        }
    }
}

impl<T: Class> TryIntoValue for T {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        self.into_value(ctx)
    }
}

impl<T: TryIntoValue> TryIntoValue for Option<T> {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        match self {
            None => Ok(ctx.null()),
            Some(v) => v.try_into_value(ctx),
        }
    }
}

impl TryIntoValue for () {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        Ok(ctx.undefined())
    }
}

impl<T: TryIntoValue> TryIntoValue for &[T] {
    #[inline]
    fn try_into_value(self, ctx: &ContextRef) -> ValueResult {
        Ok(ctx.undefined())
    }
}
