//! Android-specific JNI lifecycle helpers.
//!
//! On Android the Flutter engine starts our cdylib but does not push
//! anything into `ndk_context` (the global the cpal-on-Oboe backend
//! reads to find Android audio services). Without that, the first
//! audio call hangs / fails.
//!
//! We solve this two ways:
//!
//! 1. `JNI_OnLoad` captures the `JavaVM*` as soon as the library is
//!    loaded.
//! 2. We expose a `Java_app_chanora_chanora_1flutter_MainActivity_initChanoraContext`
//!    JNI function the Kotlin `MainActivity.onCreate` calls with its
//!    application Context. That JNI function pushes both the
//!    `JavaVM*` and a `Context` global ref into `ndk_context`.
//!
//! After that, cpal can open the default input/output devices.

#![cfg(target_os = "android")]

use std::sync::Once;

use jni::objects::{JClass, JObject};
use jni::sys::{jint, JNI_VERSION_1_6};
use jni::JNIEnv;
use log::{error, info};

/// Stash the JavaVM pointer between JNI_OnLoad and initContext.
static mut JAVA_VM: *mut std::ffi::c_void = std::ptr::null_mut();
static INIT_ONCE: Once = Once::new();

/// JNI entry point invoked by the Android runtime when the cdylib is
/// loaded via `System.loadLibrary` (which `flutter_rust_bridge` does
/// at `RustLib.init`).
#[no_mangle]
pub extern "system" fn JNI_OnLoad(
    vm: *mut std::ffi::c_void,
    _reserved: *mut std::ffi::c_void,
) -> jint {
    // Safety: Android guarantees `vm` is a valid JavaVM* for the
    // lifetime of the library.
    unsafe {
        JAVA_VM = vm;
    }
    JNI_VERSION_1_6
}

/// Called by `MainActivity.onCreate` with the application Context.
/// Initialises the `ndk_context` so cpal's Oboe backend can locate
/// the Android audio services.
///
/// Symbol mangling note: Kotlin / JNI mangles the underscore in
/// `chanora_flutter` to `chanora_1flutter` because the literal `_`
/// in a JNI symbol means package separator.
#[no_mangle]
pub extern "system" fn Java_app_chanora_chanora_1flutter_MainActivity_initChanoraContext<'local>(
    env: JNIEnv<'local>,
    _class: JClass<'local>,
    context: JObject<'local>,
) {
    INIT_ONCE.call_once(|| {
        let global = match env.new_global_ref(&context) {
            Ok(g) => g,
            Err(e) => {
                error!("initChanoraContext: new_global_ref failed: {e}");
                return;
            }
        };
        let raw_ctx = global.as_obj().as_raw() as *mut std::ffi::c_void;
        // Leak the global ref so it survives for the process lifetime
        // (ndk_context borrows the pointer).
        std::mem::forget(global);
        unsafe {
            ndk_context::initialize_android_context(JAVA_VM, raw_ctx);
        }
        info!(
            "initChanoraContext: ndk_context initialised (vm={:p}, ctx={:p})",
            unsafe { JAVA_VM },
            raw_ctx
        );
    });
}
