use cocoa::base::{id, nil};
use cocoa::foundation::{NSArray, NSAutoreleasePool, NSString};
use objc::{msg_send, sel, sel_impl};
use serde::Serialize;

#[derive(Serialize, Debug)]
pub struct RunningApp {
    pub name: String,
    pub bundle_id: String,
}

pub fn get_running_applications() -> Vec<RunningApp> {
    let mut apps = Vec::new();

    unsafe {
        let pool = NSAutoreleasePool::new(nil);

        // [NSWorkspace sharedWorkspace]
        let workspace_class = objc::runtime::Class::get("NSWorkspace").unwrap();
        let shared_workspace: id = msg_send![workspace_class, sharedWorkspace];

        // [sharedWorkspace runningApplications]
        let running_apps: id = msg_send![shared_workspace, runningApplications];
        let count: usize = msg_send![running_apps, count];

        for i in 0..count {
            let app: id = msg_send![running_apps, objectAtIndex: i];

            // Check if it's a regular GUI application
            // activationPolicy: 0 = regular, 1 = accessory, 2 = prohibited
            let policy: isize = msg_send![app, activationPolicy];
            if policy != 0 {
                continue;
            }

            let bundle_id_ns: id = msg_send![app, bundleIdentifier];
            let name_ns: id = msg_send![app, localizedName];

            if bundle_id_ns != nil && name_ns != nil {
                let bundle_id = nsstring_to_string(bundle_id_ns);
                let name = nsstring_to_string(name_ns);

                apps.push(RunningApp { name, bundle_id });
            }
        }

        // We don't drain the pool here as it might be handled by Tauri's main loop if we are called frequently,
        // but for a one-off command it's safer.
        let _: () = msg_send![pool, release];
    }

    // Sort by name for better UI experience
    apps.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
    apps
}

unsafe fn nsstring_to_string(ns_string: id) -> String {
    let char_ptr: *const std::os::raw::c_char = msg_send![ns_string, UTF8String];
    if char_ptr.is_null() {
        return String::new();
    }
    std::ffi::CStr::from_ptr(char_ptr)
        .to_string_lossy()
        .into_owned()
}
