changxi 0.3.0

TUI EPUB Reader
use crate::app::{App, ViewMode};
use crate::core::search::SearchType;
use crate::progress::ProgressStore;
use crossterm::event::{KeyCode, KeyEvent};
use ratatui_image::picker::Picker;
use std::error::Error;

pub enum Action {
    None,
    Exit,
    SwitchBook(String),
}

pub fn handle_key_event(
    app: &mut App,
    key: KeyEvent,
    picker: &mut Picker,
) -> Result<Action, Box<dyn Error>> {
    match app.mode {
        ViewMode::Library => match key.code {
            KeyCode::Char('q') => return Ok(Action::Exit),
            KeyCode::Esc | KeyCode::Char('l') => app.mode = ViewMode::Reading,
            KeyCode::Enter => {
                if let Some(path) = app.select_book() {
                    return Ok(Action::SwitchBook(path));
                }
            }
            KeyCode::Down | KeyCode::Char('j') if !app.library_books.is_empty() => {
                let i = app.library_state.selected().unwrap_or(0);
                let new_index = (i + 1) % app.library_books.len();
                app.library_state.select(Some(new_index));
                app.update_library_cover(picker);
            }
            KeyCode::PageDown if !app.library_books.is_empty() => {
                let i = app.library_state.selected().unwrap_or(0);
                let new_index = (i + 5) % app.library_books.len();
                app.library_state.select(Some(new_index));
                app.update_library_cover(picker);
            }
            KeyCode::Up | KeyCode::Char('k') if !app.library_books.is_empty() => {
                let i = app.library_state.selected().unwrap_or(0);
                let new_index = (i + app.library_books.len() - 1) % app.library_books.len();
                app.library_state.select(Some(new_index));
                app.update_library_cover(picker);
            }
            KeyCode::PageUp if !app.library_books.is_empty() => {
                let i = app.library_state.selected().unwrap_or(0);
                let new_index = (i + app.library_books.len() - 5) % app.library_books.len();
                app.library_state.select(Some(new_index));
                app.update_library_cover(picker);
            }
            KeyCode::Char('d') => {
                let i = app.library_state.selected().unwrap_or(0);
                let mut store = ProgressStore::load(app.profile_dir.as_ref().map(|p| {
                    let mut p = p.clone();
                    p.push("progress.json");
                    p
                }));
                app.delete_book(i, &mut store);
                if !app.library_books.is_empty() {
                    if i < app.library_books.len() {
                        app.library_state.select(Some(i));
                        app.update_library_cover(picker);
                    } else {
                        app.library_state.select(Some(0));
                        app.update_library_cover(picker);
                    }
                }
            }
            KeyCode::Char('s') => app.next_sort_criteria(picker),
            KeyCode::Char('S') => app.toggle_sort(picker),
            _ => {}
        },
        ViewMode::BookmarkBrowser => match key.code {
            KeyCode::Char('q') => return Ok(Action::Exit),
            KeyCode::Esc | KeyCode::Char('b') => app.mode = ViewMode::Reading,
            KeyCode::Enter => app.jump_to_bookmark(),
            KeyCode::Down | KeyCode::Char('j') if !app.bookmarks.is_empty() => {
                let i = app.bookmark_list_state.selected().unwrap_or(0);
                let new_index = (i + 1) % app.bookmarks.len();
                app.bookmark_list_state.select(Some(new_index));
            }
            KeyCode::Down | KeyCode::Char('j') => {}
            KeyCode::Up | KeyCode::Char('k') if !app.bookmarks.is_empty() => {
                let i = app.bookmark_list_state.selected().unwrap_or(0);
                let new_index = (i + app.bookmarks.len() - 1) % app.bookmarks.len();
                app.bookmark_list_state.select(Some(new_index));
            }
            KeyCode::Up | KeyCode::Char('k') => {}
            KeyCode::Char('d') => app.remove_bookmark(),
            KeyCode::Char('r') => app.start_renaming_bookmark(),
            _ => {}
        },
        ViewMode::BookmarkRenaming => match key.code {
            KeyCode::Enter => app.finish_renaming_bookmark(),
            KeyCode::Esc => app.mode = ViewMode::BookmarkBrowser,
            KeyCode::Char(c) => {
                app.bookmark_rename_input
                    .insert(app.bookmark_rename_cursor_position, c);
                app.bookmark_rename_cursor_position += 1;
            }
            KeyCode::Backspace if app.bookmark_rename_cursor_position > 0 => {
                app.bookmark_rename_input
                    .remove(app.bookmark_rename_cursor_position - 1);
                app.bookmark_rename_cursor_position -= 1;
            }
            KeyCode::Delete
                if app.bookmark_rename_cursor_position < app.bookmark_rename_input.len() =>
            {
                app.bookmark_rename_input
                    .remove(app.bookmark_rename_cursor_position);
            }
            KeyCode::Left => {
                app.bookmark_rename_cursor_position =
                    app.bookmark_rename_cursor_position.saturating_sub(1);
            }
            KeyCode::Right => {
                app.bookmark_rename_cursor_position =
                    (app.bookmark_rename_cursor_position + 1).min(app.bookmark_rename_input.len());
            }
            KeyCode::Home => app.bookmark_rename_cursor_position = 0,
            KeyCode::End => app.bookmark_rename_cursor_position = app.bookmark_rename_input.len(),
            _ => {}
        },
        ViewMode::ChapterBrowser => match key.code {
            KeyCode::Char('q') => return Ok(Action::Exit),
            KeyCode::Esc | KeyCode::Tab => app.mode = ViewMode::Reading,
            KeyCode::Enter => {
                if let Some(selected) = app.chapter_list_state.selected() {
                    let visible_indices = app.get_visible_chapter_indices();
                    if let Some(&real_index) = visible_indices.get(selected) {
                        app.jump_to_chapter(real_index);
                    }
                }
            }
            KeyCode::Down | KeyCode::Char('j') => {
                let i = app.chapter_list_state.selected().unwrap_or(0);
                let visible_indices = app.get_visible_chapter_indices();
                if i + 1 < visible_indices.len() {
                    app.chapter_list_state.select(Some(i + 1));
                }
            }
            KeyCode::PageDown => {
                let i = app.chapter_list_state.selected().unwrap_or(0);
                let visible_indices = app.get_visible_chapter_indices();
                if i + 5 < visible_indices.len() {
                    app.chapter_list_state.select(Some(i + 5));
                } else {
                    app.chapter_list_state
                        .select(Some(visible_indices.len().saturating_sub(1)));
                }
            }
            KeyCode::Up | KeyCode::Char('k') => {
                let i = app.chapter_list_state.selected().unwrap_or(0);
                if i > 0 {
                    app.chapter_list_state.select(Some(i - 1));
                }
            }
            KeyCode::PageUp => {
                let i = app.chapter_list_state.selected().unwrap_or(0);
                if i > 5 {
                    app.chapter_list_state.select(Some(i - 5));
                } else {
                    app.chapter_list_state.select(Some(0));
                }
            }
            KeyCode::Right | KeyCode::Char('l') => {
                app.expand_chapter();
            }
            KeyCode::Left | KeyCode::Char('h') => {
                app.collapse_chapter();
            }
            _ => {}
        },
        ViewMode::Search => match key.code {
            KeyCode::Esc => app.mode = ViewMode::Reading,
            KeyCode::Char('c') if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) => {
                app.search_case_sensitive = !app.search_case_sensitive;
                app.perform_search();
            }
            KeyCode::Tab => {
                app.search_type = match app.search_type {
                    SearchType::Local => SearchType::Global,
                    SearchType::Global => SearchType::Bookmark,
                    SearchType::Bookmark => SearchType::Chapter,
                    SearchType::Chapter => SearchType::Local,
                };
                app.perform_search();
            }
            KeyCode::BackTab => {
                app.search_type = match app.search_type {
                    SearchType::Local => SearchType::Chapter,
                    SearchType::Global => SearchType::Local,
                    SearchType::Bookmark => SearchType::Global,
                    SearchType::Chapter => SearchType::Bookmark,
                };
                app.perform_search();
            }
            KeyCode::Enter => {
                if app.search_results.is_empty() {
                    app.perform_search();
                } else {
                    app.jump_to_search_result();
                }
            }
            KeyCode::Down | KeyCode::Char('j') if !app.search_results.is_empty() => {
                let i = app.search_list_state.selected().unwrap_or(0);
                let new_index = (i + 1) % app.search_results.len();
                app.search_list_state.select(Some(new_index));
            }
            KeyCode::Up | KeyCode::Char('k') if !app.search_results.is_empty() => {
                let i = app.search_list_state.selected().unwrap_or(0);
                let new_index = (i + app.search_results.len() - 1) % app.search_results.len();
                app.search_list_state.select(Some(new_index));
            }
            KeyCode::Char(c) => {
                app.search_query.insert(app.search_cursor_position, c);
                app.search_cursor_position += 1;
                app.perform_search();
            }
            KeyCode::Backspace if app.search_cursor_position > 0 => {
                app.search_query.remove(app.search_cursor_position - 1);
                app.search_cursor_position -= 1;
                app.perform_search();
            }
            KeyCode::Delete if app.search_cursor_position < app.search_query.len() => {
                app.search_query.remove(app.search_cursor_position);
                app.perform_search();
            }
            KeyCode::Left => {
                app.search_cursor_position = app.search_cursor_position.saturating_sub(1);
            }
            KeyCode::Right => {
                app.search_cursor_position =
                    (app.search_cursor_position + 1).min(app.search_query.len());
            }
            KeyCode::Home => app.search_cursor_position = 0,
            KeyCode::End => app.search_cursor_position = app.search_query.len(),
            _ => {}
        },
        ViewMode::Visual => match key.code {
            KeyCode::Esc | KeyCode::Char('q') => app.mode = ViewMode::Reading,
            KeyCode::Char('v') | KeyCode::Char(' ') => app.set_visual_anchor(),
            KeyCode::Char('m') | KeyCode::Enter => app.add_bookmark(),
            KeyCode::Left | KeyCode::Char('h') => app.move_visual_cursor(0, -1),
            KeyCode::Right | KeyCode::Char('l') => app.move_visual_cursor(0, 1),
            KeyCode::Up | KeyCode::Char('k') => app.move_visual_cursor(-1, 0),
            KeyCode::Down | KeyCode::Char('j') => app.move_visual_cursor(1, 0),
            KeyCode::Char('w') => app.move_visual_word(true),
            KeyCode::Char('b') => app.move_visual_word(false),
            KeyCode::PageUp => app.move_visual_cursor(-5, 0),
            KeyCode::PageDown => app.move_visual_cursor(5, 0),
            _ => {}
        },
        _ => match key.code {
            KeyCode::Char('q') => return Ok(Action::Exit),
            KeyCode::Char('/') => app.open_search(),
            KeyCode::Char('v') => app.enter_visual_mode(),
            KeyCode::Right => {
                if app.config.view_type == "book" {
                    app.next_page(2);
                } else {
                    app.next_chapter();
                }
            }
            KeyCode::Left => {
                if app.config.view_type == "book" {
                    app.prev_page(2);
                } else {
                    app.previous_chapter();
                }
            }
            KeyCode::Home => app.go_to_start(),
            KeyCode::End => app.go_to_end(),
            KeyCode::Down | KeyCode::Char('j') => {
                if app.config.view_type != "book" {
                    app.scroll_down(1);
                }
                if app.config.view_type == "book" {
                    app.next_page(1);
                }
            }
            KeyCode::PageDown => app.next_page(2),
            KeyCode::Up | KeyCode::Char('k') => {
                if app.config.view_type != "book" {
                    app.scroll_up(1);
                }
                if app.config.view_type == "book" {
                    app.prev_page(1);
                }
            }
            KeyCode::PageUp => app.prev_page(1),
            KeyCode::Char('c') => app.toggle_mode(),
            KeyCode::Char('V') => {
                app.config.view_type = match app.config.view_type.as_str() {
                    "default" => "book".to_string(),
                    "book" => "continuous".to_string(),
                    _ => "default".to_string(),
                };
            }
            KeyCode::Char('l') => app.open_library(picker),
            KeyCode::Char('b') => app.open_bookmark_browser(),
            KeyCode::Char('m') => app.add_bookmark(),
            KeyCode::Tab => app.open_chapter_browser(),
            KeyCode::Char('n') => app.next_search_result(),
            KeyCode::Char('N') => app.prev_search_result(),
            _ => {}
        },
    }
    Ok(Action::None)
}