use crate::AviUtl2Info;
use pastey::paste;
use std::num::NonZeroIsize;

/// ホストアプリケーションのハンドル。
/// プラグインの初期化処理で使用します。
///
/// # Panics
///
/// この型がプラグインの初期化処理の外で使用された場合はPanicします。
pub struct HostAppHandle<'a> {
    internal: *mut aviutl2_sys::plugin2::HOST_APP_TABLE,
    global_leak_manager: &'a mut crate::common::LeakManager,
    is_register_plugin_done: std::sync::Arc<std::sync::atomic::AtomicBool>,
    plugin_registry: &'a mut crate::generic::PluginRegistry,
    is_edit_handle_available: std::sync::Arc<std::sync::atomic::AtomicBool>,
}

/// プラグインの初期化状態を管理するためのハンドル。
pub struct SubPlugin<T> {
    plugin: std::marker::PhantomData<T>,
    internal: std::sync::Arc<InternalReferenceHandle>,
}
struct InternalReferenceHandle {
    uninitialize_fn: fn(),
}
impl Drop for InternalReferenceHandle {
    fn drop(&mut self) {
        (self.uninitialize_fn)();
    }
}

impl<'plugin> HostAppHandle<'plugin> {
    pub(crate) unsafe fn new(
        internal: *mut aviutl2_sys::plugin2::HOST_APP_TABLE,
        global_leak_manager: &'plugin mut crate::common::LeakManager,
        is_register_plugin_done: std::sync::Arc<std::sync::atomic::AtomicBool>,
        plugin_registry: &'plugin mut crate::generic::PluginRegistry,
        is_edit_handle_available: std::sync::Arc<std::sync::atomic::AtomicBool>,
    ) -> Self {
        Self {
            internal,
            global_leak_manager,
            is_register_plugin_done,
            plugin_registry,
            is_edit_handle_available,
        }
    }

    fn assert_not_killed(&self) {
        if self
            .is_register_plugin_done
            .load(std::sync::atomic::Ordering::SeqCst)
        {
            panic!("This HostAppHandle is no longer valid.");
        }
    }

    /// プロジェクトデータ編集用のハンドルを登録します。
    pub fn create_edit_handle(&mut self) -> crate::generic::EditHandle {
        self.assert_not_killed();
        let raw_handle = unsafe { ((*self.internal).create_edit_handle)() };
        unsafe {
            crate::generic::EditHandle::new(raw_handle, self.is_edit_handle_available.clone())
        }
    }

    /// インポートメニューを登録します。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_import_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn() + 'static + Send + Sync,
    {
        self.register_menu_internal(name, callback, unsafe {
            (*self.internal).register_import_menu_param
        });
    }

    /// エクスポートメニューを登録します。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_export_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn() + 'static + Send + Sync,
    {
        self.register_menu_internal(name, callback, unsafe {
            (*self.internal).register_export_menu_param
        });
    }

    /// レイヤーメニューを登録します。
    /// レイヤー編集でオブジェクト未選択時の右クリックメニューに追加されます。
    /// 名前に`\\`を含めるとサブメニューとして登録されます。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_layer_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn() + 'static + Send + Sync,
    {
        self.register_menu_internal(name, callback, unsafe {
            (*self.internal).register_layer_menu_param
        });
    }

    /// オブジェクトメニューを登録します。
    /// レイヤー編集でオブジェクト選択時の右クリックメニューに追加されます。
    /// 名前に`\\`を含めるとサブメニューとして登録されます。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_object_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn() + 'static + Send + Sync,
    {
        self.register_menu_internal(name, callback, unsafe {
            (*self.internal).register_object_menu_param
        });
    }

    /// 編集メニューを登録します。
    /// 名前に`\\`を含めるとサブメニューとして登録されます。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_edit_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn() + 'static + Send + Sync,
    {
        self.register_menu_internal(name, callback, unsafe {
            (*self.internal).register_edit_menu_param
        });
    }

    /// 設定メニューを登録します。
    /// 設定メニューの登録後にウィンドウクライアントを登録するとシステムメニューに「設定」が追加されます。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_config_menu(
        &mut self,
        name: &str,
        callback: extern "C" fn(aviutl2_sys::plugin2::HWND, aviutl2_sys::plugin2::HINSTANCE),
    ) {
        self.assert_not_killed();
        unsafe {
            ((*self.internal).register_config_menu)(
                self.global_leak_manager.leak_as_wide_string(name),
                callback,
            )
        };
    }

    /// オブジェクト編集の設定項目メニューを登録します。
    /// オブジェクト編集の右クリックメニューに追加されます。
    /// 名前に`\\`を含めるとサブメニューとして登録されます。
    /// [Self::register_object_item_and_effect_menu]と違い、これはエフェクトそのものに対してはメニューが表示されず、設定項目に対してのみメニューが表示されるようになります。
    ///
    /// # Arguments
    ///
    /// - `name`: メニューの名称。
    /// - `callback`: メニューが選択されたときのコールバック関数。
    ///
    /// # Note
    ///
    /// `register_object_item_menu`の`allow_effect_only = false`に相当します。
    pub fn register_object_item_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn(crate::generic::ObjectHandle, &str, usize, &str) + 'static + Send + Sync,
    {
        self.assert_not_killed();
        let trampoline_param: Box<F> = Box::new(callback);
        let trampoline_param_ptr = Box::into_raw(trampoline_param);
        let name_wide = self.global_leak_manager.leak_as_wide_string(name);
        unsafe {
            ((*self.internal).register_object_item_menu_param)(
                name_wide,
                false,
                trampoline_param_ptr as *mut std::ffi::c_void,
                trampoline::<F>,
            );
        }

        unsafe extern "C" fn trampoline<F>(
            param: *mut std::ffi::c_void,
            object_handle: aviutl2_sys::plugin2::OBJECT_HANDLE,
            name_and_index: aviutl2_sys::common::LPCWSTR,
            item_name: aviutl2_sys::common::LPCWSTR,
        ) where
            F: Fn(crate::generic::ObjectHandle, &str, usize, &str) + 'static + Send + Sync,
        {
            let callback = unsafe { &mut *(param as *mut F) };
            let object_handle = crate::generic::ObjectHandle::from(object_handle);
            let name_and_index_str = unsafe { crate::common::load_wide_string(name_and_index) };
            let (name_str, index) = parse_name_and_index(&name_and_index_str);
            let item_name_str = unsafe { crate::common::load_wide_string(item_name) };
            if let Err(panic_info) =
                crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(|| {
                    callback(object_handle, name_str, index, &item_name_str);
                }))
            {
                tracing::error!(
                    "Panic occurred in object item menu callback: {}",
                    panic_info
                );
                let _ = crate::logger::write_error_log(&panic_info);
            }
        }
    }

    /// オブジェクト編集の設定項目メニューを登録します。
    /// オブジェクト編集の右クリックメニューに追加されます。
    /// 名前に`\\`を含めるとサブメニューとして登録されます。
    /// [Self::register_object_item_menu]と違い、これはエフェクトそのものに対してもメニューが表示されるようになります。
    ///
    /// # Arguments
    ///
    /// - `name`: メニューの名称。
    /// - `callback`: メニューが選択されたときのコールバック関数。
    ///
    /// # Note
    ///
    /// `register_object_item_menu`の`allow_effect_only = true`に相当します。
    pub fn register_object_item_and_effect_menu<F>(&mut self, name: &str, callback: F)
    where
        F: Fn(crate::generic::ObjectHandle, &str, usize, Option<&str>) + 'static + Send + Sync,
    {
        self.assert_not_killed();
        let trampoline_param: Box<F> = Box::new(callback);
        let trampoline_param_ptr = Box::into_raw(trampoline_param);
        let name_wide = self.global_leak_manager.leak_as_wide_string(name);
        unsafe {
            ((*self.internal).register_object_item_menu_param)(
                name_wide,
                true,
                trampoline_param_ptr as *mut std::ffi::c_void,
                trampoline::<F>,
            );
        }

        unsafe extern "C" fn trampoline<F>(
            param: *mut std::ffi::c_void,
            object_handle: aviutl2_sys::plugin2::OBJECT_HANDLE,
            effect_name_and_index: aviutl2_sys::common::LPCWSTR,
            item_name: aviutl2_sys::common::LPCWSTR,
        ) where
            F: Fn(crate::generic::ObjectHandle, &str, usize, Option<&str>) + 'static + Send + Sync,
        {
            let callback = unsafe { &mut *(param as *mut F) };
            let object_handle = crate::generic::ObjectHandle::from(object_handle);
            let effect_name_and_index =
                unsafe { crate::common::load_wide_string(effect_name_and_index) };
            let item_name = (!item_name.is_null())
                .then(|| unsafe { crate::common::load_wide_string(item_name) });

            let (effect_name_str, index) = parse_name_and_index(&effect_name_and_index);
            let item_name_option = item_name.as_deref();
            if let Err(panic_info) =
                crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(|| {
                    callback(object_handle, effect_name_str, index, item_name_option);
                }))
            {
                tracing::error!(
                    "Panic occurred in object item menu with effect callback: {}",
                    panic_info
                );
                let _ = crate::logger::write_error_log(&panic_info);
            }
        }
    }

    /// ファイルをドロップしたときの処理を登録します。
    ///
    /// # Arguments
    ///
    /// - `name`: ドラッグ時のツールチップや入力プラグインの設定で表示する名称。
    /// - `file_filter`: 対応するファイルフィルタ。
    /// - `callback`: ドロップされたファイルのパスを受け取るコールバック関数。
    pub fn register_file_drop_handler<F>(
        &mut self,
        name: &str,
        file_filters: &[crate::common::FileFilter],
        callback: F,
    ) where
        F: Fn(std::path::PathBuf) + 'static + Send + Sync,
    {
        self.assert_not_killed();
        let callback_box = Box::new(callback);
        let callback_ptr = Box::into_raw(callback_box);
        let name_wide = self.global_leak_manager.leak_as_wide_string(name);
        let file_filter_wide = self
            .global_leak_manager
            .leak_as_wide_string(&crate::common::format_file_filters(file_filters));
        unsafe {
            ((*self.internal).register_file_drop_param_handler)(
                name_wide,
                file_filter_wide,
                callback_ptr as *mut std::ffi::c_void,
                file_drop_trampoline::<F>,
            );
        }
    }

    /// ウィンドウクライアントを登録します。
    ///
    /// # Panics
    ///
    /// Win32のウィンドウハンドル以外が渡された場合はPanicします。
    pub fn register_window_client<T: raw_window_handle::HasWindowHandle>(
        &mut self,
        name: &str,
        instance: &T,
    ) -> Result<(), raw_window_handle::HandleError> {
        self.assert_not_killed();
        let raw_handle = instance.window_handle()?;
        let hwnd = match raw_handle.as_raw() {
            raw_window_handle::RawWindowHandle::Win32(handle) => handle.hwnd,
            _ => panic!("Only Win32WindowHandle is supported"),
        };
        unsafe {
            ((*self.internal).register_window_client)(
                self.global_leak_manager.leak_as_wide_string(name),
                hwnd.get() as *mut std::ffi::c_void,
            );
        }
        Ok(())
    }

    /// メニューを一括登録します。
    ///
    /// # See Also
    ///
    /// - [`crate::generic::menus`]
    pub fn register_menus<T: GenericPluginMenus>(&mut self) {
        self.assert_not_killed();
        T::register_menus(self);
    }

    /// プロジェクトファイルをロードした直後に呼ばれる関数を登録します。
    /// また、プロジェクトの初期化時にも呼ばれます。
    ///
    /// # Note
    ///
    /// [`crate::generic::GenericPlugin::on_project_load`] が自動的に登録されるため、
    /// 通常はこの関数を直接使用する必要はありません。
    pub fn register_project_load_handler(
        &mut self,
        callback: extern "C" fn(*mut aviutl2_sys::plugin2::PROJECT_FILE),
    ) {
        self.assert_not_killed();
        unsafe {
            ((*self.internal).register_project_load_handler)(callback);
        }
    }

    /// プロジェクトファイルを保存する直前に呼ばれる関数を登録します。
    ///
    /// # Note
    ///
    /// [`crate::generic::GenericPlugin::on_project_save`] が自動的に登録されるため、
    /// 通常はこの関数を直接使用する必要はありません。
    pub fn register_project_save_handler(
        &mut self,
        callback: extern "C" fn(*mut aviutl2_sys::plugin2::PROJECT_FILE),
    ) {
        self.assert_not_killed();
        unsafe {
            ((*self.internal).register_project_save_handler)(callback);
        }
    }

    /// 「キャッシュを破棄」が呼ばれたときに呼ばれる関数を登録します。
    ///
    /// # Note
    ///
    /// [`crate::generic::GenericPlugin::on_clear_cache`] が自動的に登録されるため、
    /// 通常はこの関数を直接使用する必要はありません。
    pub fn register_clear_cache_handler(
        &mut self,
        callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
    ) {
        self.assert_not_killed();
        unsafe {
            ((*self.internal).register_clear_cache_handler)(callback);
        }
    }

    /// シーンを変更した直後に呼ばれる関数を登録します。
    ///
    /// # Note
    ///
    /// [`crate::generic::GenericPlugin::on_change_scene`] が自動的に登録されるため、
    /// 通常はこの関数を直接使用する必要はありません。
    pub fn register_change_scene_handler(
        &mut self,
        callback: extern "C" fn(*mut aviutl2_sys::plugin2::EDIT_SECTION),
    ) {
        self.assert_not_killed();
        unsafe {
            ((*self.internal).register_change_scene_handler)(callback);
        }
    }

    fn register_menu_internal<F>(
        &mut self,
        name: &str,
        callback: F,
        register_fn: unsafe extern "C" fn(
            aviutl2_sys::common::LPCWSTR,
            *mut std::ffi::c_void,
            unsafe extern "C" fn(*mut std::ffi::c_void),
        ),
    ) where
        F: Fn() + 'static + Send + Sync,
    {
        self.assert_not_killed();
        let trampoline_param: Box<MenuTrampolineParam<F>> = Box::new(callback);
        let trampoline_param_ptr = Box::into_raw(trampoline_param);
        unsafe {
            register_fn(
                self.global_leak_manager.leak_as_wide_string(name),
                trampoline_param_ptr as *mut std::ffi::c_void,
                menu_trampoline::<F>,
            );
        }
    }
}

fn parse_name_and_index(name_and_index: &str) -> (&str, usize) {
    let Some(pos) = name_and_index.rfind(':') else {
        return (name_and_index, 0);
    };
    let index_str = &name_and_index[pos + 1..];
    if let Ok(index) = index_str.parse::<usize>() {
        let name = &name_and_index[..pos];
        (name, index)
    } else {
        (name_and_index, 0)
    }
}

type MenuTrampolineParam<F> = F;

unsafe extern "C" fn menu_trampoline<F>(param: *mut std::ffi::c_void)
where
    F: Fn() + 'static + Send + Sync,
{
    let callback = unsafe { &mut *(param as *mut MenuTrampolineParam<F>) };
    if let Err(panic_info) =
        crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(callback))
    {
        tracing::error!("Panic occurred in menu callback: {}", panic_info);
        let _ = crate::logger::write_error_log(&panic_info);
    }
}

unsafe extern "C" fn file_drop_trampoline<F>(
    param: *mut std::ffi::c_void,
    file_path: aviutl2_sys::common::LPCWSTR,
) where
    F: Fn(std::path::PathBuf) + 'static + Send + Sync,
{
    let callback = unsafe { &mut *(param as *mut F) };
    let path_str = unsafe { crate::common::load_wide_string(file_path) };
    let path = std::path::PathBuf::from(path_str);
    if let Err(panic_info) =
        crate::utils::catch_unwind_with_panic_info(std::panic::AssertUnwindSafe(|| {
            callback(path);
        }))
    {
        tracing::error!("Panic occurred in file drop callback: {}", panic_info);
        let _ = crate::logger::write_error_log(&panic_info);
    }
}

/// 汎用プラグインのメニュー登録用トレイト。
///
/// <div class="warning">
///
/// このトレイトは [`crate::generic::menus`] マクロで自動的に実装されます。
/// 通常は手動で実装する必要はありません。
///
/// </div>
pub trait GenericPluginMenus {
    fn register_menus(host: &mut crate::generic::HostAppHandle);
}

#[doc(inline)]
pub use aviutl2_macros::generic_menus as menus;

#[derive(Default)]
pub(crate) struct PluginRegistry {
    #[cfg(feature = "input")]
    input_plugins: Vec<std::sync::Arc<InternalReferenceHandle>>,
    #[cfg(feature = "output")]
    output_plugins: Vec<std::sync::Arc<InternalReferenceHandle>>,
    #[cfg(feature = "filter")]
    filter_plugins: Vec<std::sync::Arc<InternalReferenceHandle>>,
    #[cfg(feature = "module")]
    script_modules: Vec<std::sync::Arc<InternalReferenceHandle>>,
}
impl PluginRegistry {
    pub(crate) fn new() -> Self {
        Self::default()
    }
}

macro_rules! impl_plugin_registry {
    (
        $description:literal,
        $feature:literal,
        $module:ident,
        $name:ident,
        $register_method:ident,
        $PluginTrait:path,
        $SingletonTrait:path,
        $TableType:ty
    ) => {
        paste! {
            impl<T> SubPlugin<T> {
                #[cfg(feature = $feature)]
                #[doc = concat!($description, "の新しいインスタンスを作成します。")]
                pub fn [<new_ $name>](info: &AviUtl2Info) -> crate::AnyResult<Self>
                where
                    T: $PluginTrait + $SingletonTrait + 'static
                {
                    crate::$module::__bridge::initialize_plugin::<T>(info.version.into())?;
                    let internal = std::sync::Arc::new(InternalReferenceHandle {
                        uninitialize_fn: || {
                            unsafe {
                                crate::$module::__bridge::uninitialize_plugin::<T>();
                            }
                        },
                    });
                    Ok(Self {
                        plugin: std::marker::PhantomData,
                        internal,
                    })
                }
            }
            #[cfg(feature = $feature)]
            impl<'a> HostAppHandle<'a> {
                #[doc = concat!($description, "を登録します。")]
                pub fn [<register_ $name>]<T: $PluginTrait + $SingletonTrait + 'static>(
                    &mut self,
                    handle: &SubPlugin<T>,
                ) {
                    self.assert_not_killed();
                    unsafe { ((*self.internal).$register_method)(crate::$module::__bridge::create_table_unwind::<T>()) };
                    self.plugin_registry
                        .[<$name s>]
                        .push(std::sync::Arc::clone(&handle.internal));
                }
                #[doc = concat!("unwindなしで", $description, "を登録します。")]
                pub fn [<register_ $name _nounwind>]<T: $PluginTrait + $SingletonTrait + 'static>(
                    &mut self,
                    handle: &SubPlugin<T>,
                ) {
                    self.assert_not_killed();
                    unsafe { ((*self.internal).$register_method)(crate::$module::__bridge::create_table::<T>()) };
                    self.plugin_registry
                        .[<$name s>]
                        .push(std::sync::Arc::clone(&handle.internal));
                }
            }
        }
    };
}

impl_plugin_registry!(
    "入力プラグイン",
    "input",
    input,
    input_plugin,
    register_input_plugin,
    crate::input::InputPlugin,
    crate::input::__bridge::InputSingleton,
    aviutl2_sys::input2::INPUT_PLUGIN_TABLE
);
impl_plugin_registry!(
    "出力プラグイン",
    "output",
    output,
    output_plugin,
    register_output_plugin,
    crate::output::OutputPlugin,
    crate::output::__bridge::OutputSingleton,
    aviutl2_sys::output2::OUTPUT_PLUGIN_TABLE
);
impl_plugin_registry!(
    "フィルタープラグイン",
    "filter",
    filter,
    filter_plugin,
    register_filter_plugin,
    crate::filter::FilterPlugin,
    crate::filter::__bridge::FilterSingleton,
    aviutl2_sys::filter2::FILTER_PLUGIN_TABLE
);
#[cfg(feature = "module")]
impl<T> SubPlugin<T> {
    /// スクリプトモジュールの新しいインスタンスを作成します。
    pub fn new_script_module(info: &AviUtl2Info) -> crate::AnyResult<Self>
    where
        T: crate::module::ScriptModule + crate::module::__bridge::ScriptModuleSingleton + 'static,
    {
        crate::module::__bridge::initialize_plugin::<T>(info.version.into())?;
        let internal = std::sync::Arc::new(InternalReferenceHandle {
            uninitialize_fn: || unsafe {
                crate::module::__bridge::uninitialize_plugin::<T>();
            },
        });
        Ok(Self {
            plugin: std::marker::PhantomData,
            internal,
        })
    }
}

#[cfg(feature = "module")]
impl<'a> HostAppHandle<'a> {
    /// スクリプトモジュールを登録します。
    ///
    /// # Arguments
    ///
    /// - `name`: モジュール名。
    pub fn register_script_module<
        T: crate::module::ScriptModule + crate::module::__bridge::ScriptModuleSingleton + 'static,
    >(
        &mut self,
        name: Option<&str>,
        handle: &SubPlugin<T>,
    ) {
        self.assert_not_killed();
        unsafe {
            if let Some(name_str) = name {
                ((*self.internal).register_script_module_name)(
                    crate::module::__bridge::create_table_unwind::<T>(),
                    self.global_leak_manager.leak_as_wide_string(name_str),
                )
            } else {
                ((*self.internal).register_script_module)(
                    crate::module::__bridge::create_table_unwind::<T>(),
                )
            }
        };
        self.plugin_registry
            .script_modules
            .push(std::sync::Arc::clone(&handle.internal));
    }

    /// unwindなしでスクリプトモジュールを登録します。
    ///
    /// # Arguments
    ///
    /// - `name`: モジュール名。
    pub fn register_script_module_nounwind<
        T: crate::module::ScriptModule + crate::module::__bridge::ScriptModuleSingleton + 'static,
    >(
        &mut self,
        name: Option<&str>,
        handle: &SubPlugin<T>,
    ) {
        self.assert_not_killed();
        unsafe {
            if let Some(name_str) = name {
                ((*self.internal).register_script_module_name)(
                    crate::module::__bridge::create_table::<T>(),
                    self.global_leak_manager.leak_as_wide_string(name_str),
                )
            } else {
                ((*self.internal).register_script_module)(
                    crate::module::__bridge::create_table::<T>(),
                );
            }
        };
        self.plugin_registry
            .script_modules
            .push(std::sync::Arc::clone(&handle.internal));
    }
}

#[doc(hidden)]
pub unsafe fn __internal_rwh_from_raw(
    hwnd: aviutl2_sys::plugin2::HWND,
    hinstance: aviutl2_sys::plugin2::HINSTANCE,
) -> raw_window_handle::Win32WindowHandle {
    let mut handle =
        raw_window_handle::Win32WindowHandle::new(NonZeroIsize::new(hwnd as isize).unwrap());
    handle.hinstance = Some(NonZeroIsize::new(hinstance as isize).unwrap());
    handle
}
