// JNI helpers used by modules/platform/android.sx. Kept in the library
// so consumers don't need to vendor an identically-named copy. The sx
// compiler resolves `#source "vendors/..."` against the stdlib search
// paths in addition to the consumer's project root.

#ifdef __ANDROID__
#include <android/native_activity.h>
#include <android/input.h>
#include <jni.h>

// Mirror of struct android_app (NDK 29 / arm64) up to the fields we touch.
// Avoids depending on the glue header in the sx library compile path.
struct sx_android_app_min {
    void* userData;
    void (*onAppCmd)(struct sx_android_app_min* app, int cmd);
    int (*onInputEvent)(struct sx_android_app_min* app, AInputEvent* event);
    // ...rest of struct ignored; we only assign onInputEvent.
};

// Install an sx-side handler as `app->onInputEvent`. native_app_glue's
// process_input loop calls this for every AInputEvent it pulls off the
// input queue. Returning 1 marks the event as consumed.
void sx_android_install_input_handler(void* app,
    int (*handler)(void* app, void* event)) {
    if (app == 0) return;
    struct sx_android_app_min* a = (struct sx_android_app_min*)app;
    a->onInputEvent = (int (*)(struct sx_android_app_min*, AInputEvent*))handler;
}

// Query system-bar insets (status bar, nav bar) via JNI:
//   activity → getWindow() → getDecorView()
//            → getRootWindowInsets()
//            → getSystemWindowInset[Top|Left|Bottom|Right]()
// Out params receive physical-pixel insets; the sx caller divides by
// dpi_scale to convert to logical units. Falls back to zeros if the
// view isn't attached yet (e.g. called before the first frame).
void sx_android_query_safe_insets(void* activity_ptr,
                                  int* out_top, int* out_left,
                                  int* out_bottom, int* out_right) {
    *out_top = 0; *out_left = 0; *out_bottom = 0; *out_right = 0;
    if (activity_ptr == 0) return;

    ANativeActivity* act = (ANativeActivity*)activity_ptr;
    JavaVM* vm = act->vm;
    if (vm == 0) return;

    JNIEnv* env = 0;
    int already_attached = 1;
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        already_attached = 0;
        if ((*vm)->AttachCurrentThread(vm, &env, 0) != 0) return;
    }

    jobject activity_obj = act->clazz;
    jclass activity_cls = (*env)->GetObjectClass(env, activity_obj);

    jmethodID m_getWindow = (*env)->GetMethodID(env, activity_cls,
        "getWindow", "()Landroid/view/Window;");
    if (m_getWindow == 0) goto done;
    jobject window = (*env)->CallObjectMethod(env, activity_obj, m_getWindow);
    if (window == 0) goto done;

    jclass window_cls = (*env)->GetObjectClass(env, window);
    jmethodID m_getDecorView = (*env)->GetMethodID(env, window_cls,
        "getDecorView", "()Landroid/view/View;");
    if (m_getDecorView == 0) goto done;
    jobject decor = (*env)->CallObjectMethod(env, window, m_getDecorView);
    if (decor == 0) goto done;

    jclass view_cls = (*env)->GetObjectClass(env, decor);
    jmethodID m_getRootInsets = (*env)->GetMethodID(env, view_cls,
        "getRootWindowInsets", "()Landroid/view/WindowInsets;");
    if (m_getRootInsets == 0) goto done;
    jobject insets = (*env)->CallObjectMethod(env, decor, m_getRootInsets);
    if (insets == 0) goto done;

    jclass insets_cls = (*env)->GetObjectClass(env, insets);
    jmethodID m_top = (*env)->GetMethodID(env, insets_cls,
        "getSystemWindowInsetTop", "()I");
    jmethodID m_left = (*env)->GetMethodID(env, insets_cls,
        "getSystemWindowInsetLeft", "()I");
    jmethodID m_bottom = (*env)->GetMethodID(env, insets_cls,
        "getSystemWindowInsetBottom", "()I");
    jmethodID m_right = (*env)->GetMethodID(env, insets_cls,
        "getSystemWindowInsetRight", "()I");
    if (m_top)    *out_top    = (*env)->CallIntMethod(env, insets, m_top);
    if (m_left)   *out_left   = (*env)->CallIntMethod(env, insets, m_left);
    if (m_bottom) *out_bottom = (*env)->CallIntMethod(env, insets, m_bottom);
    if (m_right)  *out_right  = (*env)->CallIntMethod(env, insets, m_right);

done:
    if (!already_attached) (*vm)->DetachCurrentThread(vm);
}
#endif
