pub(super) fn ghostty_key_event_from_terminal_key(
    key: &crate::input::TerminalKey,
) -> Option<crate::ghostty::KeyEvent> {
    let mut event = crate::ghostty::KeyEvent::new().ok()?;
    event.set_action(match key.kind {
        crossterm::event::KeyEventKind::Press => {
            crate::ghostty::ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_PRESS
        }
        crossterm::event::KeyEventKind::Release => {
            crate::ghostty::ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_RELEASE
        }
        crossterm::event::KeyEventKind::Repeat => {
            crate::ghostty::ffi::GhosttyKeyAction_GHOSTTY_KEY_ACTION_REPEAT
        }
    });
    let mut mods = ghostty_mods_from_key_modifiers(key.modifiers);
    if matches!(key.code, crossterm::event::KeyCode::BackTab) {
        // Ghostty represents backtab as Tab with Shift rather than a distinct key.
        mods |= crate::ghostty::MOD_SHIFT;
    }
    event.set_mods(mods);
    event.set_key(ghostty_key_from_crossterm_key_code(
        key.code,
        key.shifted_codepoint,
    )?);

    if let Some(text) = ghostty_key_text(key) {
        event.set_utf8(&text);
    } else {
        event.set_utf8("");
    }

    if let Some(codepoint) = ghostty_unshifted_codepoint(key) {
        event.set_unshifted_codepoint(codepoint);
    }

    Some(event)
}

pub(super) fn ghostty_prefers_herdr_text_encoding(key: &crate::input::TerminalKey) -> bool {
    matches!(key.code, crossterm::event::KeyCode::Char(_))
}

pub(super) fn ghostty_mods_from_key_modifiers(modifiers: crossterm::event::KeyModifiers) -> u16 {
    let mut ghostty_mods = 0u16;
    if modifiers.contains(crossterm::event::KeyModifiers::SHIFT) {
        ghostty_mods |= crate::ghostty::MOD_SHIFT;
    }
    if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) {
        ghostty_mods |= crate::ghostty::MOD_CTRL;
    }
    if modifiers.contains(crossterm::event::KeyModifiers::ALT) {
        ghostty_mods |= crate::ghostty::MOD_ALT;
    }
    if modifiers.contains(crossterm::event::KeyModifiers::SUPER) {
        ghostty_mods |= crate::ghostty::MOD_SUPER;
    }
    ghostty_mods
}

pub(super) fn ghostty_mouse_encoder_for_terminal(
    terminal: &crate::ghostty::Terminal,
    position: crate::input::mouse::Position,
) -> Option<crate::ghostty::MouseEncoder> {
    let mut encoder = crate::ghostty::MouseEncoder::new().ok()?;
    encoder.set_from_terminal(terminal);
    let cols = terminal.cols().ok()? as u32;
    let rows = terminal.rows().ok()? as u32;
    match position {
        crate::input::mouse::Position::Cell { .. } => {
            if terminal
                .mode_get(crate::ghostty::MODE_MOUSE_SGR_PIXELS)
                .ok()?
            {
                encoder.set_format(crate::ghostty::MOUSE_FORMAT_SGR);
            }
            encoder.set_size(cols, rows, 1, 1);
        }
        crate::input::mouse::Position::Pixels { .. } => {
            let width_px = terminal.width_px().ok()?;
            let height_px = terminal.height_px().ok()?;
            if width_px == 0 || height_px == 0 || cols == 0 || rows == 0 {
                return None;
            }
            encoder.set_size(width_px, height_px, width_px / cols, height_px / rows);
        }
    }
    Some(encoder)
}

pub(super) fn ghostty_mouse_position_for_terminal(
    position: crate::input::mouse::Position,
) -> Option<(f32, f32)> {
    match position {
        crate::input::mouse::Position::Pixels { x, y } => Some((x as f32, y as f32)),
        crate::input::mouse::Position::Cell { column, row } => Some((column as f32, row as f32)),
    }
}

pub(super) fn ghostty_mouse_event_from_button_kind(
    kind: crossterm::event::MouseEventKind,
    column: u16,
    row: u16,
    modifiers: crossterm::event::KeyModifiers,
) -> Option<crate::ghostty::MouseEvent> {
    let mut event = crate::ghostty::MouseEvent::new().ok()?;
    let (action, button) = match kind {
        crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left) => (
            crate::ghostty::MOUSE_ACTION_PRESS,
            Some(crate::ghostty::MOUSE_BUTTON_LEFT),
        ),
        crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Middle) => (
            crate::ghostty::MOUSE_ACTION_PRESS,
            Some(crate::ghostty::MOUSE_BUTTON_MIDDLE),
        ),
        crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Right) => (
            crate::ghostty::MOUSE_ACTION_PRESS,
            Some(crate::ghostty::MOUSE_BUTTON_RIGHT),
        ),
        crossterm::event::MouseEventKind::Up(crossterm::event::MouseButton::Left) => (
            crate::ghostty::MOUSE_ACTION_RELEASE,
            Some(crate::ghostty::MOUSE_BUTTON_LEFT),
        ),
        crossterm::event::MouseEventKind::Up(crossterm::event::MouseButton::Middle) => (
            crate::ghostty::MOUSE_ACTION_RELEASE,
            Some(crate::ghostty::MOUSE_BUTTON_MIDDLE),
        ),
        crossterm::event::MouseEventKind::Up(crossterm::event::MouseButton::Right) => (
            crate::ghostty::MOUSE_ACTION_RELEASE,
            Some(crate::ghostty::MOUSE_BUTTON_RIGHT),
        ),
        crossterm::event::MouseEventKind::Drag(crossterm::event::MouseButton::Left) => (
            crate::ghostty::MOUSE_ACTION_MOTION,
            Some(crate::ghostty::MOUSE_BUTTON_LEFT),
        ),
        crossterm::event::MouseEventKind::Drag(crossterm::event::MouseButton::Middle) => (
            crate::ghostty::MOUSE_ACTION_MOTION,
            Some(crate::ghostty::MOUSE_BUTTON_MIDDLE),
        ),
        crossterm::event::MouseEventKind::Drag(crossterm::event::MouseButton::Right) => (
            crate::ghostty::MOUSE_ACTION_MOTION,
            Some(crate::ghostty::MOUSE_BUTTON_RIGHT),
        ),
        _ => return None,
    };
    event.set_action(action);
    if let Some(button) = button {
        event.set_button(button);
    } else {
        event.clear_button();
    }
    event.set_mods(ghostty_mods_from_key_modifiers(modifiers));
    event.set_position(column as f32, row as f32);
    Some(event)
}

pub(super) fn ghostty_mouse_event_from_motion_kind(
    kind: crossterm::event::MouseEventKind,
    column: u16,
    row: u16,
    modifiers: crossterm::event::KeyModifiers,
) -> Option<crate::ghostty::MouseEvent> {
    if kind != crossterm::event::MouseEventKind::Moved {
        return None;
    }

    let mut event = crate::ghostty::MouseEvent::new().ok()?;
    event.set_action(crate::ghostty::MOUSE_ACTION_MOTION);
    event.clear_button();
    event.set_mods(ghostty_mods_from_key_modifiers(modifiers));
    event.set_position(column as f32, row as f32);
    Some(event)
}

pub(super) fn ghostty_mouse_event_from_wheel_kind(
    kind: crossterm::event::MouseEventKind,
    column: u16,
    row: u16,
    modifiers: crossterm::event::KeyModifiers,
) -> Option<crate::ghostty::MouseEvent> {
    let mut event = crate::ghostty::MouseEvent::new().ok()?;
    event.set_action(crate::ghostty::MOUSE_ACTION_PRESS);
    let button = match kind {
        crossterm::event::MouseEventKind::ScrollUp => crate::ghostty::MOUSE_BUTTON_WHEEL_UP,
        crossterm::event::MouseEventKind::ScrollDown => crate::ghostty::MOUSE_BUTTON_WHEEL_DOWN,
        crossterm::event::MouseEventKind::ScrollLeft => crate::ghostty::MOUSE_BUTTON_WHEEL_LEFT,
        crossterm::event::MouseEventKind::ScrollRight => crate::ghostty::MOUSE_BUTTON_WHEEL_RIGHT,
        _ => return None,
    };
    event.set_button(button);
    event.set_mods(ghostty_mods_from_key_modifiers(modifiers));
    event.set_position(column as f32, row as f32);
    Some(event)
}

fn ghostty_key_text(key: &crate::input::TerminalKey) -> Option<String> {
    match key.code {
        crossterm::event::KeyCode::Char(c) => Some(
            key.shifted_codepoint
                .and_then(char::from_u32)
                .unwrap_or(c)
                .to_string(),
        ),
        _ => None,
    }
}

fn ghostty_unshifted_codepoint(key: &crate::input::TerminalKey) -> Option<u32> {
    match key.code {
        crossterm::event::KeyCode::Char(c) => Some(c as u32),
        _ => None,
    }
}

fn ghostty_key_from_crossterm_key_code(
    code: crossterm::event::KeyCode,
    shifted_codepoint: Option<u32>,
) -> Option<u32> {
    use crate::ghostty::ffi;
    use crossterm::event::KeyCode;

    match code {
        KeyCode::Backspace => Some(ffi::GhosttyKey_GHOSTTY_KEY_BACKSPACE),
        KeyCode::Enter => Some(ffi::GhosttyKey_GHOSTTY_KEY_ENTER),
        KeyCode::Left => Some(ffi::GhosttyKey_GHOSTTY_KEY_ARROW_LEFT),
        KeyCode::Right => Some(ffi::GhosttyKey_GHOSTTY_KEY_ARROW_RIGHT),
        KeyCode::Up => Some(ffi::GhosttyKey_GHOSTTY_KEY_ARROW_UP),
        KeyCode::Down => Some(ffi::GhosttyKey_GHOSTTY_KEY_ARROW_DOWN),
        KeyCode::Home => Some(ffi::GhosttyKey_GHOSTTY_KEY_HOME),
        KeyCode::End => Some(ffi::GhosttyKey_GHOSTTY_KEY_END),
        KeyCode::PageUp => Some(ffi::GhosttyKey_GHOSTTY_KEY_PAGE_UP),
        KeyCode::PageDown => Some(ffi::GhosttyKey_GHOSTTY_KEY_PAGE_DOWN),
        KeyCode::Tab | KeyCode::BackTab => Some(ffi::GhosttyKey_GHOSTTY_KEY_TAB),
        KeyCode::Delete => Some(ffi::GhosttyKey_GHOSTTY_KEY_DELETE),
        KeyCode::Insert => Some(ffi::GhosttyKey_GHOSTTY_KEY_INSERT),
        KeyCode::Esc => Some(ffi::GhosttyKey_GHOSTTY_KEY_ESCAPE),
        KeyCode::F(n) => Some(match n {
            1 => ffi::GhosttyKey_GHOSTTY_KEY_F1,
            2 => ffi::GhosttyKey_GHOSTTY_KEY_F2,
            3 => ffi::GhosttyKey_GHOSTTY_KEY_F3,
            4 => ffi::GhosttyKey_GHOSTTY_KEY_F4,
            5 => ffi::GhosttyKey_GHOSTTY_KEY_F5,
            6 => ffi::GhosttyKey_GHOSTTY_KEY_F6,
            7 => ffi::GhosttyKey_GHOSTTY_KEY_F7,
            8 => ffi::GhosttyKey_GHOSTTY_KEY_F8,
            9 => ffi::GhosttyKey_GHOSTTY_KEY_F9,
            10 => ffi::GhosttyKey_GHOSTTY_KEY_F10,
            11 => ffi::GhosttyKey_GHOSTTY_KEY_F11,
            12 => ffi::GhosttyKey_GHOSTTY_KEY_F12,
            _ => return None,
        }),
        KeyCode::Char(c) => ghostty_key_from_char(c, shifted_codepoint),
        _ => None,
    }
}

fn ghostty_key_from_char(c: char, shifted_codepoint: Option<u32>) -> Option<u32> {
    use crate::ghostty::ffi;

    let base = if let Some(shifted) = shifted_codepoint.and_then(char::from_u32) {
        ghostty_unshifted_ascii_pair(shifted).unwrap_or(c)
    } else {
        c
    };

    match base.to_ascii_lowercase() {
        'a' => Some(ffi::GhosttyKey_GHOSTTY_KEY_A),
        'b' => Some(ffi::GhosttyKey_GHOSTTY_KEY_B),
        'c' => Some(ffi::GhosttyKey_GHOSTTY_KEY_C),
        'd' => Some(ffi::GhosttyKey_GHOSTTY_KEY_D),
        'e' => Some(ffi::GhosttyKey_GHOSTTY_KEY_E),
        'f' => Some(ffi::GhosttyKey_GHOSTTY_KEY_F),
        'g' => Some(ffi::GhosttyKey_GHOSTTY_KEY_G),
        'h' => Some(ffi::GhosttyKey_GHOSTTY_KEY_H),
        'i' => Some(ffi::GhosttyKey_GHOSTTY_KEY_I),
        'j' => Some(ffi::GhosttyKey_GHOSTTY_KEY_J),
        'k' => Some(ffi::GhosttyKey_GHOSTTY_KEY_K),
        'l' => Some(ffi::GhosttyKey_GHOSTTY_KEY_L),
        'm' => Some(ffi::GhosttyKey_GHOSTTY_KEY_M),
        'n' => Some(ffi::GhosttyKey_GHOSTTY_KEY_N),
        'o' => Some(ffi::GhosttyKey_GHOSTTY_KEY_O),
        'p' => Some(ffi::GhosttyKey_GHOSTTY_KEY_P),
        'q' => Some(ffi::GhosttyKey_GHOSTTY_KEY_Q),
        'r' => Some(ffi::GhosttyKey_GHOSTTY_KEY_R),
        's' => Some(ffi::GhosttyKey_GHOSTTY_KEY_S),
        't' => Some(ffi::GhosttyKey_GHOSTTY_KEY_T),
        'u' => Some(ffi::GhosttyKey_GHOSTTY_KEY_U),
        'v' => Some(ffi::GhosttyKey_GHOSTTY_KEY_V),
        'w' => Some(ffi::GhosttyKey_GHOSTTY_KEY_W),
        'x' => Some(ffi::GhosttyKey_GHOSTTY_KEY_X),
        'y' => Some(ffi::GhosttyKey_GHOSTTY_KEY_Y),
        'z' => Some(ffi::GhosttyKey_GHOSTTY_KEY_Z),
        '0' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_0),
        '1' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_1),
        '2' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_2),
        '3' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_3),
        '4' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_4),
        '5' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_5),
        '6' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_6),
        '7' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_7),
        '8' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_8),
        '9' => Some(ffi::GhosttyKey_GHOSTTY_KEY_DIGIT_9),
        '`' => Some(ffi::GhosttyKey_GHOSTTY_KEY_BACKQUOTE),
        '\\' => Some(ffi::GhosttyKey_GHOSTTY_KEY_BACKSLASH),
        '[' => Some(ffi::GhosttyKey_GHOSTTY_KEY_BRACKET_LEFT),
        ']' => Some(ffi::GhosttyKey_GHOSTTY_KEY_BRACKET_RIGHT),
        ',' => Some(ffi::GhosttyKey_GHOSTTY_KEY_COMMA),
        '=' => Some(ffi::GhosttyKey_GHOSTTY_KEY_EQUAL),
        '-' => Some(ffi::GhosttyKey_GHOSTTY_KEY_MINUS),
        '.' => Some(ffi::GhosttyKey_GHOSTTY_KEY_PERIOD),
        '\'' => Some(ffi::GhosttyKey_GHOSTTY_KEY_QUOTE),
        ';' => Some(ffi::GhosttyKey_GHOSTTY_KEY_SEMICOLON),
        '/' => Some(ffi::GhosttyKey_GHOSTTY_KEY_SLASH),
        ' ' => Some(ffi::GhosttyKey_GHOSTTY_KEY_SPACE),
        _ => None,
    }
}

fn ghostty_unshifted_ascii_pair(c: char) -> Option<char> {
    Some(match c {
        '!' => '1',
        '@' => '2',
        '#' => '3',
        '$' => '4',
        '%' => '5',
        '^' => '6',
        '&' => '7',
        '*' => '8',
        '(' => '9',
        ')' => '0',
        '_' => '-',
        '+' => '=',
        '{' => '[',
        '}' => ']',
        '|' => '\\',
        ':' => ';',
        '"' => '\'',
        '<' => ',',
        '>' => '.',
        '?' => '/',
        '~' => '`',
        _ => return None,
    })
}
