#![deny(missing_docs)]
#![doc = include_str!("../README.md")]

mod attribute;
pub mod callback;
pub mod context;
pub mod data_element_value;
pub mod dom;
mod event_listener;
pub mod input;
pub mod local_storage;
pub mod node_property;
pub mod stripe;

mod output_change;

#[cfg(feature = "macros")]
pub use afia_component_macro::*;

#[doc(hidden)]
pub mod _macro;

#[cfg(any(test, feature = "emulate-imports"))]
pub mod emulated;

/// A handle that can be used to call component imports such as [`ComponentImports::create_element`]
/// and [`ComponentImports::create_text`].
///
/// [`EmulatedImports`]: emulated::EmulatedImports
///
/// ## ComponentImports and `Clone`
/// Cloning a `ComponentImports` is cheap and does not allocate.
#[derive(Clone)]
pub struct ComponentImports {
    component_imports_ptr: *mut std::ffi::c_void,
    /// If the `ComponentImports` were created via [`emulated::EmulatedImports::component_imports`],
    /// then the `component_imports_ptr` will become invalid when the emulated imports are freed.
    ///
    /// This field ensures that the emulated imports will never be freed before this
    /// `ComponentImports`, meaning that the `component_imports_ptr` will always point to a valid
    /// [`emulated::EmulatedImports`].
    #[expect(
        unused,
        reason = "Only here to ensure that the emulated imports don't get dropped."
    )]
    #[cfg(any(test, feature = "emulate-imports"))]
    created_via_emulated_imports: Option<std::sync::Arc<emulated::EmulatedImportsPtr>>,
}
impl ComponentImports {
    /// Create a new `ComponentImports` that makes calls to functions that are provided by the
    /// Afia host.
    ///
    /// Use [`EmulatedImports::component_imports`] to create `ComponentImports` during tests.
    ///
    /// [`EmulatedImports`]: emulated::EmulatedImports
    pub const fn new_dynamically_linked() -> Self {
        Self {
            component_imports_ptr: std::ptr::null_mut(),
            #[cfg(any(test, feature = "emulate-imports"))]
            created_via_emulated_imports: None,
        }
    }

    /// Create a new `ComponentImports` that makes calls to emulated functions that are provided by
    /// an [`EmulatedImports`] instance.
    ///
    /// ## Examples
    /// ```
    /// # use afia_component::ComponentImports;
    ///
    /// let (emulated, imports) = ComponentImports::new_emulated();
    /// let div = imports.create_element("div").unwrap();
    /// div.set_attribute("class", "main-info");
    /// assert_eq!(
    ///     emulated.outer_html(div),
    ///     r#"<div class="main-info"></div>"#
    /// )
    /// ```
    ///
    /// [`EmulatedImports`]: emulated::EmulatedImports
    #[cfg(any(test, feature = "emulate-imports"))]
    pub fn new_emulated() -> (emulated::EmulatedImports, Self) {
        let emulated = emulated::EmulatedImports::new();
        let imports = emulated.component_imports();

        (emulated, imports)
    }

    /// Get the pointer to the component imports.
    pub(crate) fn pointer(&self) -> *const std::ffi::c_void {
        self.component_imports_ptr
    }
}

/// Holds the arguments passed to an `__afia__$output$123` function.
#[derive(Clone)]
pub struct ComponentOutputArgs {
    /// The component instance's context, represented as a type-erased pointer.
    pub ctx: *mut (),
    /// The [`ComponentImports`].
    pub imports: ComponentImports,
    /// TODO: This will get deleted when we remove the `inputs_ptr/inputs_len` from our wasm ABI.
    pub inputs_ptr: *const u8,
}

/// Represents a type that can be created from [`ComponentOutputArgs`].
pub trait FromComponentOutputArgs {
    /// Create the type using [`ComponentOutputArgs`].
    fn from_args(args: ComponentOutputArgs) -> Self;
}

impl FromComponentOutputArgs for ComponentImports {
    fn from_args(args: ComponentOutputArgs) -> Self {
        args.imports.clone()
    }
}
