pub mod config;
pub mod context;
pub mod state;
pub mod handler;

use gtk::prelude::*;
use gtk::{glib, gdk_pixbuf, gio};
use gtk::gdk_pixbuf::InterpType;
use gstreamer as gst;
use std::ffi::{c_void, CStr};
use glib::translate::from_glib_none;
use std::os::raw::{c_char, c_int, c_uint};
use std::ptr;

// Re-export types for external use (e.g. integration tests)
pub use config::{ModuleConfig, DEADZONE_ARG, DEADZONE_LONG, DEADZONE_DESC, DEADZONE_ARG_DESC};
pub use context::{Window, GtkLock, WindowContext, __IncompleteArrayField};
pub use state::{MODULE_STATE, WindowData, ModuleState};
pub use handler::WindowHandler;

// Scale factor to reduce the image size and avoid cropping/overlap
const SPRITE_SCALE: f64 = 0.6;

#[no_mangle]
pub static module_name: [c_char; 7] = [
    b'a' as c_char,
    b'h' as c_char,
    b'f' as c_char,
    b'a' as c_char,
    b'i' as c_char,
    b'l' as c_char,
    0,
];

#[no_mangle]
pub static module_major_version: c_uint = 4;

#[no_mangle]
pub static module_minor_version: c_uint = 0;

#[no_mangle]
pub static mut module_entries: [glib::ffi::GOptionEntry; 3] = [
    glib::ffi::GOptionEntry { 
        long_name: DEADZONE_LONG.as_ptr() as *const c_char, 
        short_name: 0, 
        flags: 0, 
        arg: glib::ffi::G_OPTION_ARG_STRING, 
        arg_data: unsafe { &raw mut DEADZONE_ARG as *mut _ }, 
        description: DEADZONE_DESC.as_ptr() as *const c_char, 
        arg_description: DEADZONE_ARG_DESC.as_ptr() as *const c_char 
    },
    glib::ffi::GOptionEntry { long_name: ptr::null(), short_name: 0, flags: 0, arg: 0, arg_data: ptr::null_mut(), description: ptr::null(), arg_description: ptr::null() },
    glib::ffi::GOptionEntry { long_name: ptr::null(), short_name: 0, flags: 0, arg: 0, arg_data: ptr::null_mut(), description: ptr::null(), arg_description: ptr::null() },
];

extern "C" {
    fn ahfail_get_resource() -> *mut gio::ffi::GResource;
}

// Helper for tests to inspect window data
/// # Safety
/// `ctx` must be a valid Window pointer.
pub unsafe fn get_window_data_ref(ctx: *mut Window) -> Option<&'static state::WindowData> {
    let ptr = *(*ctx).module_data.as_ptr();
    if ptr.is_null() {
        None
    } else {
        Some(&*(ptr as *mut state::WindowData))
    }
}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn on_activation(_gtklock: *mut GtkLock, _id: c_int) {
    println!("[ahfail] on_activation called");
    if let Err(e) = gtk::init() {
        eprintln!("Failed to initialize GTK bindings: {}", e);
        return;
    }
    if let Err(e) = gst::init() {
        eprintln!("Failed to initialize GStreamer: {}", e);
        return;
    }

    let resource_ptr = ahfail_get_resource();
    if !resource_ptr.is_null() {
         let resource = from_glib_none::<_, gio::Resource>(resource_ptr);
         gio::resources_register(&resource);
    }

    // Load frames
    let mut loaded_frames: Vec<gdk_pixbuf::Pixbuf> = Vec::new();
    match gio::resources_enumerate_children("/ahfail/sprites", gio::ResourceLookupFlags::NONE) {
        Ok(mut frames) => {
            frames.sort();
            for frame_path in frames {
                let full_path = format!("/ahfail/sprites/{}", frame_path);
                match gdk_pixbuf::Pixbuf::from_resource(&full_path) {
                    Ok(pixbuf) => {
                        let w = (pixbuf.width() as f64 * SPRITE_SCALE) as i32;
                        let h = (pixbuf.height() as f64 * SPRITE_SCALE) as i32;
                        if let Some(scaled) = pixbuf.scale_simple(w, h, InterpType::Bilinear) {
                            loaded_frames.push(scaled);
                        } else {
                            loaded_frames.push(pixbuf);
                        }
                    },
                    Err(e) => eprintln!("Failed to load sprite frame {}: {}", full_path, e),
                }
            }
        },
        Err(e) => eprintln!("Failed to enumerate sprites: {}", e),
    }

    let anim_opt = if !loaded_frames.is_empty() {
        let first = &loaded_frames[0];
        let anim = gdk_pixbuf::PixbufSimpleAnim::new(first.width(), first.height(), 12.0);
        anim.set_loop(true);
        for frame in loaded_frames {
            anim.add_frame(&frame);
        }
        Some(anim.upcast())
    } else {
        None
    };

    let config = ModuleConfig::from_args();

    MODULE_STATE.with(|state| {
        let mut state = state.borrow_mut();
        state.animation = anim_opt;
        state.audio_uri = Some("resource:///ahfail/audio/magic-word.mp3".to_string());
        state.config = config;
    });
}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn on_window_create(_gtklock: *mut GtkLock, ctx: *mut Window) {
    if let Some(win_ctx) = WindowContext::new(ctx) {
        WindowHandler::create(&win_ctx);
    }
}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn on_window_destroy(_gtklock: *mut GtkLock, ctx: *mut Window) {
    if let Some(win_ctx) = WindowContext::new(ctx) {
        WindowHandler::destroy(&win_ctx);
    }
}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn on_idle_hide(gtklock: *mut GtkLock) {
    if gtklock.is_null() { return; }
    let garray = (*gtklock).windows;
    if !garray.is_null() {
         let len = (*garray).len;
         let data_ptr = (*garray).data as *mut *mut Window;
         let windows = std::slice::from_raw_parts(data_ptr, len as usize);
         
         for &window_ptr in windows {
             on_window_destroy(ptr::null_mut(), window_ptr);
         }
    }
}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn on_focus_change(_gtklock: *mut GtkLock, _win: *mut Window, _old: *mut Window) {}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn on_idle_show(_gtklock: *mut GtkLock) {}

/// # Safety
/// This function is called by the C host.
#[no_mangle]
pub unsafe extern "C" fn g_module_unload(_module: *mut c_void) {
    MODULE_STATE.with(|state| {
        let mut state = state.borrow_mut();
        state.animation = None;
        state.audio_uri = None;
        state.config.deadzone = None;
    });
}