use bone_app::{InputEvent, KeyDown, NavKey, ScrollDelta, WindowPoint};
use bone_render::{ViewportExtent, ViewportPx};
use bone_ui::input::{KeyChar, KeyCode as UiKeyCode, ModifierMask, NamedKey, PointerButton};
use winit::{
    dpi::PhysicalSize,
    event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent},
    keyboard::{Key, KeyCode, ModifiersState, NamedKey as WinitNamed, PhysicalKey, SmolStr},
};

pub enum AppEvent {
    Input(InputEvent),
    Redraw,
    Close,
    Ignored,
}

impl AppEvent {
    pub fn from_winit(event: WindowEvent) -> Self {
        match event {
            WindowEvent::CloseRequested => Self::Close,
            WindowEvent::RedrawRequested => Self::Redraw,
            WindowEvent::Resized(size) => Self::Input(InputEvent::Resize(viewport_extent(size))),
            WindowEvent::Focused(focused) => Self::Input(InputEvent::Focus(focused)),
            WindowEvent::ModifiersChanged(mods) => {
                Self::Input(InputEvent::Modifiers(modifier_mask(mods.state())))
            }
            WindowEvent::CursorMoved { position, .. } => Self::Input(InputEvent::CursorMove(
                WindowPoint::new(position.x, position.y),
            )),
            WindowEvent::CursorLeft { .. } => Self::Input(InputEvent::CursorLeft),
            WindowEvent::CursorEntered { .. } => Self::Input(InputEvent::CursorEntered),
            WindowEvent::MouseInput { state, button, .. } => match pointer_button(button) {
                Some(button) => Self::Input(InputEvent::Pointer {
                    button,
                    pressed: state == ElementState::Pressed,
                }),
                None => Self::Ignored,
            },
            WindowEvent::MouseWheel { delta, .. } => {
                Self::Input(InputEvent::Wheel(scroll_delta(delta)))
            }
            WindowEvent::KeyboardInput {
                event:
                    KeyEvent {
                        state: ElementState::Pressed,
                        physical_key,
                        logical_key,
                        text,
                        repeat,
                        ..
                    },
                ..
            } => Self::Input(InputEvent::KeyDown(key_down(
                physical_key,
                &logical_key,
                text,
                repeat,
            ))),
            WindowEvent::KeyboardInput { .. }
            | WindowEvent::ActivationTokenDone { .. }
            | WindowEvent::Moved(_)
            | WindowEvent::Destroyed
            | WindowEvent::DroppedFile(_)
            | WindowEvent::HoveredFile(_)
            | WindowEvent::HoveredFileCancelled
            | WindowEvent::Ime(_)
            | WindowEvent::PinchGesture { .. }
            | WindowEvent::PanGesture { .. }
            | WindowEvent::DoubleTapGesture { .. }
            | WindowEvent::RotationGesture { .. }
            | WindowEvent::TouchpadPressure { .. }
            | WindowEvent::AxisMotion { .. }
            | WindowEvent::Touch(_)
            | WindowEvent::ScaleFactorChanged { .. }
            | WindowEvent::ThemeChanged(_)
            | WindowEvent::Occluded(_) => Self::Ignored,
        }
    }
}

pub fn viewport_extent(size: PhysicalSize<u32>) -> ViewportExtent {
    ViewportExtent::new(
        ViewportPx::new(size.width.max(1)),
        ViewportPx::new(size.height.max(1)),
    )
}

fn modifier_mask(state: ModifiersState) -> ModifierMask {
    let bits = [
        (state.control_key(), ModifierMask::CTRL),
        (state.shift_key(), ModifierMask::SHIFT),
        (state.alt_key(), ModifierMask::ALT),
        (state.super_key(), ModifierMask::META),
    ];
    bits.iter()
        .filter(|(active, _)| *active)
        .fold(ModifierMask::NONE, |acc, (_, m)| acc.union(*m))
}

const fn pointer_button(button: MouseButton) -> Option<PointerButton> {
    match button {
        MouseButton::Left => Some(PointerButton::Primary),
        MouseButton::Right => Some(PointerButton::Secondary),
        MouseButton::Middle => Some(PointerButton::Middle),
        MouseButton::Back | MouseButton::Forward | MouseButton::Other(_) => None,
    }
}

fn scroll_delta(delta: MouseScrollDelta) -> ScrollDelta {
    match delta {
        MouseScrollDelta::LineDelta(x, y) => ScrollDelta::Lines { x, y },
        MouseScrollDelta::PixelDelta(p) => ScrollDelta::Pixels { x: p.x, y: p.y },
    }
}

fn key_down(
    physical_key: PhysicalKey,
    logical_key: &Key,
    text: Option<SmolStr>,
    repeat: bool,
) -> KeyDown {
    let physical_code = match physical_key {
        PhysicalKey::Code(c) => Some(c),
        PhysicalKey::Unidentified(_) => None,
    };
    let logical_named = match logical_key {
        Key::Named(nk) => winit_named_to_ui(*nk),
        _ => None,
    };
    let named = logical_named.or_else(|| physical_code.and_then(keycode_to_named));
    let code = named.map(UiKeyCode::Named).or_else(|| {
        physical_code
            .and_then(keycode_to_char)
            .map(|c| UiKeyCode::Char(KeyChar::from_char(c)))
    });
    KeyDown {
        code,
        nav: physical_code.and_then(keycode_to_nav),
        text: text.map(|t| t.to_string()),
        repeat,
    }
}

const fn keycode_to_nav(code: KeyCode) -> Option<NavKey> {
    match code {
        KeyCode::ArrowLeft => Some(NavKey::Left),
        KeyCode::ArrowRight => Some(NavKey::Right),
        KeyCode::ArrowUp => Some(NavKey::Up),
        KeyCode::ArrowDown => Some(NavKey::Down),
        KeyCode::KeyZ => Some(NavKey::Zoom),
        KeyCode::Equal => Some(NavKey::ZoomIn),
        KeyCode::Minus => Some(NavKey::ZoomOut),
        _ => None,
    }
}

fn keycode_to_named(code: KeyCode) -> Option<NamedKey> {
    match code {
        KeyCode::Tab => Some(NamedKey::Tab),
        KeyCode::Enter | KeyCode::NumpadEnter => Some(NamedKey::Enter),
        KeyCode::Escape => Some(NamedKey::Escape),
        KeyCode::Backspace => Some(NamedKey::Backspace),
        KeyCode::Delete => Some(NamedKey::Delete),
        KeyCode::Space => Some(NamedKey::Space),
        KeyCode::ArrowUp => Some(NamedKey::ArrowUp),
        KeyCode::ArrowDown => Some(NamedKey::ArrowDown),
        KeyCode::ArrowLeft => Some(NamedKey::ArrowLeft),
        KeyCode::ArrowRight => Some(NamedKey::ArrowRight),
        KeyCode::Home => Some(NamedKey::Home),
        KeyCode::End => Some(NamedKey::End),
        KeyCode::PageUp => Some(NamedKey::PageUp),
        KeyCode::PageDown => Some(NamedKey::PageDown),
        KeyCode::F2 => Some(NamedKey::F2),
        _ => None,
    }
}

fn winit_named_to_ui(named: WinitNamed) -> Option<NamedKey> {
    match named {
        WinitNamed::Tab => Some(NamedKey::Tab),
        WinitNamed::Enter => Some(NamedKey::Enter),
        WinitNamed::Escape => Some(NamedKey::Escape),
        WinitNamed::Backspace => Some(NamedKey::Backspace),
        WinitNamed::Delete => Some(NamedKey::Delete),
        WinitNamed::Space => Some(NamedKey::Space),
        WinitNamed::ArrowUp => Some(NamedKey::ArrowUp),
        WinitNamed::ArrowDown => Some(NamedKey::ArrowDown),
        WinitNamed::ArrowLeft => Some(NamedKey::ArrowLeft),
        WinitNamed::ArrowRight => Some(NamedKey::ArrowRight),
        WinitNamed::Home => Some(NamedKey::Home),
        WinitNamed::End => Some(NamedKey::End),
        WinitNamed::PageUp => Some(NamedKey::PageUp),
        WinitNamed::PageDown => Some(NamedKey::PageDown),
        WinitNamed::F2 => Some(NamedKey::F2),
        _ => None,
    }
}

fn keycode_to_char(code: KeyCode) -> Option<char> {
    match code {
        KeyCode::KeyA => Some('a'),
        KeyCode::KeyB => Some('b'),
        KeyCode::KeyC => Some('c'),
        KeyCode::KeyD => Some('d'),
        KeyCode::KeyE => Some('e'),
        KeyCode::KeyF => Some('f'),
        KeyCode::KeyG => Some('g'),
        KeyCode::KeyH => Some('h'),
        KeyCode::KeyI => Some('i'),
        KeyCode::KeyJ => Some('j'),
        KeyCode::KeyK => Some('k'),
        KeyCode::KeyL => Some('l'),
        KeyCode::KeyM => Some('m'),
        KeyCode::KeyN => Some('n'),
        KeyCode::KeyO => Some('o'),
        KeyCode::KeyP => Some('p'),
        KeyCode::KeyQ => Some('q'),
        KeyCode::KeyR => Some('r'),
        KeyCode::KeyS => Some('s'),
        KeyCode::KeyT => Some('t'),
        KeyCode::KeyU => Some('u'),
        KeyCode::KeyV => Some('v'),
        KeyCode::KeyW => Some('w'),
        KeyCode::KeyX => Some('x'),
        KeyCode::KeyY => Some('y'),
        KeyCode::KeyZ => Some('z'),
        KeyCode::Digit0 => Some('0'),
        KeyCode::Digit1 => Some('1'),
        KeyCode::Digit2 => Some('2'),
        KeyCode::Digit3 => Some('3'),
        KeyCode::Digit4 => Some('4'),
        KeyCode::Digit5 => Some('5'),
        KeyCode::Digit6 => Some('6'),
        KeyCode::Digit7 => Some('7'),
        KeyCode::Digit8 => Some('8'),
        KeyCode::Digit9 => Some('9'),
        _ => None,
    }
}
