trackWork 0.15.0

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

use crate::app::{App, InputMode};

/// Handle keyboard input for the passphrase unlock prompt.
pub fn handle_passphrase_prompt_input(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Enter => {
            let passphrase = if let InputMode::PassphrasePrompt { passphrase, .. } = &app.input_mode {
                passphrase.clone()
            } else {
                return Ok(false);
            };

            match app.secrets.unlock(&passphrase) {
                Ok(()) => {
                    app.input_mode = InputMode::Normal;
                    app.check_version()?;
                    Ok(false)
                }
                Err(_) => {
                    if let InputMode::PassphrasePrompt {
                        passphrase,
                        cursor_pos,
                        error_message,
                        confirm_delete,
                    } = &mut app.input_mode
                    {
                        *error_message = Some("Wrong passphrase".to_string());
                        passphrase.clear();
                        *cursor_pos = 0;
                        *confirm_delete = false;
                    }
                    Ok(false)
                }
            }
        }
        KeyCode::Esc => Ok(true), // Quit — can't proceed without passphrase
        KeyCode::Delete => {
            if let InputMode::PassphrasePrompt {
                confirm_delete,
                error_message,
                ..
            } = &mut app.input_mode
            {
                if *confirm_delete {
                    // Second press — actually delete
                    app.secrets.delete_secrets_file()?;
                    app.input_mode = InputMode::Normal;
                    app.check_version()?;
                } else {
                    // First press — ask for confirmation
                    *confirm_delete = true;
                    *error_message = Some("Press Delete again to confirm removal".to_string());
                }
            }
            Ok(false)
        }
        KeyCode::Char(c) => {
            if let InputMode::PassphrasePrompt {
                passphrase,
                cursor_pos,
                error_message,
                confirm_delete,
            } = &mut app.input_mode
            {
                *error_message = None;
                *confirm_delete = false;
                let byte_pos: usize = passphrase.chars().take(*cursor_pos).map(|ch| ch.len_utf8()).sum();
                passphrase.insert(byte_pos, c);
                *cursor_pos += 1;
            }
            Ok(false)
        }
        KeyCode::Backspace => {
            if let InputMode::PassphrasePrompt {
                passphrase,
                cursor_pos,
                confirm_delete,
                ..
            } = &mut app.input_mode
            {
                *confirm_delete = false;
                if *cursor_pos > 0 {
                    let char_idx = *cursor_pos - 1;
                    let byte_pos: usize = passphrase.chars().take(char_idx).map(|ch| ch.len_utf8()).sum();
                    let removed_len = passphrase.chars().nth(char_idx).map(|ch| ch.len_utf8()).unwrap_or(0);
                    passphrase.replace_range(byte_pos..byte_pos + removed_len, "");
                    *cursor_pos -= 1;
                }
            }
            Ok(false)
        }
        KeyCode::Left => {
            if let InputMode::PassphrasePrompt { cursor_pos, .. } = &mut app.input_mode {
                if *cursor_pos > 0 {
                    *cursor_pos -= 1;
                }
            }
            Ok(false)
        }
        KeyCode::Right => {
            if let InputMode::PassphrasePrompt { passphrase, cursor_pos, .. } = &mut app.input_mode {
                if *cursor_pos < passphrase.chars().count() {
                    *cursor_pos += 1;
                }
            }
            Ok(false)
        }
        _ => Ok(false),
    }
}

/// Handle keyboard input for the passphrase change/setup screen.
pub fn handle_passphrase_change_input(app: &mut App, key: KeyEvent) -> Result<bool> {
    match key.code {
        KeyCode::Tab => {
            app.next_field();
            Ok(false)
        }
        KeyCode::Up => {
            app.previous_field();
            Ok(false)
        }
        KeyCode::Down => {
            app.next_field();
            Ok(false)
        }
        KeyCode::Enter => {
            let (old_pass, new_pass, confirm_pass, is_initial) =
                if let InputMode::PassphraseChange {
                    old_passphrase,
                    new_passphrase,
                    confirm_passphrase,
                    is_initial_setup,
                    ..
                } = &app.input_mode
                {
                    (
                        old_passphrase.clone(),
                        new_passphrase.clone(),
                        confirm_passphrase.clone(),
                        *is_initial_setup,
                    )
                } else {
                    return Ok(false);
                };

            // Validate
            if new_pass.is_empty() {
                if let InputMode::PassphraseChange { error_message, .. } = &mut app.input_mode {
                    *error_message = Some("Passphrase cannot be empty".to_string());
                }
                return Ok(false);
            }
            if new_pass != confirm_pass {
                if let InputMode::PassphraseChange { error_message, .. } = &mut app.input_mode {
                    *error_message = Some("Passphrases do not match".to_string());
                }
                return Ok(false);
            }

            let result = if is_initial {
                app.secrets.set_passphrase(&new_pass)
            } else {
                app.secrets.change_passphrase(&old_pass, &new_pass)
            };

            match result {
                Ok(()) => {
                    // If there's a pending API token, save it now
                    if let Some(token) = app.pending_api_token.take() {
                        app.secrets.set("jira_api_token", &token)?;
                    }
                    app.input_mode = InputMode::Normal;
                    Ok(false)
                }
                Err(e) => {
                    if let InputMode::PassphraseChange { error_message, .. } = &mut app.input_mode {
                        *error_message = Some(e.to_string());
                    }
                    Ok(false)
                }
            }
        }
        KeyCode::Esc => {
            // Cancel — discard pending token
            app.pending_api_token = None;
            app.input_mode = InputMode::Normal;
            Ok(false)
        }
        KeyCode::Char(c) => {
            passphrase_change_insert_char(app, c);
            Ok(false)
        }
        KeyCode::Backspace => {
            passphrase_change_delete_char(app);
            Ok(false)
        }
        KeyCode::Left => {
            if let InputMode::PassphraseChange { cursor_pos, .. } = &mut app.input_mode {
                if *cursor_pos > 0 {
                    *cursor_pos -= 1;
                }
            }
            Ok(false)
        }
        KeyCode::Right => {
            if let InputMode::PassphraseChange {
                old_passphrase,
                new_passphrase,
                confirm_passphrase,
                current_field,
                cursor_pos,
                is_initial_setup,
                ..
            } = &mut app.input_mode
            {
                let text = get_passphrase_change_field(
                    old_passphrase, new_passphrase, confirm_passphrase,
                    *current_field, *is_initial_setup,
                );
                if *cursor_pos < text.chars().count() {
                    *cursor_pos += 1;
                }
            }
            Ok(false)
        }
        _ => Ok(false),
    }
}

fn get_passphrase_change_field<'a>(
    old: &'a str, new: &'a str, confirm: &'a str,
    current_field: usize, is_initial_setup: bool,
) -> &'a str {
    if is_initial_setup {
        match current_field {
            0 => new,
            1 => confirm,
            _ => "",
        }
    } else {
        match current_field {
            0 => old,
            1 => new,
            2 => confirm,
            _ => "",
        }
    }
}

fn passphrase_change_insert_char(app: &mut App, c: char) {
    if let InputMode::PassphraseChange {
        old_passphrase,
        new_passphrase,
        confirm_passphrase,
        current_field,
        cursor_pos,
        is_initial_setup,
        error_message,
    } = &mut app.input_mode
    {
        *error_message = None;
        let text = if *is_initial_setup {
            match *current_field {
                0 => new_passphrase,
                1 => confirm_passphrase,
                _ => return,
            }
        } else {
            match *current_field {
                0 => old_passphrase,
                1 => new_passphrase,
                2 => confirm_passphrase,
                _ => return,
            }
        };
        let byte_pos: usize = text.chars().take(*cursor_pos).map(|ch| ch.len_utf8()).sum();
        text.insert(byte_pos, c);
        *cursor_pos += 1;
    }
}

fn passphrase_change_delete_char(app: &mut App) {
    if let InputMode::PassphraseChange {
        old_passphrase,
        new_passphrase,
        confirm_passphrase,
        current_field,
        cursor_pos,
        is_initial_setup,
        ..
    } = &mut app.input_mode
    {
        if *cursor_pos == 0 {
            return;
        }
        let text = if *is_initial_setup {
            match *current_field {
                0 => new_passphrase,
                1 => confirm_passphrase,
                _ => return,
            }
        } else {
            match *current_field {
                0 => old_passphrase,
                1 => new_passphrase,
                2 => confirm_passphrase,
                _ => return,
            }
        };
        let char_idx = *cursor_pos - 1;
        let byte_pos: usize = text.chars().take(char_idx).map(|ch| ch.len_utf8()).sum();
        let removed_len = text.chars().nth(char_idx).map(|ch| ch.len_utf8()).unwrap_or(0);
        text.replace_range(byte_pos..byte_pos + removed_len, "");
        *cursor_pos -= 1;
    }
}