
#[napi]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SessionStates {
    ConsoleConnected = 1,
    ConsoleDisconnected = 2,
    RemoteConnected = 3,
    RemoteDisconnected = 4,
    UserLogon = 5,
    UserLogoff = 6,
    SessionLocked = 7,
    SessionUnlocked = 8,
    RemoteControl = 9,
    UNKNOWN = 10,
    SessionDestroyed = 11,
    UnableRetrieve = 12, // Cannot get the current Session State, happens when mutex and arc failes
}

#[cfg(target_os = "windows")]
pub mod locked_new {
    use std::sync::{Arc, Mutex};
    use lazy_static::lazy_static;
    use napi::bindgen_prelude::{Error as NapiError, Result as NapiResult, Function};
    use windows::{
        core::{w, Error, Result as WinResult},
        Win32::{
            Foundation::*,
            System::{LibraryLoader::GetModuleHandleW, RemoteDesktop::*},
            UI::WindowsAndMessaging::*,
            UI::WindowsAndMessaging::WNDCLASSEXW,
        },
    };
    use crate::locked_new::SessionStates;

    lazy_static!
    {
        static ref SESSION_STRING: Arc<Mutex<SessionStates>> = Arc::new(Mutex::new(SessionStates::UNKNOWN));
        static ref SESSION_ID: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
    }

    #[napi]
    pub fn session_locked_callback(callback: Function<(SessionStates, u32), ()>) -> NapiResult<bool> {
        // do Windows work inside, returning WinResult
        let inner: WinResult<bool> = (|| {
            unsafe {
                let hinstance = GetModuleHandleW(None)?;

                let class_name = w!("WtsNotifyWindowClass");
                let mut wc = WNDCLASSEXW {
                    cbSize: core::mem::size_of::<WNDCLASSEXW>() as u32,
                    lpfnWndProc: Some(wndproc),
                    hInstance: hinstance.into(),
                    lpszClassName: class_name,
                    ..Default::default()
                };

                let atom = RegisterClassExW(&wc);
                if atom == 0 {
                    return Err(Error::from_win32());
                }

                // Depending on your windows crate version, this may be HWND or WinResult<HWND>.
                // If it's just HWND, remove the `?` and check for null.
                let hwnd: HWND = CreateWindowExW(
                    WINDOW_EX_STYLE::default(),
                    class_name,
                    w!("WTS Session Watcher"),
                    WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                    None,
                    None,
                    Some(hinstance.into()),
                    None,
                )?;

                WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_ALL_SESSIONS);
                ShowWindow(hwnd, SW_HIDE);

                let mut msg = MSG::default();
                while GetMessageW(&mut msg, None, 0, 0).as_bool() {

                    let is_session_change = msg.message == WM_WTSSESSION_CHANGE;

                    TranslateMessage(&msg);
                    DispatchMessageW(&msg);

                    if is_session_change {
                        let state = SESSION_STRING
                            .try_lock()
                            .map(|g| *g)                           // or g.clone()
                            .unwrap_or(SessionStates::UnableRetrieve);
                        let state_id = SESSION_ID
                            .try_lock()
                            .map(|g| *g)                           // or g.clone()
                            .unwrap_or(0);

                        callback.call((state, state_id)).unwrap_or_default();
                    }
                }
            }
            Ok(true)
        })();

        extern "system" fn wndproc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
            unsafe {
                match msg {
                    WM_WTSSESSION_CHANGE => {
                        let session_state = match wparam.0 as u32 {
                            WTS_CONSOLE_CONNECT => SessionStates::ConsoleConnected, //"Console connected",
                            WTS_CONSOLE_DISCONNECT => SessionStates::ConsoleDisconnected, //"Console disconnected",
                            WTS_REMOTE_CONNECT => SessionStates::RemoteConnected, //"Remote connected",
                            WTS_REMOTE_DISCONNECT => SessionStates::RemoteDisconnected, //"Remote disconnected",
                            WTS_SESSION_LOGON => SessionStates::UserLogon, //"User logon",
                            WTS_SESSION_LOGOFF => SessionStates::UserLogoff, //"User logoff",
                            WTS_SESSION_LOCK => SessionStates::SessionLocked, //"Session locked",
                            WTS_SESSION_UNLOCK => SessionStates::SessionUnlocked, //"Session unlocked",
                            WTS_SESSION_REMOTE_CONTROL => SessionStates::RemoteControl, //"Remote control",
                            code => SessionStates::UNKNOWN, //"Unknown WTS event: {code}",
                        };

                        // SET SESSION_STATE for global use outside this function
                        // SESSION_STATE.store(session_state, Ordering::SeqCst);
                        match SESSION_STRING.clone().lock() {
                            Ok(mut m) => *m = session_state,
                            Err(_) => ()
                        };

                        let session_id = lparam.0 as u32;
                        match SESSION_ID.clone().lock() {
                            Ok(mut m) => *m = session_id,
                            Err(_) => ()
                        };
                        LRESULT(0)
                    }
                    WM_DESTROY => {
                        let _ = WTSUnRegisterSessionNotification(hwnd);
                        PostQuitMessage(0);
                        match SESSION_STRING.clone().lock() {
                            Ok(mut m) => *m = SessionStates::SessionDestroyed,
                            Err(_) => ()
                        };

                        LRESULT(0)
                    }
                    _ => DefWindowProcW(hwnd, msg, wparam, lparam),
                }
            }
        }

        // map windows::core::Error -> napi::Error
        inner.map_err(|e| NapiError::from_reason(format!("{e}")))
    }
}

#[cfg(target_os = "macos")]
pub mod locked {
    use std::thread;
    use std::time::Duration;
    use napi::bindgen_prelude::{Error as NapiError, Result as NapiResult, Function};
    use core_foundation::base::{CFGetTypeID, TCFType};
    use core_foundation::boolean::{CFBoolean, CFBooleanGetTypeID};
    use core_foundation::dictionary::CFDictionaryRef;
    use core_foundation::string::CFString;
    use crate::locked_new::SessionStates;

    #[link(name = "ApplicationServices", kind = "framework")]
    extern "C" {
        fn CGSessionCopyCurrentDictionary() -> CFDictionaryRef;
    }


    pub fn session_locked_callback(callback: Function<(SessionStates, u32), ()>) -> NapiResult<bool> {
        let interval_ms: u64 = 5000;

//         loop {
//             let handle = thread::spawn(move || {
//                 let mut last: Option<(SessionStates, u32)> = None;
//                 let sleep_dur = Duration::from_millis(interval_ms);
// 
//                 loop {
//                     let current = session_locked();
// 
//                     if last.map(|x| x != current).unwrap_or(true) {
//                         // Only notify on change (or first sample).
//                         // NonBlocking means we drop the call if JS is busy (no backpressure).
//                         let _ = callback.call(current);
//                         last = Some(current);
//                     }
// 
//                     thread::sleep(sleep_dur);
//                 }
//             });
//         }
        Ok(true)
    }

    fn session_locked() -> (SessionStates, u32) {
        unsafe {
            let dict_ref = CGSessionCopyCurrentDictionary();
            if dict_ref.is_null() {
                return (SessionStates::UNKNOWN, 0);
            }

            let dict = core_foundation::dictionary::CFDictionary::<
                CFString,
                core_foundation::base::CFTypeRef,
            >::wrap_under_create_rule(dict_ref);

            let key = CFString::from_static_string("CGSSessionScreenIsLocked");

            // TODO test was für schlüssel es villeicht noch alles gibt
            println!("{:#?}", dict);

            if let Some(value_ref) = dict.find(&key) {
                // value_ref: CFTypeRef (*const c_void). Check its runtime type first.
                if CFGetTypeID(*value_ref) == CFBooleanGetTypeID() {
                    let b = CFBoolean::wrap_under_get_rule(*value_ref as *const _);
                    return (SessionStates::SessionLocked, 0);
                }
            }

            (SessionStates::SessionUnlocked, 0)
        }
    }
}
