//! > ⚠️ Note: This is an advanced and experimental API recommended only for plugin developers who are familiar with systems programming and the C ABI. Use with caution.
//!
//! # Bun Native Plugins
//!
//! This crate provides a Rustified wrapper over the Bun's native bundler plugin C API.
//!
//! Some advantages to _native_ bundler plugins as opposed to regular ones implemented in JS:
//!
//! - Native plugins take full advantage of Bun's parallelized bundler pipeline and run on multiple threads at the same time
//! - Unlike JS, native plugins don't need to do the UTF-8 <-> UTF-16 source code string conversions
//!
//! What are native bundler plugins exactly? Precisely, they are NAPI modules which expose a C ABI function which implement a plugin lifecycle hook.
//!
//! The currently supported lifecycle hooks are:
//!
//! - `onBeforeParse` (called immediately before a file is parsed, allows you to modify the source code of the file)
//!
//! ## Getting started
//!
//! Since native bundler plugins are NAPI modules, the easiest way to get started is to create a new [napi-rs](https://github.com/napi-rs/napi-rs) project:
//!
//! ```bash
//! bun add -g @napi-rs/cli
//! napi new
//! ```
//!
//! Then install this crate:
//!
//! ```bash
//! cargo add bun-native-plugin
//! ```
//!
//! Now, inside the `lib.rs` file, expose a C ABI function which has the same function signature as the plugin lifecycle hook that you want to implement.
//!
//! For example, implementing `onBeforeParse`:
//!
//! ```rust
//! use bun_native_plugin::{OnBeforeParse};
//!
//! /// This is necessary for napi-rs to compile this into a proper NAPI module
//! #[napi]
//! pub fn register_bun_plugin() {}
//!
//! /// Use `no_mangle` so that we can reference this symbol by name later
//! /// when registering this native plugin in JS.
//! ///
//! /// Here we'll create a dummy plugin which replaces all occurrences of
//! /// `foo` with `bar`
//! #[no_mangle]
//! pub extern "C" fn on_before_parse_plugin_impl(
//!   args: *const bun_native_plugin::sys::OnBeforeParseArguments,
//!   result: *mut bun_native_plugin::sys::OnBeforeParseResult,
//! ) {
//!   let args = unsafe { &*args };
//!   let result = unsafe { &mut *result };
//!
//!   // This returns a handle which is a safe wrapper over the raw
//!   // C API.
//!   let mut handle = OnBeforeParse::from_raw(args, result) {
//!     Ok(handle) => handle,
//!     Err(_) => {
//!       // `OnBeforeParse::from_raw` handles error logging
//!       // so it fine to return here.
//!       return;
//!     }
//!   };
//!
//!   let input_source_code = match handle.input_source_code() {
//!     Ok(source_str) => source_str,
//!     Err(_) => {
//!       // If we encounter an error, we must log it so that
//!       // Bun knows this plugin failed.
//!       handle.log_error("Failed to fetch source code!");
//!       return;
//!     }
//!   };
//!
//!   let loader = handle.output_loader();
//!   let output_source_code = source_str.replace("foo", "bar");
//!   handle.set_output_source_code(output_source_code, loader);
//! }
//! ```
//!
//! Then compile this NAPI module. If you using napi-rs, the `package.json` should have a `build` script you can run:
//!
//! ```bash
//! bun run build
//! ```
//!
//! This will produce a `.node` file in the project directory.
//!
//! With the compiled NAPI module, you can now register the plugin from JS:
//!
//! ```js
//! const result = await Bun.build({
//!   entrypoints: ["index.ts"],
//!   plugins: [
//!     {
//!       name: "replace-foo-with-bar",
//!       setup(build) {
//!         const napiModule = require("path/to/napi_module.node");
//!
//!         // Register the `onBeforeParse` hook to run on all `.ts` files.
//!         // We tell it to use function we implemented inside of our `lib.rs` code.
//!         build.onBeforeParse(
//!           { filter: /\.ts/ },
//!           { napiModule, symbol: "on_before_parse_plugin_impl" },
//!         );
//!       },
//!     },
//!   ],
//! });
//! ```
//!
//! ## Very important information
//!
//! ### Error handling and panics
//!
//! It is highly recommended to avoid panicking as this will crash the runtime. Instead, you must handle errors and log them:
//!
//! ```rust
//! let input_source_code = match handle.input_source_code() {
//!   Ok(source_str) => source_str,
//!   Err(_) => {
//!     // If we encounter an error, we must log it so that
//!     // Bun knows this plugin failed.
//!     handle.log_error("Failed to fetch source code!");
//!     return;
//!   }
//! };
//! ```
//!
//! ### Passing state to and from JS: `External`
//!
//! One way to communicate data from your plugin and JS and vice versa is through the NAPI's [External](https://napi.rs/docs/concepts/external) type.
//!
//! An External in NAPI is like an opaque pointer to data that can be passed to and from JS. Inside your NAPI module, you can retrieve
//! the pointer and modify the data.
//!
//! As an example that extends our getting started example above, let's say you wanted to count the number of `foo`'s that the native plugin encounters.
//!
//! You would expose a NAPI module function which creates this state. Recall that state in native plugins must be threadsafe. This usually means
//! that your state must be `Sync`:
//!
//! ```rust
//! struct PluginState {
//!   foo_count: std::sync::atomic::AtomicU32,
//! }
//!
//! #[napi]
//! pub fn create_plugin_state() -> External<PluginState> {
//!   let external = External::new(PluginState {
//!     foo_count: 0,
//!   });
//!
//!   external
//! }
//!
//!
//! #[napi]
//! pub fn get_foo_count(plugin_state: External<PluginState>) -> u32 {
//!   let plugin_state: &PluginState = &plugin_state;
//!   plugin_state.foo_count.load(std::sync::atomic::Ordering::Relaxed)
//! }
//! ```
//!
//! When you register your plugin from Javascript, you call the napi module function to create the external and then pass it:
//!
//! ```js
//! const napiModule = require("path/to/napi_module.node");
//! const pluginState = napiModule.createPluginState();
//!
//! const result = await Bun.build({
//!   entrypoints: ["index.ts"],
//!   plugins: [
//!     {
//!       name: "replace-foo-with-bar",
//!       setup(build) {
//!         build.onBeforeParse(
//!           { filter: /\.ts/ },
//!           {
//!             napiModule,
//!             symbol: "on_before_parse_plugin_impl",
//!             // pass our NAPI external which contains our plugin state here
//!             external: pluginState,
//!           },
//!         );
//!       },
//!     },
//!   ],
//! });
//!
//! console.log("Total `foo`s encountered: ", pluginState.getFooCount());
//! ```
//!
//! Finally, from the native implementation of your plugin, you can extract the external:
//!
//! ```rust
//! pub extern "C" fn on_before_parse_plugin_impl(
//!   args: *const bun_native_plugin::sys::OnBeforeParseArguments,
//!   result: *mut bun_native_plugin::sys::OnBeforeParseResult,
//! ) {
//!   let args = unsafe { &*args };
//!   let result = unsafe { &mut *result };
//!
//!   let mut handle = OnBeforeParse::from_raw(args, result) {
//!     Ok(handle) => handle,
//!     Err(_) => {
//!       // `OnBeforeParse::from_raw` handles error logging
//!       // so it fine to return here.
//!       return;
//!     }
//!   };
//!
//!   let plugin_state: &PluginState =
//!     // This operation is only safe if you pass in an external when registering the plugin.
//!     // If you don't, this could lead to a segfault or access of undefined memory.
//!     match unsafe { handle.external().and_then(|state| state.ok_or(Error::Unknown)) } {
//!       Ok(state) => state,
//!       Err(_) => {
//!         handle.log_error("Failed to get external!");
//!         return;
//!       }
//!     };
//!
//!
//!   // Fetch our source code again
//!   let input_source_code = match handle.input_source_code() {
//!     Ok(source_str) => source_str,
//!     Err(_) => {
//!       handle.log_error("Failed to fetch source code!");
//!       return;
//!     }
//!   };
//!
//!   // Count the number of `foo`s and add it to our state
//!   let foo_count = source_code.matches("foo").count() as u32;
//!   plugin_state.foo_count.fetch_add(foo_count, std::sync::atomic::Ordering::Relaxed);
//! }
//! ```
//!
//! ### Concurrency
//!
//! Your `extern "C"` plugin function can be called _on any thread_ at _any time_ and _multiple times at once_.
//!
//! Therefore, you must design any state management to be threadsafe
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
pub use anyhow;
pub use bun_macro::bun;

pub mod sys;

#[repr(transparent)]
pub struct BunPluginName(*const c_char);

impl BunPluginName {
    pub const fn new(ptr: *const c_char) -> Self {
        Self(ptr)
    }
}

#[macro_export]
macro_rules! define_bun_plugin {
    ($name:expr) => {
        pub static BUN_PLUGIN_NAME_STRING: &str = concat!($name, "\0");

        #[no_mangle]
        pub static BUN_PLUGIN_NAME: bun_native_plugin::BunPluginName =
            bun_native_plugin::BunPluginName::new(BUN_PLUGIN_NAME_STRING.as_ptr() as *const _);

        #[napi]
        fn bun_plugin_register() {}
    };
}

unsafe impl Sync for BunPluginName {}

use std::{
    any::TypeId,
    borrow::Cow,
    cell::UnsafeCell,
    ffi::{c_char, c_void},
    marker::PhantomData,
    str::Utf8Error,
    sync::PoisonError,
};

#[repr(C)]
pub struct TaggedObject<T> {
    type_id: TypeId,
    pub(crate) object: Option<T>,
}

struct SourceCodeContext {
    source_ptr: *mut u8,
    source_len: usize,
    source_cap: usize,
}

extern "C" fn free_plugin_source_code_context(ctx: *mut c_void) {
    // SAFETY: The ctx pointer is a pointer to the `SourceCodeContext` struct we allocated.
    unsafe {
        drop(Box::from_raw(ctx as *mut SourceCodeContext));
    }
}

impl Drop for SourceCodeContext {
    fn drop(&mut self) {
        if !self.source_ptr.is_null() {
            // SAFETY: These fields come from a `String` that we allocated.
            unsafe {
                drop(String::from_raw_parts(
                    self.source_ptr,
                    self.source_len,
                    self.source_cap,
                ));
            }
        }
    }
}

pub type BunLogLevel = sys::BunLogLevel;
pub type BunLoader = sys::BunLoader;

fn get_from_raw_str<'a>(ptr: *const u8, len: usize) -> PluginResult<Cow<'a, str>> {
    let slice: &'a [u8] = unsafe { std::slice::from_raw_parts(ptr, len) };

    // Windows allows invalid UTF-16 strings in the filesystem. These get converted to WTF-8 in Zig.
    // Meaning the string may contain invalid UTF-8, we'll have to use the safe checked version.
    #[cfg(target_os = "windows")]
    {
        std::str::from_utf8(slice)
            .map(Into::into)
            .or_else(|_| Ok(String::from_utf8_lossy(slice)))
    }

    #[cfg(not(target_os = "windows"))]
    {
        // SAFETY: The source code comes from Zig, which uses UTF-8, so this should be safe.

        std::str::from_utf8(slice)
            .map(Into::into)
            .or_else(|_| Ok(String::from_utf8_lossy(slice)))
    }
}

#[derive(Debug, Clone)]
pub enum Error {
    Utf8(Utf8Error),
    IncompatiblePluginVersion,
    ExternalTypeMismatch,
    Unknown,
    LockPoisoned,
}

pub type PluginResult<T> = std::result::Result<T, Error>;
pub type Result<T> = anyhow::Result<T>;

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }

    fn description(&self) -> &str {
        "description() is deprecated; use Display"
    }

    fn cause(&self) -> Option<&dyn std::error::Error> {
        self.source()
    }
}

impl From<Utf8Error> for Error {
    fn from(value: Utf8Error) -> Self {
        Self::Utf8(value)
    }
}

impl<Guard> From<PoisonError<Guard>> for Error {
    fn from(_: PoisonError<Guard>) -> Self {
        Self::LockPoisoned
    }
}

/// A safe handle for the arguments + result struct for the
/// `OnBeforeParse` bundler lifecycle hook.
///
/// This struct acts as a safe wrapper around the raw C API structs
/// (`sys::OnBeforeParseArguments`/`sys::OnBeforeParseResult`) needed to
/// implement the `OnBeforeParse` bundler lifecycle hook.
///
/// To initialize this struct, see the `from_raw` method.
pub struct OnBeforeParse<'a> {
    pub args_raw: *mut sys::OnBeforeParseArguments,
    result_raw: *mut sys::OnBeforeParseResult,
    compilation_context: *mut SourceCodeContext,
    __phantom: PhantomData<&'a ()>,
}

impl<'a> OnBeforeParse<'a> {
    /// Initialize this struct from references to their raw counterparts.
    ///
    /// This function will do a versioning check to ensure that the plugin
    /// is compatible with the current version of Bun. If the plugin is not
    /// compatible, it will log an error and return an error result.
    ///
    /// # Example
    /// ```rust
    /// extern "C" fn on_before_parse_impl(args: *const sys::OnBeforeParseArguments, result: *mut sys::OnBeforeParseResult) {
    ///   let args = unsafe { &*args };
    ///   let result = unsafe { &mut *result };
    ///   let handle = match OnBeforeParse::from_raw(args, result) {
    ///     Ok(handle) => handle,
    ///     Err(()) => return,
    ///   };
    /// }
    /// ```
    pub fn from_raw(
        args: *mut sys::OnBeforeParseArguments,
        result: *mut sys::OnBeforeParseResult,
    ) -> PluginResult<Self> {
        if unsafe { (*args).__struct_size } < std::mem::size_of::<sys::OnBeforeParseArguments>()
            || unsafe { (*result).__struct_size } < std::mem::size_of::<sys::OnBeforeParseResult>()
        {
            let message = "This plugin is not compatible with the current version of Bun.";
            let mut log_options = sys::BunLogOptions {
                __struct_size: std::mem::size_of::<sys::BunLogOptions>(),
                message_ptr: message.as_ptr(),
                message_len: message.len(),
                path_ptr: unsafe { (*args).path_ptr },
                path_len: unsafe { (*args).path_len },
                source_line_text_ptr: std::ptr::null(),
                source_line_text_len: 0,
                level: BunLogLevel::BUN_LOG_LEVEL_ERROR as i8,
                line: 0,
                lineEnd: 0,
                column: 0,
                columnEnd: 0,
            };
            // SAFETY: The `log` function pointer is guaranteed to be valid by the Bun runtime.
            unsafe {
                ((*result).log.unwrap())(args, &mut log_options);
            }
            return Err(Error::IncompatiblePluginVersion);
        }

        Ok(Self {
            args_raw: args,
            result_raw: result,
            compilation_context: std::ptr::null_mut() as *mut _,
            __phantom: Default::default(),
        })
    }

    pub fn path(&self) -> PluginResult<Cow<'_, str>> {
        unsafe { get_from_raw_str((*self.args_raw).path_ptr, (*self.args_raw).path_len) }
    }

    pub fn namespace(&self) -> PluginResult<Cow<'_, str>> {
        unsafe {
            get_from_raw_str(
                (*self.args_raw).namespace_ptr,
                (*self.args_raw).namespace_len,
            )
        }
    }

    /// # Safety
    /// This is unsafe as you must ensure that no other invocation of the plugin (or JS!)
    /// simultaneously holds a mutable reference to the external.
    ///
    /// Get the external object from the `OnBeforeParse` arguments.
    ///
    /// The external object is set by the plugin definition inside of JS:
    /// ```js
    /// await Bun.build({
    ///   plugins: [
    ///     {
    ///       name: "my-plugin",
    ///       setup(builder) {
    ///         const native_plugin = require("./native_plugin.node");
    ///         const external = native_plugin.createExternal();
    ///         builder.external({ napiModule: native_plugin, symbol: 'onBeforeParse', external });
    ///       },
    ///     },
    ///   ],
    /// });
    /// ```
    ///
    /// The external object must be created from NAPI for this function to be safe!
    ///
    /// This function will return an error if the external object is not a
    /// valid tagged object for the given type.
    ///
    /// This function will return `Ok(None)` if there is no external object
    /// set.
    ///
    /// # Example
    /// The code to create the external from napi-rs:
    /// ```rs
    /// #[no_mangle]
    /// #[napi]
    /// pub fn create_my_external() -> External<MyStruct> {
    ///   let external = External::new(MyStruct::new());
    ///
    ///   external
    /// }
    /// ```
    ///
    /// The code to extract the external:
    /// ```rust
    /// let external = match handle.external::<MyStruct>() {
    ///     Ok(Some(external)) => external,
    ///     _ => {
    ///         handle.log_error("Could not get external object.");
    ///         return;
    ///     },
    /// };
    /// ```
    pub unsafe fn external<'b, T: 'static + Sync>(
        &self,
        from_raw: unsafe fn(*mut c_void) -> Option<&'b T>,
    ) -> PluginResult<Option<&'b T>> {
        if unsafe { (*self.args_raw).external.is_null() } {
            return Ok(None);
        }

        let external = unsafe { from_raw((*self.args_raw).external as *mut _) };

        Ok(external)
    }

    /// The same as [`crate::bun_native_plugin::OnBeforeParse::external`], but returns a mutable reference.
    ///
    /// # Safety
    /// This is unsafe as you must ensure that no other invocation of the plugin (or JS!)
    /// simultaneously holds a mutable reference to the external.
    pub unsafe fn external_mut<'b, T: 'static + Sync>(
        &mut self,
        from_raw: unsafe fn(*mut c_void) -> Option<&'b mut T>,
    ) -> PluginResult<Option<&'b mut T>> {
        if unsafe { (*self.args_raw).external.is_null() } {
            return Ok(None);
        }

        let external = unsafe { from_raw((*self.args_raw).external as *mut _) };

        Ok(external)
    }

    /// Get the input source code for the current file.
    ///
    /// On Windows, this function may return an `Err(Error::Utf8(...))` if the
    /// source code contains invalid UTF-8.
    pub fn input_source_code(&self) -> PluginResult<Cow<'_, str>> {
        let fetch_result = unsafe {
            ((*self.result_raw).fetchSourceCode.unwrap())(
                self.args_raw as *const _,
                self.result_raw,
            )
        };

        if fetch_result != 0 {
            Err(Error::Unknown)
        } else {
            // SAFETY: We don't hand out mutable references to `result_raw` so dereferencing here is safe.
            unsafe {
                get_from_raw_str((*self.result_raw).source_ptr, (*self.result_raw).source_len)
            }
        }
    }

    /// Set the output source code for the current file.
    pub fn set_output_source_code(&mut self, source: String, loader: BunLoader) {
        let source_cap = source.capacity();
        let source = source.leak();
        let source_ptr = source.as_mut_ptr();
        let source_len = source.len();

        if self.compilation_context.is_null() {
            self.compilation_context = Box::into_raw(Box::new(SourceCodeContext {
                source_ptr,
                source_len,
                source_cap,
            }));

            // SAFETY: We don't hand out mutable references to `result_raw` so dereferencing it is safe.
            unsafe {
                (*self.result_raw).plugin_source_code_context =
                    self.compilation_context as *mut c_void;
                (*self.result_raw).free_plugin_source_code_context =
                    Some(free_plugin_source_code_context);
            }
        } else {
            unsafe {
                // SAFETY: If we're here we know that `compilation_context` is not null.
                let context = &mut *self.compilation_context;

                drop(String::from_raw_parts(
                    context.source_ptr,
                    context.source_len,
                    context.source_cap,
                ));

                context.source_ptr = source_ptr;
                context.source_len = source_len;
                context.source_cap = source_cap;
            }
        }

        // SAFETY: We don't hand out mutable references to `result_raw` so dereferencing it is safe.
        unsafe {
            (*self.result_raw).loader = loader as u8;
            (*self.result_raw).source_ptr = source_ptr;
            (*self.result_raw).source_len = source_len;
        }
    }

    /// Set the output loader for the current file.
    pub fn set_output_loader(&self, loader: BunLoader) {
        // SAFETY: We don't hand out mutable references to `result_raw` so dereferencing it is safe.
        unsafe {
            (*self.result_raw).loader = loader as u8;
        }
    }

    /// Get the output loader for the current file.
    pub fn output_loader(&self) -> BunLoader {
        unsafe { std::mem::transmute((*self.result_raw).loader as u32) }
    }

    /// Log an error message.
    pub fn log_error(&self, message: &str) {
        self.log(message, BunLogLevel::BUN_LOG_LEVEL_ERROR)
    }

    /// Log a message with the given level.
    pub fn log(&self, message: &str, level: BunLogLevel) {
        let mut log_options = log_from_message_and_level(
            message,
            level,
            unsafe { (*self.args_raw).path_ptr },
            unsafe { (*self.args_raw).path_len },
        );
        unsafe {
            ((*self.result_raw).log.unwrap())(self.args_raw, &mut log_options);
        }
    }
}

pub fn log_from_message_and_level(
    message: &str,
    level: BunLogLevel,
    path: *const u8,
    path_len: usize,
) -> sys::BunLogOptions {
    sys::BunLogOptions {
        __struct_size: std::mem::size_of::<sys::BunLogOptions>(),
        message_ptr: message.as_ptr(),
        message_len: message.len(),
        path_ptr: path as *const _,
        path_len,
        source_line_text_ptr: std::ptr::null(),
        source_line_text_len: 0,
        level: level as i8,
        line: 0,
        lineEnd: 0,
        column: 0,
        columnEnd: 0,
    }
}
