pub(crate) mod bindings;
pub(crate) mod modkeys;
pub(crate) mod spawn;

use crate::compositor::exit_confirm::exit_confirm_controller;
use crate::compositor::root::Halley;
use crate::compositor::screenshot::screenshot_controller;
use crate::input::ctx::InputCtx;

use std::time::Instant;

use smithay::input::keyboard::{FilterResult, KeysymHandle};
use smithay::utils::SERIAL_COUNTER;

use self::bindings::{
    apply_bound_key, apply_compositor_action_release, compositor_action_allows_repeat,
    compositor_binding_action, key_is_compositor_binding,
    modifiers_keep_focus_cycle_session_active,
};
use self::modkeys::{is_modifier_keycode, update_mod_state};
use halley_config::CompositorBindingAction;
use halley_config::keybinds::key_name_to_evdev;
use smithay::backend::input::KeyState;

use crate::compositor::interaction::state::ClusterNamePromptRepeatAction;

#[inline]
fn now_millis_u32() -> u32 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| (d.as_millis() & 0xffff_ffff) as u32)
        .unwrap_or(0)
}

fn flush_trapped_modal_release(st: &mut Halley, code: u32) {
    let Some(keyboard) = st.platform.seat.get_keyboard() else {
        st.input.interaction_state.modal_release_keys.remove(&code);
        return;
    };

    let (_, mods_changed) =
        keyboard.input_intercept::<(), _>(st, code.into(), KeyState::Released, |_, _, _| ());
    keyboard.input_forward(
        st,
        code.into(),
        KeyState::Released,
        SERIAL_COUNTER.next_serial(),
        now_millis_u32(),
        mods_changed,
    );
    st.input.interaction_state.modal_release_keys.remove(&code);
}

#[inline]
fn cluster_mode_allows_keyboard_action(action: &CompositorBindingAction) -> bool {
    matches!(
        action,
        CompositorBindingAction::ZoomIn
            | CompositorBindingAction::ZoomOut
            | CompositorBindingAction::ZoomReset
    )
}

fn handle_focus_cycle_session_input<B: crate::backend::interface::BackendView>(
    st: &mut Halley,
    ctx: &InputCtx<'_, B>,
    code: u32,
    pressed: bool,
) {
    let mods = ctx.mod_state.borrow().clone();
    let matched_action = if pressed && !is_modifier_keycode(code) {
        compositor_binding_action(st, code, &mods)
    } else {
        None
    };

    if let Some(keyboard) = st.platform.seat.get_keyboard() {
        let serial = SERIAL_COUNTER.next_serial();
        keyboard.input::<(), _>(
            st,
            code.into(),
            if pressed {
                KeyState::Pressed
            } else {
                KeyState::Released
            },
            serial,
            now_millis_u32(),
            |_, _, _| FilterResult::Intercept(()),
        );
    }

    if pressed {
        let escape = key_name_to_evdev("escape").map(|value| value + 8);
        if Some(code) == escape {
            crate::compositor::interaction::state::trap_modal_key_release(st, code);
            if st.cancel_focus_cycle() {
                ctx.backend.request_redraw();
            }
            return;
        }

        if let Some(CompositorBindingAction::FocusCycle(direction)) = matched_action
            && st.start_or_step_focus_cycle(direction, Instant::now())
        {
            ctx.backend.request_redraw();
        }
        return;
    }

    if !modifiers_keep_focus_cycle_session_active(st, &mods)
        && st.commit_focus_cycle(Instant::now())
    {
        ctx.backend.request_redraw();
    }
}

fn cluster_prompt_input_char(keysym: &KeysymHandle<'_>) -> Option<char> {
    let ch = keysym.modified_sym().key_char()?;
    (!ch.is_control()).then_some(ch)
}

fn log_keyboard_binding_resolution(
    st: &Halley,
    code: u32,
    pressed: bool,
    mods: &crate::compositor::interaction::ModState,
    matched_action: Option<&CompositorBindingAction>,
    matched_launch: Option<&str>,
    matched_binding: bool,
    intercept: bool,
) {
    let _ = (
        st,
        code,
        pressed,
        mods,
        matched_action,
        matched_launch,
        matched_binding,
        intercept,
    );
}

pub(crate) fn handle_keyboard_input<B: crate::backend::interface::BackendView>(
    st: &mut Halley,
    ctx: &InputCtx<'_, B>,
    code: u32,
    pressed: bool,
) {
    let exit_confirm_active = exit_confirm_controller(&*st).active();
    update_mod_state(&mut ctx.mod_state.borrow_mut(), code, pressed);
    if !pressed
        && st
            .input
            .interaction_state
            .modal_release_keys
            .contains(&code)
    {
        flush_trapped_modal_release(st, code);
        return;
    }
    let exit_escape = key_name_to_evdev("escape").map(|code| code + 8);
    let exit_return = key_name_to_evdev("return").map(|code| code + 8);
    if exit_confirm_active {
        if let Some(keyboard) = st.platform.seat.get_keyboard() {
            let serial = SERIAL_COUNTER.next_serial();
            keyboard.input::<(), _>(
                st,
                code.into(),
                if pressed {
                    KeyState::Pressed
                } else {
                    KeyState::Released
                },
                serial,
                now_millis_u32(),
                |_, _, _| FilterResult::Intercept(()),
            );
        }
        if pressed {
            if Some(code) == exit_escape {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                exit_confirm_controller(&mut *st).clear();
                ctx.backend.request_redraw();
            } else if Some(code) == exit_return {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                exit_confirm_controller(&mut *st).clear();
                st.request_exit();
                ctx.backend.request_redraw();
            }
        }
        return;
    }

    if pressed {
        let now_ms = st.now_ms(Instant::now());
        if crate::compositor::interaction::state::note_typing_activity(st, now_ms) {
            ctx.backend.request_redraw();
        }
    }

    if screenshot_controller(&mut *st).screenshot_session_active() {
        if let Some(keyboard) = st.platform.seat.get_keyboard() {
            let serial = SERIAL_COUNTER.next_serial();
            keyboard.input::<(), _>(
                st,
                code.into(),
                if pressed {
                    KeyState::Pressed
                } else {
                    KeyState::Released
                },
                serial,
                now_millis_u32(),
                |_, _, _| FilterResult::Intercept(()),
            );
        }
        if pressed {
            let escape = key_name_to_evdev("escape").map(|value| value + 8);
            let enter = key_name_to_evdev("return").map(|value| value + 8);
            let left = key_name_to_evdev("left").map(|value| value + 8);
            let right = key_name_to_evdev("right").map(|value| value + 8);
            let menu_mode = st
                .input
                .interaction_state
                .screenshot_session
                .as_ref()
                .is_some_and(|session| session.mode == halley_ipc::CaptureMode::Menu);
            if Some(code) == escape {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                if menu_mode {
                    let _ = screenshot_controller(&mut *st).cancel_screenshot_session();
                } else {
                    let _ = screenshot_controller(&mut *st).return_screenshot_session_to_menu();
                }
                ctx.backend.request_redraw();
            } else if Some(code) == enter {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                let _ = screenshot_controller(&mut *st).confirm_screenshot_session(Instant::now());
                ctx.backend.request_redraw();
            } else if Some(code) == left {
                if menu_mode {
                    crate::compositor::interaction::state::trap_modal_key_release(st, code);
                }
                let _ = screenshot_controller(&mut *st).move_screenshot_menu_selection(-1);
                ctx.backend.request_redraw();
            } else if Some(code) == right {
                if menu_mode {
                    crate::compositor::interaction::state::trap_modal_key_release(st, code);
                }
                let _ = screenshot_controller(&mut *st).move_screenshot_menu_selection(1);
                ctx.backend.request_redraw();
            }
        }
        return;
    }

    let prompt_monitor = st.model.monitor_state.current_monitor.clone();
    if crate::compositor::clusters::system::cluster_system_controller(&*st)
        .cluster_name_prompt_active_for_monitor(prompt_monitor.as_str())
    {
        let mut repeated_char = None;
        if let Some(keyboard) = st.platform.seat.get_keyboard() {
            let _ = keyboard.input::<(), _>(
                st,
                code.into(),
                if pressed {
                    KeyState::Pressed
                } else {
                    KeyState::Released
                },
                SERIAL_COUNTER.next_serial(),
                now_millis_u32(),
                |_, _, keysym| {
                    if pressed {
                        repeated_char = cluster_prompt_input_char(&keysym);
                    }
                    FilterResult::Intercept(())
                },
            );
        }
        if pressed {
            let first_press = ctx.mod_state.borrow_mut().intercepted_keys.insert(code);
            let left = key_name_to_evdev("left").map(|value| value + 8);
            let right = key_name_to_evdev("right").map(|value| value + 8);
            let delete = key_name_to_evdev("delete").map(|value| value + 8);
            let backspace = key_name_to_evdev("backspace").map(|value| value + 8);
            let escape = key_name_to_evdev("escape").map(|value| value + 8);
            let enter = key_name_to_evdev("return").map(|value| value + 8);
            let repeat_action = if Some(code) == left {
                Some(ClusterNamePromptRepeatAction::MoveLeft)
            } else if Some(code) == right {
                Some(ClusterNamePromptRepeatAction::MoveRight)
            } else if Some(code) == delete {
                Some(ClusterNamePromptRepeatAction::Delete)
            } else if Some(code) == backspace {
                Some(ClusterNamePromptRepeatAction::Backspace)
            } else {
                repeated_char.map(ClusterNamePromptRepeatAction::Insert)
            };
            let handled = if Some(code) == escape {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .cancel_cluster_name_prompt_for_monitor(prompt_monitor.as_str())
            } else if Some(code) == enter {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .confirm_cluster_name_prompt_for_monitor(
                        prompt_monitor.as_str(),
                        Instant::now(),
                    )
            } else if !first_press && repeat_action.is_none() {
                false
            } else if Some(code) == left {
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .cluster_name_prompt_move_left_for_monitor(prompt_monitor.as_str())
            } else if Some(code) == right {
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .cluster_name_prompt_move_right_for_monitor(prompt_monitor.as_str())
            } else if Some(code) == backspace {
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .cluster_name_prompt_backspace_for_monitor(prompt_monitor.as_str())
            } else if Some(code) == delete {
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .cluster_name_prompt_delete_for_monitor(prompt_monitor.as_str())
            } else if let Some(ch) = repeated_char {
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .insert_cluster_name_prompt_char_for_monitor(prompt_monitor.as_str(), ch)
            } else {
                false
            };
            if handled && let Some(action) = repeat_action {
                let now_ms = st.now_ms(Instant::now());
                crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                    .start_cluster_name_prompt_repeat_for_monitor(
                        prompt_monitor.as_str(),
                        code,
                        action,
                        now_ms,
                    );
            }
            if handled {
                ctx.backend.request_redraw();
            }
        } else {
            ctx.mod_state.borrow_mut().intercepted_keys.remove(&code);
            crate::compositor::clusters::system::cluster_system_controller(&mut *st)
                .stop_cluster_name_prompt_repeat_for_code(code);
        }
        return;
    }

    if st.focus_cycle_session_active() {
        handle_focus_cycle_session_input(st, ctx, code, pressed);
        return;
    }

    let cluster_escape = key_name_to_evdev("escape").map(|code| code + 8);
    let cluster_return = key_name_to_evdev("return").map(|code| code + 8);
    if st.cluster_mode_active() && (Some(code) == cluster_escape || Some(code) == cluster_return) {
        if let Some(keyboard) = st.platform.seat.get_keyboard() {
            let serial = SERIAL_COUNTER.next_serial();
            keyboard.input::<(), _>(
                st,
                code.into(),
                if pressed {
                    KeyState::Pressed
                } else {
                    KeyState::Released
                },
                serial,
                now_millis_u32(),
                |_, _, _| FilterResult::Intercept(()),
            );
        }
        if pressed {
            let handled = if Some(code) == cluster_escape {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                st.exit_cluster_mode()
            } else {
                crate::compositor::interaction::state::trap_modal_key_release(st, code);
                st.confirm_cluster_mode(Instant::now())
            };
            if handled || Some(code) == cluster_return || Some(code) == cluster_escape {
                ctx.backend.request_redraw();
            }
        }
        return;
    }

    let mods = ctx.mod_state.borrow().clone();
    let is_mod_key = is_modifier_keycode(code);
    let layer_shell_keyboard_focus =
        crate::compositor::monitor::layer_shell::keyboard_focus_is_layer_surface(st);
    let session_lock_active = crate::protocol::wayland::session_lock::session_lock_active(st);
    let compositor_shortcuts_blocked = layer_shell_keyboard_focus || session_lock_active;
    let matched_action = if pressed && !is_mod_key && !compositor_shortcuts_blocked {
        compositor_binding_action(st, code, &mods)
    } else {
        None
    };
    let matched_launch = if pressed && !is_mod_key && !compositor_shortcuts_blocked {
        st.runtime
            .tuning
            .launch_bindings
            .iter()
            .find(|binding| {
                bindings::input_matches_binding(code, binding.key)
                    && self::modkeys::modifier_exact(&mods, binding.modifiers)
            })
            .map(|binding| binding.command.clone())
    } else {
        None
    };
    let matched_binding = matched_action.is_some()
        || matched_launch.is_some()
        || (pressed && !is_mod_key && key_is_compositor_binding(st, code, &mods));
    let cluster_blocks_key = st.cluster_mode_active() && !is_mod_key;
    let cluster_allowed_action = matched_action
        .as_ref()
        .is_some_and(cluster_mode_allows_keyboard_action);

    if pressed
        && !is_mod_key
        && !matched_binding
        && !cluster_blocks_key
        && !compositor_shortcuts_blocked
        && let Some(fid) = st.last_input_surface_node_for_monitor(st.focused_monitor())
    {
        let open_monitors = st
            .model
            .cluster_state
            .cluster_bloom_open
            .keys()
            .cloned()
            .collect::<Vec<_>>();
        for monitor in open_monitors {
            let open_core = st
                .cluster_bloom_for_monitor(monitor.as_str())
                .and_then(|cid| st.model.field.cluster(cid).and_then(|cluster| cluster.core));
            if open_core != Some(fid) {
                let _ = st.close_cluster_bloom_for_monitor(monitor.as_str());
            }
        }
        st.set_interaction_focus(Some(fid), 30_000, Instant::now());
    }

    let mut first_binding_press = false;
    let mut repeat_binding_press = false;

    let intercept = if is_mod_key {
        false
    } else if cluster_blocks_key {
        if pressed && cluster_allowed_action {
            let mut ms = ctx.mod_state.borrow_mut();
            first_binding_press = ms.intercepted_keys.insert(code);
            repeat_binding_press = !first_binding_press;
            if first_binding_press && let Some(action) = matched_action.clone() {
                ms.intercepted_compositor_actions.insert(code, action);
            }
        } else if !pressed {
            let mut ms = ctx.mod_state.borrow_mut();
            let intercepted = ms.intercepted_keys.remove(&code);
            if intercepted {
                if let Some(action) = ms.intercepted_compositor_actions.remove(&code)
                    && apply_compositor_action_release(st, action)
                {
                    ctx.backend.request_redraw();
                }
            } else {
                ms.intercepted_compositor_actions.remove(&code);
            }
        }
        true
    } else if pressed {
        if matched_binding {
            let mut ms = ctx.mod_state.borrow_mut();
            first_binding_press = ms.intercepted_keys.insert(code);
            repeat_binding_press = !first_binding_press;
            if first_binding_press && let Some(action) = matched_action.clone() {
                ms.intercepted_compositor_actions.insert(code, action);
            }
            true
        } else {
            false
        }
    } else {
        let mut ms = ctx.mod_state.borrow_mut();
        let intercepted = ms.intercepted_keys.remove(&code);
        if intercepted {
            if let Some(action) = ms.intercepted_compositor_actions.remove(&code)
                && apply_compositor_action_release(st, action)
            {
                ctx.backend.request_redraw();
            }
        }
        intercepted
    };

    if let Some(keyboard) = st.platform.seat.get_keyboard() {
        let now = Instant::now();
        let serial = SERIAL_COUNTER.next_serial();
        crate::protocol::wayland::activation::note_input_serial(st, serial, st.now_ms(now));
        let key_state = if pressed {
            KeyState::Pressed
        } else {
            KeyState::Released
        };

        keyboard.input::<(), _>(
            st,
            code.into(),
            key_state,
            serial,
            now_millis_u32(),
            |_, _, _| {
                if intercept {
                    FilterResult::Intercept(())
                } else {
                    FilterResult::Forward
                }
            },
        );
    }

    log_keyboard_binding_resolution(
        st,
        code,
        pressed,
        &mods,
        matched_action.as_ref(),
        matched_launch.as_deref(),
        matched_binding,
        intercept,
    );

    if pressed
        && matched_binding
        && (first_binding_press
            || (repeat_binding_press
                && matched_action
                    .as_ref()
                    .is_some_and(|action: &CompositorBindingAction| {
                        compositor_action_allows_repeat(action.clone())
                    })))
        && apply_bound_key(st, code, &mods, ctx.config_path, ctx.wayland_display)
    {
        ctx.backend.request_redraw();
    }
}
