//! Window rendering and frame display

use super::input;
use super::{InputEvent, ViewerEvent};
use crate::proto;
use anyhow::Result;
use std::num::NonZeroU32;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::{debug, error, info, warn};
use winit::{
    application::ApplicationHandler,
    dpi::LogicalSize,
    event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent},
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
    keyboard::{KeyCode, PhysicalKey},
    window::{Window, WindowId},
};

/// Frame data received from server
#[derive(Debug, Clone)]
pub struct FrameData {
    pub width: u32,
    pub height: u32,
    pub data: Vec<u8>,
    pub compressed: bool,
    // Carried through from the wire frame; the renderer does not branch on it yet.
    #[allow(dead_code)]
    pub is_keyframe: bool,
}

/// Viewer-local tracker of which modifier keys are currently held down on the remote.
///
/// Mirrors what the viewer has forwarded so that on focus loss it can emit explicit
/// key-ups for anything still pressed, preventing a stuck Ctrl/Alt/Shift/Win.
#[derive(Default)]
struct ViewerModifierState {
    ctrl: bool,
    alt: bool,
    shift: bool,
    meta: bool,
}

impl ViewerModifierState {
    /// Record a modifier transition for `vk_code`.
    fn update(&mut self, vk_code: u32, down: bool) {
        match vk_code {
            0x11 | 0xA2 | 0xA3 => self.ctrl = down, // Ctrl / LCtrl / RCtrl
            0x12 | 0xA4 | 0xA5 => self.alt = down,  // Alt / LAlt / RAlt
            0x10 | 0xA0 | 0xA1 => self.shift = down, // Shift / LShift / RShift
            0x5B | 0x5C => self.meta = down,        // LWin / RWin
            _ => {}
        }
    }

    /// Return the canonical VK of every held modifier, then clear all state.
    fn drain_held(&mut self) -> Vec<u16> {
        let mut held = Vec::new();
        if self.ctrl {
            held.push(0x11u16);
        }
        if self.alt {
            held.push(0x12);
        }
        if self.shift {
            held.push(0x10);
        }
        if self.meta {
            held.push(0x5B);
        }
        *self = ViewerModifierState::default();
        held
    }
}

struct ViewerApp {
    window: Option<Arc<Window>>,
    surface: Option<softbuffer::Surface<Arc<Window>, Arc<Window>>>,
    frame_buffer: Vec<u32>,
    frame_width: u32,
    frame_height: u32,
    viewer_rx: mpsc::Receiver<ViewerEvent>,
    input_tx: mpsc::Sender<InputEvent>,
    mouse_x: i32,
    mouse_y: i32,
    modifiers: ViewerModifierState,
    #[cfg(windows)]
    keyboard_hook: Option<input::KeyboardHook>,
}

impl ViewerApp {
    fn new(viewer_rx: mpsc::Receiver<ViewerEvent>, input_tx: mpsc::Sender<InputEvent>) -> Self {
        Self {
            window: None,
            surface: None,
            frame_buffer: Vec::new(),
            frame_width: 0,
            frame_height: 0,
            viewer_rx,
            input_tx,
            mouse_x: 0,
            mouse_y: 0,
            modifiers: ViewerModifierState::default(),
            #[cfg(windows)]
            keyboard_hook: None,
        }
    }

    fn process_frame(&mut self, frame: FrameData) {
        let data = if frame.compressed {
            // Decompress zstd
            match zstd::decode_all(frame.data.as_slice()) {
                Ok(decompressed) => decompressed,
                Err(e) => {
                    error!("Failed to decompress frame: {}", e);
                    return;
                }
            }
        } else {
            frame.data
        };

        // Convert BGRA to ARGB (softbuffer expects 0RGB format on little-endian)
        let pixel_count = (frame.width * frame.height) as usize;
        if data.len() < pixel_count * 4 {
            error!("Frame data too small: {} < {}", data.len(), pixel_count * 4);
            return;
        }

        // Resize frame buffer if needed
        if self.frame_width != frame.width || self.frame_height != frame.height {
            self.frame_width = frame.width;
            self.frame_height = frame.height;
            self.frame_buffer.resize(pixel_count, 0);

            // Resize window to match frame
            if let Some(window) = &self.window {
                let _ = window.request_inner_size(LogicalSize::new(frame.width, frame.height));
            }
        }

        // Convert BGRA to 0RGB (ignore alpha, swap B and R)
        for i in 0..pixel_count {
            let offset = i * 4;
            let b = data[offset] as u32;
            let g = data[offset + 1] as u32;
            let r = data[offset + 2] as u32;
            // 0RGB format: 0x00RRGGBB
            self.frame_buffer[i] = (r << 16) | (g << 8) | b;
        }

        // Request redraw
        if let Some(window) = &self.window {
            window.request_redraw();
        }
    }

    fn render(&mut self) {
        let Some(surface) = &mut self.surface else {
            return;
        };
        let Some(window) = &self.window else { return };

        if self.frame_buffer.is_empty() || self.frame_width == 0 || self.frame_height == 0 {
            return;
        }

        let size = window.inner_size();
        if size.width == 0 || size.height == 0 {
            return;
        }

        // Resize surface if needed
        let width = NonZeroU32::new(size.width).unwrap();
        let height = NonZeroU32::new(size.height).unwrap();

        if let Err(e) = surface.resize(width, height) {
            error!("Failed to resize surface: {}", e);
            return;
        }

        let mut buffer = match surface.buffer_mut() {
            Ok(b) => b,
            Err(e) => {
                error!("Failed to get surface buffer: {}", e);
                return;
            }
        };

        // Simple nearest-neighbor scaling
        let scale_x = self.frame_width as f32 / size.width as f32;
        let scale_y = self.frame_height as f32 / size.height as f32;

        for y in 0..size.height {
            for x in 0..size.width {
                let src_x = ((x as f32 * scale_x) as u32).min(self.frame_width - 1);
                let src_y = ((y as f32 * scale_y) as u32).min(self.frame_height - 1);
                let src_idx = (src_y * self.frame_width + src_x) as usize;
                let dst_idx = (y * size.width + x) as usize;

                if src_idx < self.frame_buffer.len() && dst_idx < buffer.len() {
                    buffer[dst_idx] = self.frame_buffer[src_idx];
                }
            }
        }

        if let Err(e) = buffer.present() {
            error!("Failed to present buffer: {}", e);
        }
    }

    fn send_mouse_event(&self, event_type: proto::MouseEventType, x: i32, y: i32) {
        let event = proto::MouseEvent {
            x,
            y,
            buttons: Some(proto::MouseButtons::default()),
            wheel_delta_x: 0,
            wheel_delta_y: 0,
            event_type: event_type as i32,
        };

        let _ = self.input_tx.try_send(InputEvent::Mouse(event));
    }

    fn send_mouse_button(&self, button: MouseButton, state: ElementState) {
        let event_type = match state {
            ElementState::Pressed => proto::MouseEventType::MouseDown,
            ElementState::Released => proto::MouseEventType::MouseUp,
        };

        let mut buttons = proto::MouseButtons::default();
        match button {
            MouseButton::Left => buttons.left = true,
            MouseButton::Right => buttons.right = true,
            MouseButton::Middle => buttons.middle = true,
            _ => {}
        }

        let event = proto::MouseEvent {
            x: self.mouse_x,
            y: self.mouse_y,
            buttons: Some(buttons),
            wheel_delta_x: 0,
            wheel_delta_y: 0,
            event_type: event_type as i32,
        };

        let _ = self.input_tx.try_send(InputEvent::Mouse(event));
    }

    fn send_mouse_wheel(&self, delta_x: i32, delta_y: i32) {
        let event = proto::MouseEvent {
            x: self.mouse_x,
            y: self.mouse_y,
            buttons: Some(proto::MouseButtons::default()),
            wheel_delta_x: delta_x,
            wheel_delta_y: delta_y,
            event_type: proto::MouseEventType::MouseWheel as i32,
        };

        let _ = self.input_tx.try_send(InputEvent::Mouse(event));
    }

    fn send_key_event(&mut self, key: PhysicalKey, state: ElementState) {
        let vk_code = match key {
            PhysicalKey::Code(code) => keycode_to_vk(code),
            _ => return,
        };
        if vk_code == 0 {
            return;
        }

        let down = state == ElementState::Pressed;

        // Track modifier state locally so focus loss can release anything still held.
        self.modifiers.update(vk_code, down);

        // The winit path has no hardware scan code; the agent derives one from the VK.
        // The extended-key flag is derived from the VK so extended keys (arrows, etc.)
        // still inject correctly without a captured LLKHF_EXTENDED bit.
        let event = proto::KeyEvent {
            down,
            key_type: proto::KeyEventType::KeyVk as i32,
            vk_code,
            scan_code: 0,
            unicode: String::new(),
            is_extended: crate::input::vk_is_extended(vk_code as u16),
            modifiers: Some(proto::Modifiers::default()),
        };

        let _ = self.input_tx.try_send(InputEvent::Key(event));
    }

    /// Release every modifier this viewer currently believes is held on the remote.
    ///
    /// Invoked on focus loss and at window close so that a Ctrl/Alt/Shift/Win whose
    /// key-up the viewer never saw (because focus left mid-press) is explicitly released
    /// on the remote, preventing a "stuck modifier".
    fn release_held_modifiers(&mut self) {
        for vk in self.modifiers.drain_held() {
            let event = proto::KeyEvent {
                down: false,
                key_type: proto::KeyEventType::KeyVk as i32,
                vk_code: vk as u32,
                scan_code: 0,
                unicode: String::new(),
                is_extended: crate::input::vk_is_extended(vk),
                modifiers: Some(proto::Modifiers::default()),
            };
            let _ = self.input_tx.try_send(InputEvent::Key(event));
        }
    }

    fn screen_to_frame_coords(&self, x: f64, y: f64) -> (i32, i32) {
        let Some(window) = &self.window else {
            return (x as i32, y as i32);
        };

        let size = window.inner_size();
        if size.width == 0 || size.height == 0 || self.frame_width == 0 || self.frame_height == 0 {
            return (x as i32, y as i32);
        }

        // Scale from window coordinates to frame coordinates
        let scale_x = self.frame_width as f64 / size.width as f64;
        let scale_y = self.frame_height as f64 / size.height as f64;

        let frame_x = (x * scale_x) as i32;
        let frame_y = (y * scale_y) as i32;

        (frame_x, frame_y)
    }
}

impl ApplicationHandler for ViewerApp {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.window.is_some() {
            return;
        }

        let window_attrs = Window::default_attributes()
            .with_title("GuruConnect Viewer")
            .with_inner_size(LogicalSize::new(1280, 720));

        let window = Arc::new(event_loop.create_window(window_attrs).unwrap());

        // Create software rendering surface
        let context = softbuffer::Context::new(window.clone()).unwrap();
        let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();

        self.window = Some(window.clone());
        self.surface = Some(surface);

        // Install keyboard hook
        #[cfg(windows)]
        {
            let input_tx = self.input_tx.clone();
            match input::KeyboardHook::new(input_tx) {
                Ok(hook) => {
                    info!("Keyboard hook installed");
                    self.keyboard_hook = Some(hook);
                }
                Err(e) => {
                    error!("Failed to install keyboard hook: {}", e);
                }
            }
        }

        info!("Window created");
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
        // Check for incoming viewer events (non-blocking)
        while let Ok(viewer_event) = self.viewer_rx.try_recv() {
            match viewer_event {
                ViewerEvent::Frame(frame) => {
                    self.process_frame(frame);
                }
                ViewerEvent::Connected => {
                    info!("Connected to remote session");
                }
                ViewerEvent::Disconnected(reason) => {
                    warn!("Disconnected: {}", reason);
                    event_loop.exit();
                }
                ViewerEvent::CursorPosition(_x, _y, _visible) => {
                    // Could update cursor display here
                }
                ViewerEvent::CursorShape(_shape) => {
                    // Could update cursor shape here
                }
            }
        }

        match event {
            WindowEvent::CloseRequested => {
                info!("Window close requested");
                // Release any modifiers still held so the remote isn't left latched.
                self.release_held_modifiers();
                event_loop.exit();
            }
            WindowEvent::RedrawRequested => {
                self.render();
            }
            WindowEvent::Resized(size) => {
                debug!("Window resized to {}x{}", size.width, size.height);
                if let Some(window) = &self.window {
                    window.request_redraw();
                }
            }
            WindowEvent::CursorMoved { position, .. } => {
                let (x, y) = self.screen_to_frame_coords(position.x, position.y);
                self.mouse_x = x;
                self.mouse_y = y;
                self.send_mouse_event(proto::MouseEventType::MouseMove, x, y);
            }
            WindowEvent::MouseInput { state, button, .. } => {
                self.send_mouse_button(button, state);
            }
            WindowEvent::MouseWheel { delta, .. } => {
                let (dx, dy) = match delta {
                    MouseScrollDelta::LineDelta(x, y) => (x as i32 * 120, y as i32 * 120),
                    MouseScrollDelta::PixelDelta(pos) => (pos.x as i32, pos.y as i32),
                };
                self.send_mouse_wheel(dx, dy);
            }
            // Focus changes drive the low-level hook's focus gate. The hook is GLOBAL
            // (fires for all desktop input), so it must only divert system keys while the
            // viewer is focused; we flip `set_viewer_focused` here. On blur we also release
            // any held modifiers so they don't stay latched on the remote — winit's hook
            // pump only runs while we have focus, so this is the safety net for a modifier
            // pressed-but-not-released across the blur.
            WindowEvent::Focused(focused) => {
                input::set_viewer_focused(focused);
                if focused {
                    debug!("Viewer gained focus; system-key forwarding active");
                } else {
                    debug!("Viewer lost focus; releasing held modifiers on remote");
                    self.release_held_modifiers();
                }
            }
            // Note: This handles keys that aren't captured by the low-level hook.
            // The hook handles the Windows key and other diverted system combinations.
            WindowEvent::KeyboardInput { event, .. } if !event.repeat => {
                // Host key: Pause/Break toggles "send system keys to remote". It is
                // intercepted locally (not forwarded) so the technician can flip the
                // behavior without affecting the remote. Only act on key-down.
                if matches!(event.physical_key, PhysicalKey::Code(KeyCode::Pause))
                    && event.state == ElementState::Pressed
                {
                    let enabled = input::toggle_send_system_keys();
                    info!(
                        "Send-system-keys toggled {} (Pause/Break host key)",
                        if enabled { "ON" } else { "OFF" }
                    );
                    return;
                }
                self.send_key_event(event.physical_key, event.state);
            }
            _ => {}
        }
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        // Keep checking for events
        event_loop.set_control_flow(ControlFlow::Poll);

        // Process Windows messages for keyboard hook
        #[cfg(windows)]
        input::pump_messages();

        // Request redraw periodically to check for new frames
        if let Some(window) = &self.window {
            window.request_redraw();
        }
    }
}

/// Run the viewer window
pub async fn run_window(
    viewer_rx: mpsc::Receiver<ViewerEvent>,
    input_tx: mpsc::Sender<InputEvent>,
) -> Result<()> {
    let event_loop = EventLoop::new()?;
    let mut app = ViewerApp::new(viewer_rx, input_tx);

    event_loop.run_app(&mut app)?;

    Ok(())
}

/// Convert winit KeyCode to Windows virtual key code
fn keycode_to_vk(code: KeyCode) -> u32 {
    match code {
        // Letters
        KeyCode::KeyA => 0x41,
        KeyCode::KeyB => 0x42,
        KeyCode::KeyC => 0x43,
        KeyCode::KeyD => 0x44,
        KeyCode::KeyE => 0x45,
        KeyCode::KeyF => 0x46,
        KeyCode::KeyG => 0x47,
        KeyCode::KeyH => 0x48,
        KeyCode::KeyI => 0x49,
        KeyCode::KeyJ => 0x4A,
        KeyCode::KeyK => 0x4B,
        KeyCode::KeyL => 0x4C,
        KeyCode::KeyM => 0x4D,
        KeyCode::KeyN => 0x4E,
        KeyCode::KeyO => 0x4F,
        KeyCode::KeyP => 0x50,
        KeyCode::KeyQ => 0x51,
        KeyCode::KeyR => 0x52,
        KeyCode::KeyS => 0x53,
        KeyCode::KeyT => 0x54,
        KeyCode::KeyU => 0x55,
        KeyCode::KeyV => 0x56,
        KeyCode::KeyW => 0x57,
        KeyCode::KeyX => 0x58,
        KeyCode::KeyY => 0x59,
        KeyCode::KeyZ => 0x5A,

        // Numbers
        KeyCode::Digit0 => 0x30,
        KeyCode::Digit1 => 0x31,
        KeyCode::Digit2 => 0x32,
        KeyCode::Digit3 => 0x33,
        KeyCode::Digit4 => 0x34,
        KeyCode::Digit5 => 0x35,
        KeyCode::Digit6 => 0x36,
        KeyCode::Digit7 => 0x37,
        KeyCode::Digit8 => 0x38,
        KeyCode::Digit9 => 0x39,

        // Function keys
        KeyCode::F1 => 0x70,
        KeyCode::F2 => 0x71,
        KeyCode::F3 => 0x72,
        KeyCode::F4 => 0x73,
        KeyCode::F5 => 0x74,
        KeyCode::F6 => 0x75,
        KeyCode::F7 => 0x76,
        KeyCode::F8 => 0x77,
        KeyCode::F9 => 0x78,
        KeyCode::F10 => 0x79,
        KeyCode::F11 => 0x7A,
        KeyCode::F12 => 0x7B,

        // Special keys
        KeyCode::Escape => 0x1B,
        KeyCode::Tab => 0x09,
        KeyCode::CapsLock => 0x14,
        KeyCode::ShiftLeft => 0x10,
        KeyCode::ShiftRight => 0x10,
        KeyCode::ControlLeft => 0x11,
        KeyCode::ControlRight => 0x11,
        KeyCode::AltLeft => 0x12,
        KeyCode::AltRight => 0x12,
        KeyCode::Space => 0x20,
        KeyCode::Enter => 0x0D,
        KeyCode::Backspace => 0x08,
        KeyCode::Delete => 0x2E,
        KeyCode::Insert => 0x2D,
        KeyCode::Home => 0x24,
        KeyCode::End => 0x23,
        KeyCode::PageUp => 0x21,
        KeyCode::PageDown => 0x22,

        // Arrow keys
        KeyCode::ArrowUp => 0x26,
        KeyCode::ArrowDown => 0x28,
        KeyCode::ArrowLeft => 0x25,
        KeyCode::ArrowRight => 0x27,

        // Numpad
        KeyCode::NumLock => 0x90,
        KeyCode::Numpad0 => 0x60,
        KeyCode::Numpad1 => 0x61,
        KeyCode::Numpad2 => 0x62,
        KeyCode::Numpad3 => 0x63,
        KeyCode::Numpad4 => 0x64,
        KeyCode::Numpad5 => 0x65,
        KeyCode::Numpad6 => 0x66,
        KeyCode::Numpad7 => 0x67,
        KeyCode::Numpad8 => 0x68,
        KeyCode::Numpad9 => 0x69,
        KeyCode::NumpadAdd => 0x6B,
        KeyCode::NumpadSubtract => 0x6D,
        KeyCode::NumpadMultiply => 0x6A,
        KeyCode::NumpadDivide => 0x6F,
        KeyCode::NumpadDecimal => 0x6E,
        KeyCode::NumpadEnter => 0x0D,

        // Punctuation
        KeyCode::Semicolon => 0xBA,
        KeyCode::Equal => 0xBB,
        KeyCode::Comma => 0xBC,
        KeyCode::Minus => 0xBD,
        KeyCode::Period => 0xBE,
        KeyCode::Slash => 0xBF,
        KeyCode::Backquote => 0xC0,
        KeyCode::BracketLeft => 0xDB,
        KeyCode::Backslash => 0xDC,
        KeyCode::BracketRight => 0xDD,
        KeyCode::Quote => 0xDE,

        // Other
        KeyCode::PrintScreen => 0x2C,
        KeyCode::ScrollLock => 0x91,
        KeyCode::Pause => 0x13,

        _ => 0,
    }
}
