trackWork 0.15.0

A terminal-based time tracking application for managing work sessions
use anyhow::Result;
use chrono::Datelike;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use crate::app::{App, InputMode};
use crate::passphrase;
use crate::settings;
use crate::tasks;

use super::week_summary;

const OPERATIONS_MENU_ITEMS: usize = 4;

/// Handle keyboard input for dashboard views
pub fn handle_input(app: &mut App, key: KeyEvent) -> Result<bool> {
    match &app.input_mode {
        InputMode::Normal => handle_normal_mode(app, key),
        InputMode::Editing { .. } => handle_editing_mode(app, key),
        InputMode::EditingDay { .. } => handle_editing_day_mode(app, key),
        InputMode::Creating { .. } => handle_creating_mode(app, key),
        InputMode::ConfirmDelete { entry_id } => handle_confirm_delete_mode(app, key, *entry_id),
        InputMode::Settings { .. } => {
            settings::handle_settings_input(app, key);
            Ok(false)
        }
        InputMode::Triggers { .. } => {
            crate::triggers::handle_triggers_input(app, key);
            Ok(false)
        }
        InputMode::WhatsNew => handle_whats_new_mode(app, key),
        InputMode::Tasks { .. } => tasks::handle_tasks_input(app, key),
        InputMode::PassphrasePrompt { .. } => passphrase::handle_passphrase_prompt_input(app, key),
        InputMode::PassphraseChange { .. } => passphrase::handle_passphrase_change_input(app, key),
        InputMode::OperationsMenu { .. } => handle_operations_menu_mode(app, key),
        InputMode::Hotkeys => {
            // Any key closes the hotkeys reference modal.
            app.input_mode = InputMode::Normal;
            Ok(false)
        }
        InputMode::WeekSummary { .. } => week_summary::handle_week_summary_input(app, key),
    }
}

fn handle_normal_mode(app: &mut App, key: KeyEvent) -> Result<bool> {
    // Any key other than `q` cancels a pending quit confirmation.
    if !matches!(key.code, KeyCode::Char('q') | KeyCode::Char('Q')) {
        app.quit_armed_at = None;
    }
    match key.code {
        KeyCode::Char('q') | KeyCode::Char('Q') => Ok(app.handle_quit_key()), // double-tap to quit
        KeyCode::Char('n') | KeyCode::Char('N') => {
            app.start_creating();
            Ok(false)
        }
        KeyCode::Char('e') | KeyCode::Char('E') => {
            if app.at_work_selected {
                app.start_editing_day();
            } else {
                app.start_editing();
            }
            Ok(false)
        }
        KeyCode::Char('D') => {
            if app.at_work_selected {
                app.reset_day_overrides()?;
            } else {
                app.force_delete_entry()?;
            }
            Ok(false)
        }
        KeyCode::Char('d') => {
            if app.at_work_selected {
                app.reset_day_overrides()?;
            } else {
                app.confirm_delete();
            }
            Ok(false)
        }
        KeyCode::Char('s') | KeyCode::Char('S') if key.modifiers.contains(KeyModifiers::SHIFT) => {
            app.open_settings();
            Ok(false)
        }
        KeyCode::Char('s') | KeyCode::Char('S') => {
            if app.at_work_selected {
                app.toggle_at_work_start()?;
            } else {
                app.stop_or_restart_entry()?;
            }
            Ok(false)
        }
        KeyCode::Char('l') | KeyCode::Char('L') if key.modifiers.contains(KeyModifiers::SHIFT) => {
            if !app.at_work_selected {
                let _ = app.toggle_logged();
            }
            Ok(false)
        }
        KeyCode::Char('l') | KeyCode::Char('L') => {
            if !app.at_work_selected {
                let _ = app.run_log_command();
            }
            Ok(false)
        }
        KeyCode::Char('o') | KeyCode::Char('O') => {
            if !app.at_work_selected {
                let _ = app.copy_time_to_clipboard();
                let _ = app.run_open_worklog_command();
            }
            Ok(false)
        }
        KeyCode::Char('f') | KeyCode::Char('F') => {
            app.start_creating_off_work();
            Ok(false)
        }
        KeyCode::Char('t') | KeyCode::Char('T') => {
            app.open_tasks();
            Ok(false)
        }
        KeyCode::Char('m') | KeyCode::Char('M') => {
            app.input_mode = InputMode::OperationsMenu { selected_index: 0 };
            Ok(false)
        }
        KeyCode::Char('w') | KeyCode::Char('W') => {
            app.input_mode = InputMode::WhatsNew;
            Ok(false)
        }
        KeyCode::Esc => {
            app.clear_status();
            Ok(false)
        }
        KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SHIFT)  => {
            app.move_entry_up()?;
            Ok(false)
        }
        KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SHIFT) => {
            app.move_entry_down()?;
            Ok(false)
        }
        // Modifier-free reorder fallback (Shift+Arrow doesn't report its modifier on
        // some terminals, e.g. macOS Terminal.app). Uppercase chars are sent reliably.
        KeyCode::Char('K') => {
            app.move_entry_up()?;
            Ok(false)
        }
        KeyCode::Char('J') => {
            app.move_entry_down()?;
            Ok(false)
        }
        KeyCode::Down => {
            app.select_next();
            Ok(false)
        }
        KeyCode::Up => {
            app.select_previous();
            Ok(false)
        }
        KeyCode::Left => {
            app.previous_day()?;
            Ok(false)
        }
        KeyCode::Right => {
            app.next_day()?;
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn handle_editing_mode(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Enter => {
            if let Err(_e) = app.save_entry() {
                // If there's an error parsing, just ignore it and stay in edit mode
            }
            Ok(false)
        }
        KeyCode::Up => {
            app.decrement_time();
            Ok(false)
        }
        KeyCode::Down => {
            app.increment_time();
            Ok(false)
        }
        KeyCode::Left => {
            app.cursor_left();
            Ok(false)
        }
        KeyCode::Right => {
            app.cursor_right();
            Ok(false)
        }
        KeyCode::Char(c) => {
            app.input_char(c);
            Ok(false)
        }
        KeyCode::Backspace => {
            app.delete_char();
            Ok(false)
        }
        KeyCode::Esc => {
            app.cancel_input();
            Ok(false)
        }
        KeyCode::Tab => {
            app.next_field();
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn handle_editing_day_mode(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Enter => {
            let _ = app.save_entry();
            Ok(false)
        }
        KeyCode::Up => {
            app.decrement_time();
            Ok(false)
        }
        KeyCode::Down => {
            app.increment_time();
            Ok(false)
        }
        KeyCode::Left => {
            app.cursor_left();
            Ok(false)
        }
        KeyCode::Right => {
            app.cursor_right();
            Ok(false)
        }
        KeyCode::Char(c) => {
            app.input_char(c);
            Ok(false)
        }
        KeyCode::Backspace => {
            app.delete_char();
            Ok(false)
        }
        KeyCode::Esc => {
            app.cancel_input();
            Ok(false)
        }
        KeyCode::Tab => {
            app.next_field();
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn handle_creating_mode(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Enter => {
            if let Err(_e) = app.save_entry() {
                // If there's an error parsing, just ignore it and stay in edit mode
            }
            Ok(false)
        }
        KeyCode::Up => {
            // Ctrl+Up navigates suggestions
            app.suggestion_previous();
            app.apply_selected_suggestion();
            Ok(false)
        }
        KeyCode::Down => {
            // Ctrl+Down navigates suggestions
            app.suggestion_next();
            app.apply_selected_suggestion();
            Ok(false)
        }
        KeyCode::Left => {
            app.cursor_left();
            Ok(false)
        }
        KeyCode::Right => {
            app.cursor_right();
            Ok(false)
        }
        KeyCode::Char(c) => {
            app.input_char(c);
            Ok(false)
        }
        KeyCode::Backspace => {
            app.delete_char();
            Ok(false)
        }
        KeyCode::Esc => {
            app.cancel_input();
            Ok(false)
        }
        KeyCode::Tab => {
            app.next_field();
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn handle_confirm_delete_mode(app: &mut App, key: KeyEvent, entry_id: i64) -> Result<bool> {
    match key.code {
        KeyCode::Char('y') | KeyCode::Char('Y') => {
            app.delete_entry(entry_id)?;
            Ok(false)
        }
        KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
            app.cancel_input();
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn handle_whats_new_mode(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Enter | KeyCode::Esc => {
            app.close_whats_new()?;
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn handle_operations_menu_mode(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Esc => {
            app.input_mode = InputMode::Normal;
            Ok(false)
        }
        KeyCode::Up => {
            if let InputMode::OperationsMenu { selected_index } = &mut app.input_mode {
                if *selected_index > 0 {
                    *selected_index -= 1;
                }
            }
            Ok(false)
        }
        KeyCode::Down => {
            if let InputMode::OperationsMenu { selected_index } = &mut app.input_mode {
                if *selected_index + 1 < OPERATIONS_MENU_ITEMS {
                    *selected_index += 1;
                }
            }
            Ok(false)
        }
        KeyCode::Enter => {
            if let InputMode::OperationsMenu { selected_index } = &app.input_mode {
                match *selected_index {
                    0 => {
                        app.sync_all_task_names();
                        app.input_mode = InputMode::Normal;
                    }
                    1 => {
                        let selected_day =
                            app.current_date.weekday().num_days_from_monday() as usize;
                        app.input_mode = InputMode::WeekSummary {
                            anchor: app.current_date,
                            selected_day,
                            selected_field: 0,
                            editing: None,
                        };
                    }
                    2 => {
                        app.input_mode = InputMode::Hotkeys;
                    }
                    3 => {
                        app.open_settings();
                    }
                    _ => {
                        app.input_mode = InputMode::Normal;
                    }
                }
            } else {
                app.input_mode = InputMode::Normal;
            }
            Ok(false)
        }
        _ => Ok(false),
    }
}