use super::state::{ProfileAction, ProfilesDialogState, matching};
use crate::tui::app::App;
use crate::tui::events::AppMode;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyOutcome {
Consumed,
Close,
Switch(usize),
Delete(usize),
Create(String, Option<String>),
Migrate(String, String),
NotConsumed,
}
pub async fn handle_key(app: &mut App, key: KeyEvent) {
let profiles = app.profiles_dialog.profiles.clone();
let active = app.profiles_dialog.active_profile.clone();
match decide(&mut app.profiles_dialog, &profiles, &active, key) {
KeyOutcome::Consumed | KeyOutcome::NotConsumed => {}
KeyOutcome::Close => {
app.mode = AppMode::Chat;
}
KeyOutcome::Switch(idx) => {
let visible = matching(&profiles, &app.profiles_dialog.filter);
if let Some(entry) = visible.get(idx) {
let name = entry.name.clone();
super::actions::switch_to(app, &name);
}
}
KeyOutcome::Delete(idx) => {
let visible = matching(&profiles, &app.profiles_dialog.filter);
if let Some(entry) = visible.get(idx) {
let name = entry.name.clone();
super::actions::delete(app, &name);
}
}
KeyOutcome::Create(name, desc) => {
super::actions::create(app, &name, desc.as_deref());
}
KeyOutcome::Migrate(from, to) => {
super::actions::migrate(app, &from, &to);
}
}
}
pub fn decide(
state: &mut ProfilesDialogState,
profiles: &[crate::config::profile::ProfileEntry],
active: &str,
key: KeyEvent,
) -> KeyOutcome {
if key.code == KeyCode::Esc {
if state.action != ProfileAction::None {
state.action = ProfileAction::None;
state.input_buffer.clear();
state.input_buffer_2.clear();
return KeyOutcome::Consumed;
}
return KeyOutcome::Close;
}
match &state.action {
ProfileAction::CreateName => {
return handle_text_input(state, key, ProfileAction::CreateDesc);
}
ProfileAction::CreateDesc => {
return handle_create_desc(state, key);
}
ProfileAction::ConfirmDelete(name) => {
return handle_confirm_delete(state, name.clone(), key);
}
ProfileAction::MigrateFrom => {
return handle_text_input(state, key, ProfileAction::MigrateTo);
}
ProfileAction::MigrateTo => {
return handle_migrate_to(state, key);
}
ProfileAction::None => {}
}
match key.code {
KeyCode::Tab | KeyCode::Down => {
move_selection(state, profiles, 1);
KeyOutcome::Consumed
}
KeyCode::BackTab | KeyCode::Up => {
move_selection(state, profiles, -1);
KeyOutcome::Consumed
}
KeyCode::Enter => {
let visible = matching(profiles, &state.filter);
if let Some(entry) = visible.get(state.selected_index) {
if entry.name == active {
KeyOutcome::Consumed } else {
KeyOutcome::Switch(state.selected_index)
}
} else {
KeyOutcome::Consumed
}
}
KeyCode::Char('n') if key.modifiers.is_empty() => {
state.action = ProfileAction::CreateName;
state.input_buffer.clear();
KeyOutcome::Consumed
}
KeyCode::Char('d') if key.modifiers.is_empty() => {
let visible = matching(profiles, &state.filter);
if let Some(entry) = visible.get(state.selected_index) {
if entry.name == "default" {
return KeyOutcome::Consumed; }
state.action = ProfileAction::ConfirmDelete(entry.name.clone());
}
KeyOutcome::Consumed
}
KeyCode::Char('m') if key.modifiers.is_empty() => {
state.action = ProfileAction::MigrateFrom;
state.input_buffer.clear();
state.input_buffer_2.clear();
KeyOutcome::Consumed
}
KeyCode::Backspace => {
state.filter.pop();
state.selected_index = 0;
KeyOutcome::Consumed
}
KeyCode::Char(c) => {
if key.modifiers.contains(KeyModifiers::CONTROL) {
return KeyOutcome::NotConsumed;
}
state.filter.push(c);
state.selected_index = 0;
KeyOutcome::Consumed
}
_ => KeyOutcome::NotConsumed,
}
}
fn handle_text_input(
state: &mut ProfilesDialogState,
key: KeyEvent,
next_action: ProfileAction,
) -> KeyOutcome {
match key.code {
KeyCode::Enter => {
if state.input_buffer.trim().is_empty() {
return KeyOutcome::Consumed;
}
state.action = next_action;
KeyOutcome::Consumed
}
KeyCode::Backspace => {
state.input_buffer.pop();
KeyOutcome::Consumed
}
KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
state.input_buffer.push(c);
KeyOutcome::Consumed
}
_ => KeyOutcome::Consumed,
}
}
fn handle_create_desc(state: &mut ProfilesDialogState, key: KeyEvent) -> KeyOutcome {
match key.code {
KeyCode::Enter => {
let name = state.input_buffer.trim().to_string();
if name.is_empty() {
return KeyOutcome::Consumed;
}
let desc = if state.input_buffer_2.trim().is_empty() {
None
} else {
Some(state.input_buffer_2.trim().to_string())
};
KeyOutcome::Create(name, desc)
}
KeyCode::Backspace => {
state.input_buffer_2.pop();
KeyOutcome::Consumed
}
KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
state.input_buffer_2.push(c);
KeyOutcome::Consumed
}
_ => KeyOutcome::Consumed,
}
}
fn handle_confirm_delete(
state: &mut ProfilesDialogState,
_name: String,
key: KeyEvent,
) -> KeyOutcome {
match key.code {
KeyCode::Char('y') | KeyCode::Enter => {
let idx = state.selected_index;
state.action = ProfileAction::None;
KeyOutcome::Delete(idx)
}
_ => {
state.action = ProfileAction::None;
KeyOutcome::Consumed
}
}
}
fn handle_migrate_to(state: &mut ProfilesDialogState, key: KeyEvent) -> KeyOutcome {
match key.code {
KeyCode::Enter => {
let from = state.input_buffer.trim().to_string();
let to = state.input_buffer_2.trim().to_string();
if from.is_empty() || to.is_empty() {
return KeyOutcome::Consumed;
}
KeyOutcome::Migrate(from, to)
}
KeyCode::Backspace => {
state.input_buffer_2.pop();
KeyOutcome::Consumed
}
KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
state.input_buffer_2.push(c);
KeyOutcome::Consumed
}
_ => KeyOutcome::Consumed,
}
}
fn move_selection(
state: &mut ProfilesDialogState,
profiles: &[crate::config::profile::ProfileEntry],
delta: i32,
) {
let visible = matching(profiles, &state.filter);
let count = visible.len();
if count == 0 {
state.selected_index = 0;
return;
}
let count_i = count as i32;
let cur = state.selected_index.min(count - 1) as i32;
let next = ((cur + delta) % count_i + count_i) % count_i;
state.selected_index = next as usize;
}