//! Mouse input simulation using Windows SendInput API

use super::MouseButton;
use anyhow::Result;

#[cfg(windows)]
use windows::Win32::UI::Input::KeyboardAndMouse::{
    SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_HWHEEL,
    MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP,
    MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_VIRTUALDESK,
    MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT,
};

// X button constants (not exported in windows crate 0.58+)
#[cfg(windows)]
const XBUTTON1: u32 = 0x0001;
#[cfg(windows)]
const XBUTTON2: u32 = 0x0002;

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
    GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN,
};

/// Mouse input controller
pub struct MouseController {
    /// Virtual screen dimensions for coordinate translation
    #[cfg(windows)]
    virtual_screen: VirtualScreen,
}

#[cfg(windows)]
struct VirtualScreen {
    x: i32,
    y: i32,
    width: i32,
    height: i32,
}

impl MouseController {
    /// Create a new mouse controller
    pub fn new() -> Result<Self> {
        #[cfg(windows)]
        {
            let virtual_screen = unsafe {
                VirtualScreen {
                    x: GetSystemMetrics(SM_XVIRTUALSCREEN),
                    y: GetSystemMetrics(SM_YVIRTUALSCREEN),
                    width: GetSystemMetrics(SM_CXVIRTUALSCREEN),
                    height: GetSystemMetrics(SM_CYVIRTUALSCREEN),
                }
            };

            Ok(Self { virtual_screen })
        }

        #[cfg(not(windows))]
        {
            anyhow::bail!("Mouse input only supported on Windows")
        }
    }

    /// Move mouse to absolute screen coordinates
    #[cfg(windows)]
    pub fn move_to(&mut self, x: i32, y: i32) -> Result<()> {
        // Convert screen coordinates to normalized absolute coordinates (0-65535)
        let norm_x = ((x - self.virtual_screen.x) * 65535) / self.virtual_screen.width;
        let norm_y = ((y - self.virtual_screen.y) * 65535) / self.virtual_screen.height;

        let input = INPUT {
            r#type: INPUT_MOUSE,
            Anonymous: INPUT_0 {
                mi: MOUSEINPUT {
                    dx: norm_x,
                    dy: norm_y,
                    mouseData: 0,
                    dwFlags: MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        };

        self.send_input(&[input])
    }

    /// Press mouse button down
    #[cfg(windows)]
    pub fn button_down(&mut self, button: MouseButton) -> Result<()> {
        let (flags, data) = match button {
            MouseButton::Left => (MOUSEEVENTF_LEFTDOWN, 0),
            MouseButton::Right => (MOUSEEVENTF_RIGHTDOWN, 0),
            MouseButton::Middle => (MOUSEEVENTF_MIDDLEDOWN, 0),
            MouseButton::X1 => (MOUSEEVENTF_XDOWN, XBUTTON1),
            MouseButton::X2 => (MOUSEEVENTF_XDOWN, XBUTTON2),
        };

        let input = INPUT {
            r#type: INPUT_MOUSE,
            Anonymous: INPUT_0 {
                mi: MOUSEINPUT {
                    dx: 0,
                    dy: 0,
                    mouseData: data,
                    dwFlags: flags,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        };

        self.send_input(&[input])
    }

    /// Release mouse button
    #[cfg(windows)]
    pub fn button_up(&mut self, button: MouseButton) -> Result<()> {
        let (flags, data) = match button {
            MouseButton::Left => (MOUSEEVENTF_LEFTUP, 0),
            MouseButton::Right => (MOUSEEVENTF_RIGHTUP, 0),
            MouseButton::Middle => (MOUSEEVENTF_MIDDLEUP, 0),
            MouseButton::X1 => (MOUSEEVENTF_XUP, XBUTTON1),
            MouseButton::X2 => (MOUSEEVENTF_XUP, XBUTTON2),
        };

        let input = INPUT {
            r#type: INPUT_MOUSE,
            Anonymous: INPUT_0 {
                mi: MOUSEINPUT {
                    dx: 0,
                    dy: 0,
                    mouseData: data,
                    dwFlags: flags,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        };

        self.send_input(&[input])
    }

    /// Scroll mouse wheel
    #[cfg(windows)]
    pub fn scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> {
        let mut inputs = Vec::new();

        // Vertical scroll
        if delta_y != 0 {
            inputs.push(INPUT {
                r#type: INPUT_MOUSE,
                Anonymous: INPUT_0 {
                    mi: MOUSEINPUT {
                        dx: 0,
                        dy: 0,
                        mouseData: delta_y as u32,
                        dwFlags: MOUSEEVENTF_WHEEL,
                        time: 0,
                        dwExtraInfo: 0,
                    },
                },
            });
        }

        // Horizontal scroll
        if delta_x != 0 {
            inputs.push(INPUT {
                r#type: INPUT_MOUSE,
                Anonymous: INPUT_0 {
                    mi: MOUSEINPUT {
                        dx: 0,
                        dy: 0,
                        mouseData: delta_x as u32,
                        dwFlags: MOUSEEVENTF_HWHEEL,
                        time: 0,
                        dwExtraInfo: 0,
                    },
                },
            });
        }

        if !inputs.is_empty() {
            self.send_input(&inputs)?;
        }

        Ok(())
    }

    /// Send input events
    #[cfg(windows)]
    fn send_input(&self, inputs: &[INPUT]) -> Result<()> {
        let sent = unsafe { SendInput(inputs, std::mem::size_of::<INPUT>() as i32) };

        if sent as usize != inputs.len() {
            anyhow::bail!("SendInput failed: sent {} of {} inputs", sent, inputs.len());
        }

        Ok(())
    }

    #[cfg(not(windows))]
    pub fn move_to(&mut self, _x: i32, _y: i32) -> Result<()> {
        anyhow::bail!("Mouse input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn button_down(&mut self, _button: MouseButton) -> Result<()> {
        anyhow::bail!("Mouse input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn button_up(&mut self, _button: MouseButton) -> Result<()> {
        anyhow::bail!("Mouse input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn scroll(&mut self, _delta_x: i32, _delta_y: i32) -> Result<()> {
        anyhow::bail!("Mouse input only supported on Windows")
    }
}
