// TODO: lifetimes when storing in e.g. statics
// TODO: either qualify everything or nothing

macro_rules! class_wrapper {
    ($class_name:ident($($argument_name:ident: $argument_type:ty),+)) => {
        crate::vscode_sys::macros::class_wrapper_inner! {
            ::napi::bindgen_prelude::FnArgs<($($argument_type,)*)>,
            ::napi::bindgen_prelude::FnArgs::from(($($argument_name,)*)),
            $class_name($($argument_name: $argument_type),+)
        }
    };
}

macro_rules! class_wrapper_empty {
    ($class_name:ident) => {
        crate::vscode_sys::macros::class_wrapper_inner! {
            (),
            (),
            $class_name()
        }
    };
}

macro_rules! class_wrapper_inner {
    ($constructor_args_type:ty, $constructor_args_initializer:expr, $class_name:ident($($argument_name:ident: $argument_type:ty),*)) => {
        pub struct $class_name<'env> {
            pub inner: ::napi::bindgen_prelude::Object<'env>,
        }

        impl<'env> $class_name<'env> {
            pub fn new(
                env: &napi::Env,
                $($argument_name: $argument_type),*
            ) -> Result<Self, ::napi::Error> {
                let vscode_object = VscodeContext::vscode(env)?;
                let constructor_prototype: ::napi::bindgen_prelude::Function<
                    'env,
                    $constructor_args_type,
                    ::napi::bindgen_prelude::Object<'env>,
                > = vscode_object.get_named_property(stringify!($class_name))?;

                let class_result: ::napi::bindgen_prelude::Unknown =
                    constructor_prototype.new_instance($constructor_args_initializer)?;
                let class_object = ::napi::bindgen_prelude::Object::from_unknown(class_result)?;

                Ok(Self {
                    inner: class_object,
                })
            }
        }

        impl<'env> ToNapiValue for &$class_name<'env> {
            unsafe fn to_napi_value(env: napi_env, val: Self) -> Result<napi_value, napi::Error> {
                unsafe { bindgen_prelude::Object::to_napi_value(env, val.inner) }
            }
        }

        impl<'env> ToNapiValue for $class_name<'env> {
            unsafe fn to_napi_value(env: napi_env, val: Self) -> Result<napi_value, napi::Error> {
                unsafe { bindgen_prelude::Object::to_napi_value(env, val.inner) }
            }
        }

        // TODO: validate non-optional fields
        // TODO: move validation logic into validatenapivalue???
        impl<'env> FromNapiValue for $class_name<'env> {
            unsafe fn from_napi_value(
                raw_env: napi_sys::napi_env,
                napi_val: napi_sys::napi_value,
            ) -> Result<Self, napi::Error> {
                let inner = unsafe { bindgen_prelude::Object::from_napi_value(raw_env, napi_val) }?;

                Ok(Self { inner })
            }
        }

        impl<'env> TypeName for $class_name<'env> {
            fn type_name() -> &'static str {
                bindgen_prelude::Object::type_name()
            }

            fn value_type() -> napi::ValueType {
                bindgen_prelude::Object::value_type()
            }
        }

        impl<'env> ValidateNapiValue for $class_name<'env> {
            unsafe fn validate(
                raw_env: napi_sys::napi_env,
                raw_class: napi_sys::napi_value,
            ) -> Result<napi_sys::napi_value, napi::Error> {
                let class = match unsafe { <bindgen_prelude::Object as ValidateNapiValue>::validate(raw_env, raw_class) } {
                    Ok(_validated_class) => bindgen_prelude::Object::from_raw(raw_env, raw_class),
                    Err(mut error) => {
                        error.reason = format!("Invalid type for {}: {}", stringify!($class_name), error.reason);

                        return Err(error);
                    }
                };

                let env = napi::Env::from_raw(raw_env);
                let vscode_object = VscodeContext::vscode(&env)?;

                let constructor: bindgen_prelude::Function<$constructor_args_type, bindgen_prelude::Object> = vscode_object.get_named_property(stringify!($class_name))?;

                if !class.instanceof(constructor)? {
                    return Err(napi::Error::from_reason(
                        concat!("Object is not an instance of ", stringify!($class_name)),
                    ));
                }

                // TODO: validate fields

                Ok(raw_class)
            }
        }
    };
}

pub(crate) use class_wrapper;
pub(crate) use class_wrapper_empty;
pub(crate) use class_wrapper_inner;