//! Safe JNI bridge for Android platform integration.
//!
//! Reconstructing the raw `JavaVM` / `NativeActivity` handles handed over by
//! the Android runtime requires `unsafe` FFI. That single reconstruction lives
//! in `solitaire_app`'s entry point ([`solitaire_app::android_main`]); this
//! module only ever stores the resulting *safe* [`JavaVM`] and activity
//! [`GlobalRef`] and hands callers an attached [`JNIEnv`].
//!
//! As a result every consumer of Android JNI — the keystore, clipboard, and
//! safe-area subsystems — goes through the safe functions here and stays
//! `forbid(unsafe_code)`. The only crate carrying `unsafe` is `solitaire_app`.
//!
//! Only compiled and linked on `target_os = "android"`.

use jni::objects::{GlobalRef, JObject};
use jni::{JNIEnv, JavaVM};
use std::sync::OnceLock;

static ANDROID_JVM: OnceLock<JavaVM> = OnceLock::new();
static ANDROID_ACTIVITY: OnceLock<GlobalRef> = OnceLock::new();

/// Store the process-wide [`JavaVM`]. Called once from Android startup
/// (`solitaire_app::android_main`); subsequent calls are ignored.
pub fn set_jvm(vm: JavaVM) {
    let _ = ANDROID_JVM.set(vm);
}

/// Store a global reference to the `NativeActivity`. Called once from Android
/// startup; subsequent calls are ignored.
pub fn set_activity(activity: GlobalRef) {
    let _ = ANDROID_ACTIVITY.set(activity);
}

/// Run `f` with a [`JNIEnv`] attached to the current thread.
///
/// Returns an error string if the bridge has not been initialised yet or the
/// thread cannot be attached. The closure's JNI errors are surfaced through the
/// same `String` channel so callers have a single error type to map.
pub fn with_env<F, R>(f: F) -> Result<R, String>
where
    F: for<'local> FnOnce(&mut JNIEnv<'local>) -> jni::errors::Result<R>,
{
    let vm = ANDROID_JVM
        .get()
        .ok_or_else(|| "Android JavaVM not initialised".to_string())?;
    let mut env = vm
        .attach_current_thread_permanently()
        .map_err(|e| format!("attach_current_thread: {e}"))?;
    f(&mut env).map_err(|e| format!("JNI: {e}"))
}

/// Run `f` with an attached [`JNIEnv`] and the `NativeActivity` object.
///
/// Like [`with_env`] but also resolves the cached activity global reference, so
/// callers that need to invoke instance methods on the activity (clipboard,
/// window insets) never touch a raw handle.
pub fn with_activity_env<F, R>(f: F) -> Result<R, String>
where
    F: for<'local> FnOnce(&mut JNIEnv<'local>, &JObject<'local>) -> jni::errors::Result<R>,
{
    let activity = ANDROID_ACTIVITY
        .get()
        .ok_or_else(|| "Android activity not initialised".to_string())?;
    with_env(|env| f(env, activity.as_obj()))
}
