//! Some functions in the site component wasm ABI pass context to the Afia host.
//!
//! The Afia host will then pass the context back when it calls the site component's exported
//! functions.
//!
//! ```
//! // TODO: This `afia-component` crate will eventually abstract over these `extern`
//! //  functions. When that happens we'll want to update this example to use the abstraction.
//! //  Users of this crate won't be working directly with the low-level C ABI, they'll be using
//! //  higher level abstractions that this crate exposes.
//!
//! use afia_component::ComponentImports;
//!
//! #[export_name = "__afia__$create_instance"]
//! pub extern "C" fn __afia_create_instance() -> isize {
//!     123
//! }
//!
//! #[export_name = "__afia__$output$123$create_element"]
//! pub extern "C" fn create_element(context: isize, _inputs_ptr: isize, _inputs_len: usize) -> i64 {
//!     assert_eq!(context, 123);
//!
//!     let div = ComponentImports::new_dynamically_linked().create_element("div").unwrap();
//!     div.temporary_way_to_get_i64()
//! }
//! ```
//!
//! This module contains types that are useful when passing and receiving context from the Afia host.

use crate::context::crate_private::CratePrivate;

/// Some `afia-component` functions have a `context` parameter.
///
/// This `context` gets passed to Afia, and then Afia later passes the context back to the site
/// component.
///
/// Types that implement this `Context` trait can be used as `context`.
pub trait Context: sealed::Sealed {
    /// Get an `isize` representation of the `Context` .
    fn to_isize(&self, _: CratePrivate) -> isize;
}
mod sealed {
    pub trait Sealed {}
}
pub(crate) mod crate_private {
    pub struct CratePrivate;
}

impl Context for isize {
    fn to_isize(&self, _: CratePrivate) -> isize {
        *self
    }
}
impl sealed::Sealed for isize {}

impl<T> Context for *const T {
    fn to_isize(&self, _: CratePrivate) -> isize {
        *self as isize
    }
}
impl<T> sealed::Sealed for *const T {}

impl<T> Context for *mut T {
    fn to_isize(&self, _: CratePrivate) -> isize {
        *self as isize
    }
}
impl<T> sealed::Sealed for *mut T {}

/// Provides a safe abstraction over context that is a mutable pointer.
///
/// TODO: Not sure whether the `afia-component` will eventually abstract this away such that
///  the user does not need to use it... Still feeling out this `site-componenet-utils` crate...
#[repr(C)]
// rustc regression is making this trigger a dead code warning even though it shouldn't.
// can remove this `#[allow(dead_code)]` once the regression is fixed
// https://github.com/rust-lang/rust/issues/126706
#[allow(dead_code)]
pub struct ContextPtr<T>(*mut T);

/// Wraps a context pointer such that it can be passed to the Afia host, but it cannot be accessed
/// by the site component.
///
/// This is useful when the component is already holding an `&mut T` reference and wishes to avoid
/// unintentionally creating two mutable references.
///
/// TODO: Not sure whether the `afia-component` will eventually abstract this away such that
///  the user does not need to use it... so... not including an example for now.
#[repr(C)]
// rustc regression is making this trigger a dead code warning even though it shouldn't.
// can remove this `#[allow(dead_code)]` once the regression is fixed
// https://github.com/rust-lang/rust/issues/126706
#[allow(dead_code)]
pub struct OpaqueContextPtr<T>(*mut T);

impl<T> ContextPtr<T> {
    /// Get a mutable reference to the value pointed to by a context pointer.
    ///
    /// ```
    /// // TODO: This `afia-component` crate will eventually abstract over these `extern`
    /// //  functions. When that happens we'll want to update this example to use the abstraction.
    /// //  Users of this crate won't be working directly with the low-level C ABI, they'll be using
    /// //  higher level abstractions that this crate exposes.
    ///
    /// use afia_component::ComponentImports;
    /// use afia_component::context::ContextPtr;
    ///
    /// struct Context {
    ///     imports: ComponentImports,
    ///     label: &'static str
    /// }
    ///
    /// #[export_name = "__afia__$create_instance"]
    /// pub extern "C" fn create_instance() -> *mut Context {
    ///     Context::new_raw_with_imports_label(ComponentImports::new_dynamically_linked(), "world")
    /// }
    ///
    /// #[export_name = "__afia__$output$123$create_element"]
    /// pub extern "C" fn create_element(mut context: ContextPtr<Context>, _inputs_ptr: isize, _inputs_len: usize) -> i64 {
    ///     let div = context.with_ref_mut(|ctx, _| {
    ///         assert_eq!(ctx.label, "world");
    ///
    ///         let div = ctx.imports.create_element("div").unwrap();
    ///         div.set_attribute("hello", ctx.label);
    ///
    ///         div
    ///     });
    ///     div.temporary_way_to_get_i64()
    /// }
    ///
    /// impl Context {
    ///     fn new_raw_with_imports_label(imports: ComponentImports, label: &'static str) -> *mut Context {
    ///         Box::into_raw(Box::new(Context { imports, label }))
    ///     }
    /// }
    /// ```
    pub fn with_ref_mut<Ret>(
        &mut self,
        run: impl FnOnce(&mut T, OpaqueContextPtr<T>) -> Ret,
    ) -> Ret {
        let val = unsafe { &mut *self.0 };
        let opaque = OpaqueContextPtr(self.0);
        run(val, opaque)
    }
}

impl<T> Context for OpaqueContextPtr<T> {
    fn to_isize(&self, _: CratePrivate) -> isize {
        self.0 as isize
    }
}
impl<T> sealed::Sealed for OpaqueContextPtr<T> {}

impl<T> OpaqueContextPtr<T> {
    /// Get an `i32` that can get sent from the component to the Afia host.
    ///
    /// TODO: Make the `afia-component` methods that call the Afia host take this
    ///  [`OpaqueContextPointer`] as an argument and then we can use [`Context::to_isize`]
    ///  to convert it to `isize` from within `afia-component`.
    ///  Then we can delete this `to_i32` method.
    ///  This way the site component can't get access to the underlying value, making this
    ///  a truly opaque wrapper.
    pub fn to_i32(&self) -> i32 {
        self.0 as i32
    }
}
