use helios_common::protocol::{sanitize_label, WindowInfo};
use std::collections::HashMap;

// ── Windows implementation ──────────────────────────────────────────────────

#[cfg(windows)]
mod win_impl {
    use super::*;
    use windows::Win32::Foundation::{BOOL, HWND, LPARAM};
    use windows::Win32::Graphics::Dwm::{DwmGetWindowAttribute, DWMWA_CLOAKED};
    use windows::Win32::UI::WindowsAndMessaging::{
        BringWindowToTop, EnumWindows, GetWindowTextW,
        IsWindowVisible, SetForegroundWindow, ShowWindow,
        SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE,
    };
    use windows::Win32::UI::Input::KeyboardAndMouse::{
        keybd_event, KEYEVENTF_KEYUP, VK_MENU,
    };
    use windows::Win32::System::Threading::{
        OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_FORMAT,
        PROCESS_QUERY_LIMITED_INFORMATION,
    };
    use windows::Win32::System::ProcessStatus::GetModuleBaseNameW;

    unsafe extern "system" fn enum_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
        let list = &mut *(lparam.0 as *mut Vec<HWND>);
        list.push(hwnd);
        BOOL(1)
    }

    fn get_all_hwnds() -> Vec<HWND> {
        let mut list: Vec<HWND> = Vec::new();
        unsafe {
            let _ = EnumWindows(
                Some(enum_callback),
                LPARAM(&mut list as *mut Vec<HWND> as isize),
            );
        }
        list
    }

    fn is_cloaked(hwnd: HWND) -> bool {
        let mut cloaked: u32 = 0;
        unsafe {
            DwmGetWindowAttribute(
                hwnd,
                DWMWA_CLOAKED,
                &mut cloaked as *mut u32 as *mut _,
                std::mem::size_of::<u32>() as u32,
            ).is_err() == false && cloaked != 0
        }
    }

    fn hwnd_title(hwnd: HWND) -> String {
        let mut buf = [0u16; 512];
        let len = unsafe { GetWindowTextW(hwnd, &mut buf) };
        String::from_utf16_lossy(&buf[..len as usize])
    }

    /// Get the process name (exe without extension) for a window handle.
    /// Tries `GetModuleBaseNameW` first, then `QueryFullProcessImageNameW`
    /// (which works for elevated processes with `PROCESS_QUERY_LIMITED_INFORMATION`).
    fn hwnd_process_name(hwnd: HWND) -> Option<String> {
        unsafe {
            let mut pid: u32 = 0;
            windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId(hwnd, Some(&mut pid));
            if pid == 0 {
                return None;
            }
            let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?;

            // Try GetModuleBaseNameW first
            let mut buf = [0u16; 260];
            let len = GetModuleBaseNameW(handle, None, &mut buf);
            if len > 0 {
                let _ = windows::Win32::Foundation::CloseHandle(handle);
                let name = String::from_utf16_lossy(&buf[..len as usize]);
                return Some(strip_exe_ext(&name));
            }

            // Fallback: QueryFullProcessImageNameW (works for elevated processes)
            let mut buf2 = [0u16; 1024];
            let mut size = buf2.len() as u32;
            let ok = QueryFullProcessImageNameW(handle, PROCESS_NAME_FORMAT(0), windows::core::PWSTR(buf2.as_mut_ptr()), &mut size);
            let _ = windows::Win32::Foundation::CloseHandle(handle);
            if ok.is_ok() && size > 0 {
                let full_path = String::from_utf16_lossy(&buf2[..size as usize]);
                // Extract filename from full path
                let filename = full_path.rsplit('\\').next().unwrap_or(&full_path);
                return Some(strip_exe_ext(filename));
            }

            None
        }
    }

    fn strip_exe_ext(name: &str) -> String {
        name.strip_suffix(".exe")
            .or(name.strip_suffix(".EXE"))
            .unwrap_or(name)
            .to_string()
    }

    pub fn list_windows() -> Result<Vec<WindowInfo>, String> {
        let hwnds = get_all_hwnds();

        // Collect visible windows with non-empty titles
        let mut raw_windows: Vec<(HWND, String, String)> = Vec::new();
        for hwnd in &hwnds {
            let visible = unsafe { IsWindowVisible(*hwnd).as_bool() };
            if !visible {
                continue;
            }
            if is_cloaked(*hwnd) {
                continue;
            }
            let title = hwnd_title(*hwnd);
            if title.is_empty() {
                continue;
            }
            // "Program Manager" is always the Windows desktop shell, never a real window
            if title.trim().eq_ignore_ascii_case("program manager") {
                continue;
            }
            let process_name = hwnd_process_name(*hwnd).unwrap_or_default();
            let proc_lower = process_name.to_lowercase();
            // ApplicationFrameHost is a UWP container — always a duplicate of the real app window
            // MsEdgeWebView2 is an embedded browser component, never a standalone user window
            if proc_lower == "applicationframehost" || proc_lower == "msedgewebview2" {
                continue;
            }
            raw_windows.push((*hwnd, title, process_name));
        }

        // Generate labels with dedup numbering
        let mut label_index: HashMap<String, usize> = HashMap::new();
        let mut windows = Vec::new();
        for (hwnd, title, proc_name) in raw_windows {
            let base = if proc_name.is_empty() {
                sanitize_label(&title)
            } else {
                sanitize_label(&proc_name)
            };
            if base.is_empty() {
                continue;
            }
            let idx = label_index.entry(base.clone()).or_insert(0);
            *idx += 1;
            let label = if *idx == 1 {
                base.clone()
            } else {
                format!("{}{}", base, idx)
            };
            windows.push(WindowInfo {
                id: hwnd.0 as u64,
                title,
                label,
                visible: true,
            });
        }
        Ok(windows)
    }

    pub fn minimize_all() -> Result<(), String> {
        let hwnds = get_all_hwnds();
        for hwnd in hwnds {
            let visible = unsafe { IsWindowVisible(hwnd).as_bool() };
            let title = hwnd_title(hwnd);
            if visible && !title.is_empty() {
                unsafe {
                    let _ = ShowWindow(hwnd, SW_MINIMIZE);
                }
            }
        }
        Ok(())
    }

    unsafe fn force_foreground(hwnd: HWND) {
        keybd_event(VK_MENU.0 as u8, 0, Default::default(), 0);
        keybd_event(VK_MENU.0 as u8, 0, KEYEVENTF_KEYUP, 0);
        ShowWindow(hwnd, SW_RESTORE);
        BringWindowToTop(hwnd).ok();
        SetForegroundWindow(hwnd);
    }

    pub fn focus_window(window_id: u64) -> Result<(), String> {
        let hwnd = HWND(window_id as isize);
        unsafe { force_foreground(hwnd); }
        Ok(())
    }

    pub fn maximize_and_focus(window_id: u64) -> Result<(), String> {
        let hwnd = HWND(window_id as isize);
        unsafe {
            ShowWindow(hwnd, SW_MAXIMIZE);
            force_foreground(hwnd);
        }
        Ok(())
    }
}

// ── Non-Windows stubs ───────────────────────────────────────────────────────

#[cfg(not(windows))]
mod win_impl {
    use super::*;

    pub fn list_windows() -> Result<Vec<WindowInfo>, String> {
        Err("Window management is only supported on Windows".to_string())
    }

    pub fn minimize_all() -> Result<(), String> {
        Err("Window management is only supported on Windows".to_string())
    }

    pub fn focus_window(_window_id: u64) -> Result<(), String> {
        Err("Window management is only supported on Windows".to_string())
    }

    pub fn maximize_and_focus(_window_id: u64) -> Result<(), String> {
        Err("Window management is only supported on Windows".to_string())
    }
}

// ── Public API ──────────────────────────────────────────────────────────────

pub fn list_windows() -> Result<Vec<WindowInfo>, String> {
    win_impl::list_windows()
}

pub fn minimize_all() -> Result<(), String> {
    win_impl::minimize_all()
}

pub fn focus_window(window_id: u64) -> Result<(), String> {
    win_impl::focus_window(window_id)
}

pub fn maximize_and_focus(window_id: u64) -> Result<(), String> {
    win_impl::maximize_and_focus(window_id)
}
