//! Windows UI Automation accessibility provider.

use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::Duration;

use windows::Win32::Foundation::*;
use windows::Win32::System::Com::{CoInitializeEx, COINIT};

use windows::Win32::UI::Accessibility::*;

use xa11y_core::{
    Action, ActionData, CancelHandle, ElementData, Error, Event, EventReceiver, EventType,
    Provider, RawPlatformData, Rect, Result, Role, StateSet, Subscription, Toggled,
};

static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);

/// Initialize COM for UIA. Called once per WindowsProvider creation.
/// Does not uninitialize on drop — COM lifetime is managed by the process.
fn ensure_com_initialized() -> windows::core::Result<()> {
    // Use MTA (0x0) — same mode as the Rust runtime default.
    // STA (0x2) would conflict with Rust's thread pool.
    let hr = unsafe { CoInitializeEx(None, COINIT(0x0)) };
    // S_OK, S_FALSE (already initialized), or RPC_E_CHANGED_MODE are all fine
    if hr.is_err() && hr.0 as u32 != 0x80010106 {
        hr.ok()?;
    }
    Ok(())
}

/// Windows accessibility provider using UI Automation.
pub struct WindowsProvider {
    automation: IUIAutomation,
    /// Cached UIA elements for action dispatch (keyed by handle ID).
    handle_cache: Mutex<HashMap<u64, IUIAutomationElement>>,
}

// IUIAutomation is COM and thread-safe via proxy
unsafe impl Send for WindowsProvider {}
unsafe impl Sync for WindowsProvider {}

impl WindowsProvider {
    pub fn new() -> Result<Self> {
        ensure_com_initialized().map_err(|e| Error::Platform {
            code: e.code().0 as i64,
            message: format!("COM initialization failed: {}", e),
        })?;
        let automation: IUIAutomation = unsafe {
            windows::Win32::System::Com::CoCreateInstance(
                &CUIAutomation8,
                None,
                windows::Win32::System::Com::CLSCTX_ALL,
            )
        }
        .map_err(|e| Error::Platform {
            code: e.code().0 as i64,
            message: format!("Failed to create IUIAutomation: {}", e),
        })?;
        Ok(Self {
            automation,
            handle_cache: Mutex::new(HashMap::new()),
        })
    }

    /// Re-acquire a UIA element via its native window handle.
    /// This triggers WM_GETOBJECT which activates AccessKit's UIA provider,
    /// ensuring the element's children include virtual accessibility elements.
    fn reacquire_via_hwnd(
        &self,
        element: &IUIAutomationElement,
    ) -> std::result::Result<IUIAutomationElement, ()> {
        let hwnd = unsafe { element.CurrentNativeWindowHandle() }.map_err(|_| ())?;
        if hwnd.0.is_null() {
            return Err(());
        }
        unsafe { self.automation.ElementFromHandle(hwnd) }.map_err(|_| ())
    }

    fn find_app_by_pid(&self, pid: u32) -> Result<(IUIAutomationElement, String)> {
        let root = unsafe { self.automation.GetRootElement() }.map_err(|e| Error::Platform {
            code: e.code().0 as i64,
            message: format!("GetRootElement failed: {}", e),
        })?;

        let condition = unsafe {
            self.automation.CreatePropertyCondition(
                UIA_ProcessIdPropertyId,
                &windows::core::VARIANT::from(pid as i32),
            )
        }
        .map_err(|e| Error::Platform {
            code: e.code().0 as i64,
            message: format!("CreatePropertyCondition failed: {}", e),
        })?;

        let el = unsafe { root.FindFirst(TreeScope_Children, &condition) }.map_err(|_| {
            Error::Platform {
                code: -1,
                message: format!("No window found for PID {}", pid),
            }
        })?;

        // Re-acquire via HWND to activate AccessKit provider
        let el = self.reacquire_via_hwnd(&el).unwrap_or(el);

        let name = unsafe { el.CurrentName() }
            .map(|s| s.to_string())
            .unwrap_or_default();

        Ok((el, name))
    }

    /// Cache a UIA element and return its handle ID.
    fn cache_element(&self, uia: IUIAutomationElement) -> u64 {
        let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
        self.handle_cache.lock().unwrap().insert(handle, uia);
        handle
    }

    /// Look up a cached UIA element by handle.
    fn get_cached(&self, handle: u64) -> Result<IUIAutomationElement> {
        self.handle_cache
            .lock()
            .unwrap()
            .get(&handle)
            .cloned()
            .ok_or(Error::ElementStale {
                selector: format!("handle:{}", handle),
            })
    }

    /// Build an ElementData from a UIA element, caching the native handle.
    fn build_element_data(&self, element: &IUIAutomationElement, pid: Option<u32>) -> ElementData {
        let control_type = unsafe { element.CurrentControlType() }.unwrap_or(UIA_CONTROLTYPE_ID(0));
        let mut role = map_uia_control_type(control_type);

        // Refine role using AriaRole property for elements that UIA maps ambiguously
        // (e.g., Alert/Heading both become ControlType.Text, Dialog becomes Window)
        if matches!(
            role,
            Role::StaticText | Role::Window | Role::Group | Role::Unknown
        ) {
            if let Ok(v) = unsafe { element.GetCurrentPropertyValue(UIA_AriaRolePropertyId) } {
                if let Ok(aria) = windows::core::BSTR::try_from(&v) {
                    let aria_str = aria.to_string();
                    match aria_str.as_str() {
                        "alert" => role = Role::Alert,
                        "dialog" | "alertdialog" => role = Role::Dialog,
                        "heading" => role = Role::Heading,
                        "separator" => role = Role::Separator,
                        "progressbar" => role = Role::ProgressBar,
                        "link" => role = Role::Link,
                        _ => {}
                    }
                }
            }
        }

        let name = unsafe { element.CurrentName() }
            .ok()
            .map(|s| s.to_string())
            .filter(|s| !s.is_empty());

        // Value: use ValuePattern or RangeValuePattern
        let value = get_value(element, role);

        // Try FullDescription first (AccessKit's description), then HelpText
        let description = unsafe { element.GetCurrentPropertyValue(UIA_FullDescriptionPropertyId) }
            .ok()
            .and_then(|v| {
                let bstr = windows::core::BSTR::try_from(&v).ok()?;
                let s = bstr.to_string();
                if s.is_empty() {
                    None
                } else {
                    Some(s)
                }
            })
            .or_else(|| {
                unsafe { element.GetCurrentPropertyValue(UIA_HelpTextPropertyId) }
                    .ok()
                    .and_then(|v| {
                        let bstr = windows::core::BSTR::try_from(&v).ok()?;
                        let s = bstr.to_string();
                        if s.is_empty() {
                            None
                        } else {
                            Some(s)
                        }
                    })
            });

        let states = parse_states(element, role);

        // Bounds
        let bounds = unsafe { element.CurrentBoundingRectangle() }
            .ok()
            .and_then(|r| {
                let width = (r.right - r.left).max(0) as u32;
                let height = (r.bottom - r.top).max(0) as u32;
                if width == 0 && height == 0 {
                    None
                } else {
                    Some(Rect {
                        x: r.left,
                        y: r.top,
                        width,
                        height,
                    })
                }
            });

        // Actions
        let actions = get_actions(element, role);

        // Raw platform data
        let raw = {
            let automation_id = unsafe { element.CurrentAutomationId() }
                .ok()
                .map(|s| s.to_string())
                .filter(|s| !s.is_empty());
            let class_name = unsafe { element.CurrentClassName() }
                .ok()
                .map(|s| s.to_string())
                .filter(|s| !s.is_empty());
            RawPlatformData::Windows {
                control_type_id: control_type.0,
                automation_id,
                class_name,
            }
        };

        // Stable ID: AutomationId (always captured for cross-snapshot correlation)
        let stable_id = unsafe { element.CurrentAutomationId() }
            .ok()
            .map(|s| s.to_string())
            .filter(|s| !s.is_empty());

        let (numeric_value, min_value, max_value) = if matches!(
            role,
            Role::Slider | Role::ProgressBar | Role::ScrollBar | Role::SpinButton
        ) {
            if let Ok(pattern) = unsafe {
                element
                    .GetCurrentPatternAs::<IUIAutomationRangeValuePattern>(UIA_RangeValuePatternId)
            } {
                (
                    unsafe { pattern.CurrentValue() }.ok(),
                    unsafe { pattern.CurrentMinimum() }.ok(),
                    unsafe { pattern.CurrentMaximum() }.ok(),
                )
            } else {
                (None, None, None)
            }
        } else {
            (None, None, None)
        };

        let handle = self.cache_element(element.clone());

        ElementData {
            role,
            name,
            value,
            description,
            bounds,
            actions,
            states,
            stable_id,
            numeric_value,
            min_value,
            max_value,
            pid,
            raw,
            handle,
        }
    }

    /// Get direct UIA children of an element. Tries FindAll first (works with
    /// AccessKit fragment roots), falls back to RawViewWalker for native elements.
    fn uia_children(&self, element: &IUIAutomationElement) -> Vec<IUIAutomationElement> {
        let mut children = Vec::new();

        let found_via_find_all =
            if let Ok(true_cond) = unsafe { self.automation.CreateTrueCondition() } {
                if let Ok(child_arr) = unsafe { element.FindAll(TreeScope_Children, &true_cond) } {
                    let count = unsafe { child_arr.Length() }.unwrap_or(0);
                    if count > 0 {
                        for i in 0..count {
                            if let Ok(child_el) = unsafe { child_arr.GetElement(i) } {
                                children.push(child_el);
                            }
                        }
                        true
                    } else {
                        false
                    }
                } else {
                    false
                }
            } else {
                false
            };

        // Fall back to RawViewWalker if FindAll found nothing
        if !found_via_find_all {
            if let Ok(walker) = unsafe { self.automation.RawViewWalker() } {
                let mut child = unsafe { walker.GetFirstChildElement(element) }.ok();
                while let Some(ref child_el) = child {
                    children.push(child_el.clone());
                    child = unsafe { walker.GetNextSiblingElement(child_el) }.ok();
                }
            }
        }

        children
    }
}

impl Provider for WindowsProvider {
    fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
        match element {
            None => {
                // Top-level: list all GUI application windows
                let root =
                    unsafe { self.automation.GetRootElement() }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: format!("GetRootElement failed: {}", e),
                    })?;

                let condition = unsafe {
                    self.automation.CreatePropertyCondition(
                        UIA_ControlTypePropertyId,
                        &windows::core::VARIANT::from(UIA_WindowControlTypeId.0),
                    )
                }
                .map_err(|e| Error::Platform {
                    code: e.code().0 as i64,
                    message: format!("CreatePropertyCondition failed: {}", e),
                })?;

                let windows =
                    unsafe { root.FindAll(TreeScope_Children, &condition) }.map_err(|e| {
                        Error::Platform {
                            code: e.code().0 as i64,
                            message: format!("FindAll failed: {}", e),
                        }
                    })?;

                let count = unsafe { windows.Length() }.unwrap_or(0);
                let mut results = Vec::new();
                let mut seen_pids = HashSet::new();

                for i in 0..count {
                    if let Ok(el) = unsafe { windows.GetElement(i) } {
                        let pid = unsafe { el.CurrentProcessId() }.unwrap_or(0) as u32;
                        if pid == 0 || !seen_pids.insert(pid) {
                            continue;
                        }
                        let name = unsafe { el.CurrentName() }
                            .map(|s| s.to_string())
                            .unwrap_or_default();
                        if name.is_empty() {
                            continue;
                        }
                        // Re-acquire via HWND to activate AccessKit provider
                        let el = self.reacquire_via_hwnd(&el).unwrap_or(el);
                        let mut data = self.build_element_data(&el, Some(pid));
                        // Ensure the name is set from the window title
                        if data.name.is_none() {
                            data.name = Some(name);
                        }
                        results.push(data);
                    }
                }

                Ok(results)
            }
            Some(element_data) => {
                let uia = self.get_cached(element_data.handle)?;
                let children = self.uia_children(&uia);
                let pid = element_data.pid;
                Ok(children
                    .iter()
                    .map(|child| self.build_element_data(child, pid))
                    .collect())
            }
        }
    }

    fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
        let uia = self.get_cached(element.handle)?;
        if let Ok(walker) = unsafe { self.automation.RawViewWalker() } {
            if let Ok(parent) = unsafe { walker.GetParentElement(&uia) } {
                // Check if the parent is the desktop root (no further parent)
                let parent_parent = unsafe { walker.GetParentElement(&parent) };
                if parent_parent.is_err() {
                    // Parent is the desktop root — this element is top-level
                    return Ok(None);
                }
                let data = self.build_element_data(&parent, element.pid);
                return Ok(Some(data));
            }
        }
        Ok(None)
    }

    fn perform_action(
        &self,
        element: &ElementData,
        action: Action,
        data: Option<ActionData>,
    ) -> Result<()> {
        let uia_element = self.get_cached(element.handle)?;

        match action {
            Action::Press | Action::Toggle | Action::Select => {
                // Try InvokePattern first, then TogglePattern, then SelectionItemPattern
                if let Ok(pattern) = unsafe {
                    uia_element
                        .GetCurrentPatternAs::<IUIAutomationInvokePattern>(UIA_InvokePatternId)
                } {
                    unsafe { pattern.Invoke() }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "Invoke failed".to_string(),
                    })?;
                    return Ok(());
                }
                if let Ok(pattern) = unsafe {
                    uia_element
                        .GetCurrentPatternAs::<IUIAutomationTogglePattern>(UIA_TogglePatternId)
                } {
                    unsafe { pattern.Toggle() }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "Toggle failed".to_string(),
                    })?;
                    return Ok(());
                }
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationSelectionItemPattern>(
                        UIA_SelectionItemPatternId,
                    )
                } {
                    unsafe { pattern.Select() }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "Select failed".to_string(),
                    })?;
                    return Ok(());
                }
                Err(Error::ActionNotSupported {
                    action,
                    role: element.role,
                })
            }

            Action::Focus => {
                unsafe { uia_element.SetFocus() }.map_err(|e| Error::Platform {
                    code: e.code().0 as i64,
                    message: "SetFocus failed".to_string(),
                })?;
                Ok(())
            }

            Action::SetValue => match data {
                Some(ActionData::NumericValue(v)) => {
                    if let Ok(pattern) = unsafe {
                        uia_element.GetCurrentPatternAs::<IUIAutomationRangeValuePattern>(
                            UIA_RangeValuePatternId,
                        )
                    } {
                        unsafe { pattern.SetValue(v) }.map_err(|e| Error::Platform {
                            code: e.code().0 as i64,
                            message: "RangeValue.SetValue failed".to_string(),
                        })?;
                        return Ok(());
                    }
                    // Fall back to ValuePattern with string
                    if let Ok(pattern) = unsafe {
                        uia_element
                            .GetCurrentPatternAs::<IUIAutomationValuePattern>(UIA_ValuePatternId)
                    } {
                        let s: windows::core::BSTR = v.to_string().into();
                        unsafe { pattern.SetValue(&s) }.map_err(|e| Error::Platform {
                            code: e.code().0 as i64,
                            message: "Value.SetValue failed".to_string(),
                        })?;
                        return Ok(());
                    }
                    Err(Error::Platform {
                        code: -1,
                        message: "No Value or RangeValue pattern".to_string(),
                    })
                }
                Some(ActionData::Value(text)) => {
                    if let Ok(pattern) = unsafe {
                        uia_element
                            .GetCurrentPatternAs::<IUIAutomationValuePattern>(UIA_ValuePatternId)
                    } {
                        let s: windows::core::BSTR = text.into();
                        unsafe { pattern.SetValue(&s) }
                            .map_err(|_| Error::TextValueNotSupported)?;
                        return Ok(());
                    }
                    Err(Error::TextValueNotSupported)
                }
                _ => Err(Error::Platform {
                    code: -1,
                    message: "SetValue requires ActionData".to_string(),
                }),
            },

            Action::Expand => {
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationExpandCollapsePattern>(
                        UIA_ExpandCollapsePatternId,
                    )
                } {
                    // Ignore errors (may already be expanded)
                    let _ = unsafe { pattern.Expand() };
                    return Ok(());
                }
                if let Ok(pattern) = unsafe {
                    uia_element
                        .GetCurrentPatternAs::<IUIAutomationInvokePattern>(UIA_InvokePatternId)
                } {
                    let _ = unsafe { pattern.Invoke() };
                }
                Ok(())
            }

            Action::Collapse => {
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationExpandCollapsePattern>(
                        UIA_ExpandCollapsePatternId,
                    )
                } {
                    // Ignore errors (may already be collapsed)
                    let _ = unsafe { pattern.Collapse() };
                    return Ok(());
                }
                if let Ok(pattern) = unsafe {
                    uia_element
                        .GetCurrentPatternAs::<IUIAutomationInvokePattern>(UIA_InvokePatternId)
                } {
                    let _ = unsafe { pattern.Invoke() };
                }
                Ok(())
            }

            Action::Increment => {
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationRangeValuePattern>(
                        UIA_RangeValuePatternId,
                    )
                } {
                    let current = unsafe { pattern.CurrentValue() }.unwrap_or(0.0);
                    let small = unsafe { pattern.CurrentSmallChange() }.unwrap_or(1.0);
                    let step = if small <= 0.0 { 1.0 } else { small };
                    unsafe { pattern.SetValue(current + step) }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "Increment failed".to_string(),
                    })?;
                    return Ok(());
                }
                Err(Error::ActionNotSupported {
                    action,
                    role: element.role,
                })
            }

            Action::Decrement => {
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationRangeValuePattern>(
                        UIA_RangeValuePatternId,
                    )
                } {
                    let current = unsafe { pattern.CurrentValue() }.unwrap_or(0.0);
                    let small = unsafe { pattern.CurrentSmallChange() }.unwrap_or(1.0);
                    let step = if small <= 0.0 { 1.0 } else { small };
                    unsafe { pattern.SetValue(current - step) }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "Decrement failed".to_string(),
                    })?;
                    return Ok(());
                }
                Err(Error::ActionNotSupported {
                    action,
                    role: element.role,
                })
            }

            Action::ShowMenu => {
                // No direct UIA equivalent; try context menu via legacy
                Err(Error::ActionNotSupported {
                    action,
                    role: element.role,
                })
            }

            Action::ScrollIntoView => {
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationScrollItemPattern>(
                        UIA_ScrollItemPatternId,
                    )
                } {
                    let _ = unsafe { pattern.ScrollIntoView() };
                }
                Ok(())
            }

            Action::Blur => {
                // Focus the desktop root to blur the current element
                let root =
                    unsafe { self.automation.GetRootElement() }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "GetRootElement failed".to_string(),
                    })?;
                unsafe { root.SetFocus() }.map_err(|e| Error::Platform {
                    code: e.code().0 as i64,
                    message: "SetFocus on root failed".to_string(),
                })?;
                Ok(())
            }

            Action::ScrollDown | Action::ScrollRight => {
                let amount = match data {
                    Some(ActionData::ScrollAmount(amount)) => amount,
                    _ => {
                        return Err(Error::Platform {
                            code: -1,
                            message: "Scroll requires ActionData::ScrollAmount".to_string(),
                        })
                    }
                };
                if let Ok(pattern) = unsafe {
                    uia_element
                        .GetCurrentPatternAs::<IUIAutomationScrollPattern>(UIA_ScrollPatternId)
                } {
                    let count = (amount.abs() as u32).max(1);
                    let is_vertical = matches!(action, Action::ScrollDown);
                    for _ in 0..count {
                        let (h, v) = if is_vertical {
                            let v = if amount >= 0.0 {
                                ScrollAmount_SmallIncrement
                            } else {
                                ScrollAmount_SmallDecrement
                            };
                            (ScrollAmount_NoAmount, v)
                        } else {
                            let h = if amount >= 0.0 {
                                ScrollAmount_SmallIncrement
                            } else {
                                ScrollAmount_SmallDecrement
                            };
                            (h, ScrollAmount_NoAmount)
                        };
                        let _ = unsafe { pattern.Scroll(h, v) };
                    }
                    return Ok(());
                }
                Err(Error::ActionNotSupported {
                    action,
                    role: element.role,
                })
            }

            Action::SetTextSelection => {
                let (start, end) = match data {
                    Some(ActionData::TextSelection { start, end }) => (start, end),
                    _ => {
                        return Err(Error::Platform {
                            code: -1,
                            message: "SetTextSelection requires ActionData::TextSelection"
                                .to_string(),
                        })
                    }
                };
                if let Ok(pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationTextPattern>(UIA_TextPatternId)
                } {
                    let range =
                        unsafe { pattern.DocumentRange() }.map_err(|e| Error::Platform {
                            code: e.code().0 as i64,
                            message: "DocumentRange failed".to_string(),
                        })?;
                    // Collapse and move to start position
                    let _ = unsafe { range.Move(TextUnit_Character, start as i32) };
                    // Extend end to selection length
                    let _ = unsafe {
                        range.MoveEndpointByUnit(
                            TextPatternRangeEndpoint_End,
                            TextUnit_Character,
                            (end - start) as i32,
                        )
                    };
                    unsafe { range.Select() }.map_err(|e| Error::Platform {
                        code: e.code().0 as i64,
                        message: "Select range failed".to_string(),
                    })?;
                    return Ok(());
                }
                Err(Error::ActionNotSupported {
                    action,
                    role: element.role,
                })
            }

            Action::TypeText => {
                let text = match data {
                    Some(ActionData::Value(text)) => text,
                    _ => {
                        return Err(Error::Platform {
                            code: -1,
                            message: "TypeText requires ActionData::Value".to_string(),
                        })
                    }
                };
                // Insert text via ValuePattern (accessibility API, not input simulation).
                // Get current value, get insertion point from TextPattern, splice, set new value.
                if let Ok(value_pattern) = unsafe {
                    uia_element.GetCurrentPatternAs::<IUIAutomationValuePattern>(UIA_ValuePatternId)
                } {
                    let current = unsafe { value_pattern.CurrentValue() }
                        .map(|s| s.to_string())
                        .unwrap_or_default();

                    // Try to get cursor position from TextPattern
                    let insert_pos = if let Ok(text_pattern) = unsafe {
                        uia_element
                            .GetCurrentPatternAs::<IUIAutomationTextPattern>(UIA_TextPatternId)
                    } {
                        // Get the selection/caret range — its start offset is the cursor
                        unsafe { text_pattern.GetSelection() }
                            .ok()
                            .and_then(|arr| unsafe { arr.GetElement(0) }.ok())
                            .map(|_| current.len()) // Fallback: append at end
                            .unwrap_or(current.len())
                    } else {
                        current.len() // No TextPattern — append at end
                    };

                    let mut new_value = current;
                    new_value.insert_str(insert_pos.min(new_value.len()), &text);
                    let bstr: windows::core::BSTR = new_value.into();
                    unsafe { value_pattern.SetValue(&bstr) }
                        .map_err(|_| Error::TextValueNotSupported)?;
                    return Ok(());
                }
                Err(Error::TextValueNotSupported)
            }
        }
    }

    fn subscribe(&self, element: &ElementData) -> Result<Subscription> {
        let pid = element.pid.ok_or(Error::Platform {
            code: -1,
            message: "Element has no PID for subscribe".to_string(),
        })?;
        let app_name = element.name.clone().unwrap_or_default();
        self.subscribe_impl(app_name, pid, move |p| {
            let (el, _name) = p.find_app_by_pid(pid)?;
            Ok(el)
        })
    }
}

// ── Helper Functions ─────────────────────────────────────────────────────────

/// Get the value of an element using UIA patterns.
fn get_value(element: &IUIAutomationElement, role: Role) -> Option<String> {
    // For checkboxes/radios, value is handled by state — skip
    if matches!(role, Role::CheckBox | Role::RadioButton) {
        return None;
    }

    // Try RangeValuePattern first (sliders, progress bars, spinners)
    if let Ok(pattern) = unsafe {
        element.GetCurrentPatternAs::<IUIAutomationRangeValuePattern>(UIA_RangeValuePatternId)
    } {
        if let Ok(v) = unsafe { pattern.CurrentValue() } {
            return Some(v.to_string());
        }
    }

    // Try ValuePattern (text fields, combo boxes)
    if let Ok(pattern) =
        unsafe { element.GetCurrentPatternAs::<IUIAutomationValuePattern>(UIA_ValuePatternId) }
    {
        if let Ok(v) = unsafe { pattern.CurrentValue() } {
            let s = v.to_string();
            if !s.is_empty() {
                return Some(s);
            }
        }
    }

    None
}

/// Determine available actions from UIA patterns.
fn get_actions(element: &IUIAutomationElement, role: Role) -> Vec<Action> {
    let mut actions: Vec<Action> = Vec::new();

    if unsafe { element.GetCurrentPatternAs::<IUIAutomationInvokePattern>(UIA_InvokePatternId) }
        .is_ok()
    {
        actions.push(Action::Press);
    }

    if unsafe { element.GetCurrentPatternAs::<IUIAutomationTogglePattern>(UIA_TogglePatternId) }
        .is_ok()
    {
        if !actions.contains(&Action::Press) {
            actions.push(Action::Press);
        }
    }

    if unsafe {
        element
            .GetCurrentPatternAs::<IUIAutomationExpandCollapsePattern>(UIA_ExpandCollapsePatternId)
    }
    .is_ok()
    {
        actions.push(Action::Expand);
        actions.push(Action::Collapse);
    }

    if unsafe { element.GetCurrentPatternAs::<IUIAutomationValuePattern>(UIA_ValuePatternId) }
        .is_ok()
    {
        if !actions.contains(&Action::SetValue) {
            actions.push(Action::SetValue);
        }
    }

    if unsafe {
        element.GetCurrentPatternAs::<IUIAutomationRangeValuePattern>(UIA_RangeValuePatternId)
    }
    .is_ok()
    {
        if !actions.contains(&Action::SetValue) {
            actions.push(Action::SetValue);
        }
        actions.push(Action::Increment);
        actions.push(Action::Decrement);
    }

    if unsafe {
        element.GetCurrentPatternAs::<IUIAutomationSelectionItemPattern>(UIA_SelectionItemPatternId)
    }
    .is_ok()
    {
        actions.push(Action::Select);
    }

    // Focus: most elements can be focused
    if unsafe { element.CurrentIsKeyboardFocusable() }
        .unwrap_or(BOOL(0))
        .as_bool()
    {
        actions.push(Action::Focus);
    }

    // For text fields and sliders, ensure SetValue is present
    if matches!(role, Role::TextField | Role::TextArea | Role::Slider)
        && !actions.contains(&Action::SetValue)
    {
        actions.push(Action::SetValue);
    }

    actions
}

/// Parse UIA element properties into xa11y StateSet.
#[allow(non_upper_case_globals)]
fn parse_states(element: &IUIAutomationElement, role: Role) -> StateSet {
    let enabled = unsafe { element.CurrentIsEnabled() }
        .unwrap_or(BOOL(1))
        .as_bool();
    let offscreen = unsafe { element.CurrentIsOffscreen() }
        .unwrap_or(BOOL(0))
        .as_bool();
    let visible = !offscreen;
    let focused = unsafe { element.CurrentHasKeyboardFocus() }
        .unwrap_or(BOOL(0))
        .as_bool();

    // Checked: from TogglePattern
    let checked = match role {
        Role::CheckBox | Role::RadioButton => {
            if let Ok(pattern) = unsafe {
                element.GetCurrentPatternAs::<IUIAutomationTogglePattern>(UIA_TogglePatternId)
            } {
                match unsafe { pattern.CurrentToggleState() } {
                    Ok(ToggleState_On) => Some(Toggled::On),
                    Ok(ToggleState_Off) => Some(Toggled::Off),
                    Ok(ToggleState_Indeterminate) => Some(Toggled::Mixed),
                    _ => Some(Toggled::Off),
                }
            } else {
                // For radio buttons, check SelectionItemPattern
                if let Ok(pattern) = unsafe {
                    element.GetCurrentPatternAs::<IUIAutomationSelectionItemPattern>(
                        UIA_SelectionItemPatternId,
                    )
                } {
                    if unsafe { pattern.CurrentIsSelected() }
                        .unwrap_or(BOOL(0))
                        .as_bool()
                    {
                        Some(Toggled::On)
                    } else {
                        Some(Toggled::Off)
                    }
                } else {
                    Some(Toggled::Off)
                }
            }
        }
        _ => None,
    };

    // Expanded: from ExpandCollapsePattern
    let expanded = if let Ok(pattern) = unsafe {
        element
            .GetCurrentPatternAs::<IUIAutomationExpandCollapsePattern>(UIA_ExpandCollapsePatternId)
    } {
        match unsafe { pattern.CurrentExpandCollapseState() } {
            Ok(ExpandCollapseState_Expanded) => Some(true),
            Ok(ExpandCollapseState_Collapsed) => Some(false),
            _ => None,
        }
    } else {
        None
    };

    // Selected: from SelectionItemPattern
    let selected = if let Ok(pattern) = unsafe {
        element.GetCurrentPatternAs::<IUIAutomationSelectionItemPattern>(UIA_SelectionItemPatternId)
    } {
        unsafe { pattern.CurrentIsSelected() }
            .unwrap_or(BOOL(0))
            .as_bool()
    } else {
        false
    };

    let editable = match role {
        Role::TextField | Role::TextArea => {
            if let Ok(pattern) = unsafe {
                element.GetCurrentPatternAs::<IUIAutomationValuePattern>(UIA_ValuePatternId)
            } {
                unsafe { pattern.CurrentIsReadOnly() }.unwrap_or(BOOL(1)) == BOOL(0)
            } else {
                true
            }
        }
        _ => false,
    };

    let focusable = unsafe { element.CurrentIsKeyboardFocusable() }.unwrap_or(FALSE) == TRUE;

    StateSet {
        enabled,
        visible,
        focused,
        focusable,
        modal: false,
        checked,
        selected,
        expanded,
        editable,
        required: false,
        busy: false,
    }
}

/// Map UIA ControlTypeId to xa11y Role.
#[allow(non_upper_case_globals)]
fn map_uia_control_type(control_type: UIA_CONTROLTYPE_ID) -> Role {
    match control_type {
        UIA_ButtonControlTypeId => Role::Button,
        UIA_CheckBoxControlTypeId => Role::CheckBox,
        UIA_RadioButtonControlTypeId => Role::RadioButton,
        UIA_EditControlTypeId => Role::TextField,
        UIA_TextControlTypeId => Role::StaticText,
        UIA_ComboBoxControlTypeId => Role::ComboBox,
        UIA_ListControlTypeId => Role::List,
        UIA_ListItemControlTypeId => Role::ListItem,
        UIA_MenuControlTypeId => Role::Menu,
        UIA_MenuItemControlTypeId => Role::MenuItem,
        UIA_MenuBarControlTypeId => Role::MenuBar,
        UIA_TabControlTypeId => Role::TabGroup,
        UIA_TabItemControlTypeId => Role::Tab,
        UIA_TableControlTypeId => Role::Table,
        UIA_DataGridControlTypeId => Role::Table,
        UIA_DataItemControlTypeId => Role::TableRow,
        UIA_ToolBarControlTypeId => Role::Toolbar,
        UIA_ScrollBarControlTypeId => Role::ScrollBar,
        UIA_SliderControlTypeId => Role::Slider,
        UIA_ImageControlTypeId => Role::Image,
        UIA_HyperlinkControlTypeId => Role::Link,
        UIA_GroupControlTypeId => Role::Group,
        UIA_WindowControlTypeId => Role::Window,
        UIA_PaneControlTypeId => Role::Group,
        UIA_ProgressBarControlTypeId => Role::ProgressBar,
        UIA_TreeItemControlTypeId => Role::TreeItem,
        UIA_TreeControlTypeId => Role::List,
        UIA_DocumentControlTypeId => Role::WebArea,
        UIA_HeaderControlTypeId => Role::Group,
        UIA_HeaderItemControlTypeId => Role::TableCell,
        UIA_SeparatorControlTypeId => Role::Separator,
        UIA_SpinnerControlTypeId => Role::SpinButton,
        UIA_SplitButtonControlTypeId => Role::Button,
        UIA_StatusBarControlTypeId => Role::Status,
        UIA_ThumbControlTypeId => Role::Unknown,
        UIA_TitleBarControlTypeId => Role::Group,
        UIA_ToolTipControlTypeId => Role::Tooltip,
        UIA_CalendarControlTypeId => Role::Group,
        UIA_CustomControlTypeId => Role::Unknown,
        _ => Role::Unknown,
    }
}

// ── Event subscription (polling-based) ───────────────────────────────────────

impl WindowsProvider {
    /// Spawn a polling thread that detects focus and structure changes.
    fn subscribe_impl<F>(&self, app_name: String, app_pid: u32, root_fn: F) -> Result<Subscription>
    where
        F: Fn(&WindowsProvider) -> Result<IUIAutomationElement> + Send + 'static,
    {
        let (tx, rx) = std::sync::mpsc::channel();
        let poll_provider = WindowsProvider::new()?;
        let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
        let stop_clone = stop.clone();

        let handle = std::thread::spawn(move || {
            let mut prev_focused: Option<String> = None;
            let mut prev_element_count: usize = 0;

            while !stop_clone.load(std::sync::atomic::Ordering::Relaxed) {
                std::thread::sleep(Duration::from_millis(100));

                let root_uia = match root_fn(&poll_provider) {
                    Ok(r) => r,
                    Err(_) => continue,
                };

                // Collect all elements by walking children lazily
                let mut all_elements = Vec::new();
                let root_data = poll_provider.build_element_data(&root_uia, Some(app_pid));
                all_elements.push(root_data);

                // BFS to collect subtree
                let mut queue = vec![root_uia];
                while let Some(uia) = queue.pop() {
                    let children = poll_provider.uia_children(&uia);
                    for child in &children {
                        let data = poll_provider.build_element_data(child, Some(app_pid));
                        all_elements.push(data);
                    }
                    queue.extend(children);
                }

                // Detect focus changes
                let focused_name = all_elements
                    .iter()
                    .find(|n| n.states.focused)
                    .and_then(|n| n.name.clone());
                if focused_name != prev_focused {
                    if prev_focused.is_some() {
                        let _ = tx.send(Event {
                            event_type: EventType::FocusChanged,
                            app_name: app_name.clone(),
                            app_pid,
                            target: all_elements.iter().find(|n| n.states.focused).cloned(),
                            state_flag: None,
                            state_value: None,
                            text_change: None,
                            timestamp: std::time::Instant::now(),
                        });
                    }
                    prev_focused = focused_name;
                }

                // Detect structure changes
                let element_count = all_elements.len();
                if element_count != prev_element_count && prev_element_count > 0 {
                    let _ = tx.send(Event {
                        event_type: EventType::StructureChanged,
                        app_name: app_name.clone(),
                        app_pid,
                        target: None,
                        state_flag: None,
                        state_value: None,
                        text_change: None,
                        timestamp: std::time::Instant::now(),
                    });
                }
                prev_element_count = element_count;
            }
        });

        let cancel = CancelHandle::new(move || {
            stop.store(true, std::sync::atomic::Ordering::Relaxed);
            let _ = handle.join();
        });

        Ok(Subscription::new(EventReceiver::new(rx), cancel))
    }
}

#[cfg(test)]
#[allow(non_upper_case_globals)]
mod tests {
    use super::*;

    #[test]
    fn role_mapping_covers_common_types() {
        assert_eq!(map_uia_control_type(UIA_ButtonControlTypeId), Role::Button);
        assert_eq!(
            map_uia_control_type(UIA_CheckBoxControlTypeId),
            Role::CheckBox
        );
        assert_eq!(map_uia_control_type(UIA_EditControlTypeId), Role::TextField);
        assert_eq!(
            map_uia_control_type(UIA_TextControlTypeId),
            Role::StaticText
        );
        assert_eq!(
            map_uia_control_type(UIA_ComboBoxControlTypeId),
            Role::ComboBox
        );
        assert_eq!(map_uia_control_type(UIA_ListControlTypeId), Role::List);
        assert_eq!(
            map_uia_control_type(UIA_ListItemControlTypeId),
            Role::ListItem
        );
        assert_eq!(map_uia_control_type(UIA_MenuControlTypeId), Role::Menu);
        assert_eq!(
            map_uia_control_type(UIA_MenuItemControlTypeId),
            Role::MenuItem
        );
        assert_eq!(
            map_uia_control_type(UIA_MenuBarControlTypeId),
            Role::MenuBar
        );
        assert_eq!(map_uia_control_type(UIA_TabControlTypeId), Role::TabGroup);
        assert_eq!(map_uia_control_type(UIA_TabItemControlTypeId), Role::Tab);
        assert_eq!(map_uia_control_type(UIA_SliderControlTypeId), Role::Slider);
        assert_eq!(map_uia_control_type(UIA_WindowControlTypeId), Role::Window);
        assert_eq!(
            map_uia_control_type(UIA_ProgressBarControlTypeId),
            Role::ProgressBar
        );
        assert_eq!(
            map_uia_control_type(UIA_TreeItemControlTypeId),
            Role::TreeItem
        );
        assert_eq!(
            map_uia_control_type(UIA_SeparatorControlTypeId),
            Role::Separator
        );
        assert_eq!(map_uia_control_type(UIA_ImageControlTypeId), Role::Image);
        assert_eq!(map_uia_control_type(UIA_HyperlinkControlTypeId), Role::Link);
        assert_eq!(map_uia_control_type(UIA_GroupControlTypeId), Role::Group);
        assert_eq!(
            map_uia_control_type(UIA_CONTROLTYPE_ID(99999)),
            Role::Unknown
        );
    }

    #[test]
    fn provider_new_succeeds() {
        let provider = WindowsProvider::new();
        assert!(provider.is_ok());
    }
}
