// SPDX-License-Identifier: GPL-3.0-or-later
/*
File: src/tui/handlers.rs
Handles keyboard input and system events for the TUI.
This file is a near-complete copy of the application's TUI input handler,
with additions for quick session logging (t) and session management (T).
It also avoids deprecated/removed chrono helpers by using NaiveDateTime::from_timestamp_opt
and DateTime::<Utc>::from_utc(...) to construct timezone-aware values.
*/

use crate::config::Config;
use crate::model::parser::{extract_inline_aliases, validate_alias_integrity};
use crate::model::{AppIntent, Task, TaskStatus};
use crate::storage::LOCAL_CALENDAR_HREF;
use crate::system::SystemEvent;
use crate::tui::action::{Action, AppEvent, SidebarMode};
use crate::tui::state::{AppState, Focus, InputMode};
use chrono::NaiveTime;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use std::collections::HashMap;
use tokio::sync::mpsc::Sender;

// Weighted-random helper from the shared store
use crate::store::{TaskListItem, select_weighted_random_index};
use rust_i18n::t;

fn get_available_actions(state: &AppState, task: &Task) -> Vec<crate::config::TaskAction> {
    use crate::config::TaskAction;
    let mut actions = Vec::new();
    let is_done_or_cancelled =
        task.status.is_done() || task.status == crate::model::TaskStatus::Cancelled;
    let is_paused = task.is_paused();

    let has_relationships = !task.dependencies.is_empty()
        || !task.related_to.is_empty()
        || task.has_blocking_tasks
        || task.has_related_tasks
        || task.parent_uid.is_some();

    for &action in TaskAction::ALL {
        let available = match action {
            TaskAction::Move => {
                let count = state
                    .calendars
                    .iter()
                    .filter(|c| !state.disabled_calendars.contains(&c.href))
                    .count();
                count > 1
            }
            TaskAction::OpenUrl => task.url.is_some(),
            TaskAction::DeleteTree => task.has_subtasks,
            TaskAction::OpenCoordinates => task.geo.is_some(),
            TaskAction::OpenLocations => state.store.count_tree_locations(&task.uid) > 1,
            TaskAction::ToggleDetails => has_relationships,
            TaskAction::CompleteAndShift => {
                task.rrule.is_some() && !is_done_or_cancelled && !task.is_relative_recurrence()
            }
            TaskAction::ExtractSubtasks => task.has_extractable_subtasks(),
            TaskAction::TogglePin => true,
            TaskAction::Promote => task.parent_uid.is_some(),
            TaskAction::Yank => state.yanked_uid.is_none(),
            TaskAction::StopTimer => {
                task.status == crate::model::TaskStatus::InProcess || is_paused
            }
            TaskAction::ToggleTimer | TaskAction::AddSession | TaskAction::Cancel => {
                !is_done_or_cancelled
            }
            _ => true,
        };
        if available {
            actions.push(action);
        }
    }
    actions
}

fn update_action_menu_filter(state: &mut AppState) {
    let filter = state.action_filter.to_lowercase();
    state.action_menu_items = state
        .available_actions
        .iter()
        .copied()
        .filter(|a| {
            if filter.is_empty() {
                return true;
            }
            let label = a.label().to_lowercase();
            use crate::config::TaskAction::*;
            let matches_alias = match a {
                ToggleDetails => filter == "l" || filter == "rel",
                CompleteAndShift => filter == "r" || filter == "rep" || filter == "repeat",
                ToggleTimer => filter == "s" || filter == "start" || filter == "pause",
                StopTimer => filter == "stop",
                AddSession => filter == "t" || filter == "log",
                IncreasePriority => filter == "+" || filter == "up",
                DecreasePriority => filter == "-" || filter == "down",
                Edit => filter == "e",
                Yank => filter == "y" || filter == "copy",
                ExtractSubtasks => filter == "extract" || filter == "parse",
                TogglePin => filter == "p" || filter == "pin",
                CreateSubtask => filter == "c" || filter == "sub",
                DuplicateTree => filter == "d" || filter == "dup",
                Promote => filter == "<" || filter == "outdent",
                Move => filter == "m",
                Cancel => filter == "x",
                Delete | DeleteTree => filter == "del" || filter == "rm",
                OpenUrl => filter == "o" || filter == "url" || filter == "link",
                OpenCoordinates | OpenLocations => filter == "g" || filter == "map",
            };
            label.contains(&filter) || matches_alias
        })
        .collect();
    state.action_selection_state.select(Some(0));
}

fn open_action_menu(state: &mut AppState) {
    if let Some(task) = state.get_selected_task() {
        let actions = get_available_actions(state, task);
        if !actions.is_empty() {
            state.available_actions = actions.clone();
            state.action_menu_items = actions;
            state.action_selection_state.select(Some(0));
            state.action_filter.clear();
            state.mode = InputMode::ActionMenu;
            state.message = format!(" {} ", rust_i18n::t!("actions"));
        }
    }
}

async fn execute_task_action(
    state: &mut AppState,
    action: crate::config::TaskAction,
    task: &Task,
    action_tx: &Sender<Action>,
) {
    use crate::config::TaskAction::*;
    let uid = task.uid.clone();
    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
    let mut intent = None;

    match action {
        OpenUrl => {
            if let Some(url) = &task.url {
                #[cfg(not(target_os = "android"))]
                {
                    let target_url = url.clone();
                    std::thread::spawn(move || {
                        #[cfg(target_os = "linux")]
                        let _ = std::process::Command::new("xdg-open")
                            .arg(target_url)
                            .spawn();
                        #[cfg(target_os = "windows")]
                        let _ = std::process::Command::new("explorer")
                            .arg(target_url)
                            .spawn();
                        #[cfg(target_os = "macos")]
                        let _ = std::process::Command::new("open").arg(target_url).spawn();
                    });
                }
                state.message = rust_i18n::t!("open_url").to_string();
            }
        }
        OpenCoordinates => {
            if let Some(geo) = &task.geo {
                #[cfg(not(target_os = "android"))]
                {
                    let target_url = format!("geo:{}", geo);
                    std::thread::spawn(move || {
                        #[cfg(target_os = "linux")]
                        let _ = std::process::Command::new("xdg-open")
                            .arg(target_url)
                            .spawn();
                        #[cfg(target_os = "windows")]
                        let _ = std::process::Command::new("explorer")
                            .arg(target_url)
                            .spawn();
                        #[cfg(target_os = "macos")]
                        let _ = std::process::Command::new("open").arg(target_url).spawn();
                    });
                }
                state.message = rust_i18n::t!("open_coordinates").to_string();
            }
        }
        OpenLocations => {
            let waypoints = state.store.get_tree_waypoints(&uid);
            let mut gpx_string = String::from(
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpx version=\"1.1\" creator=\"Cfait\" xmlns=\"http://www.topografix.com/GPX/1/1\">\n",
            );
            for (name, geo) in waypoints {
                let parts: Vec<&str> = geo.split(',').collect();
                if parts.len() >= 2 {
                    let escaped_name = name
                        .replace('&', "&amp;")
                        .replace('<', "&lt;")
                        .replace('>', "&gt;");
                    gpx_string.push_str(&format!(
                        "  <wpt lat=\"{}\" lon=\"{}\"><name>{}</name></wpt>\n",
                        parts[0].trim(),
                        parts[1].trim(),
                        escaped_name
                    ));
                }
            }
            gpx_string.push_str("</gpx>");

            if let Ok(cache_dir) = state.ctx.get_cache_dir() {
                let path = cache_dir.join(format!("locations_{}.gpx", uuid::Uuid::new_v4()));
                if std::fs::write(&path, gpx_string).is_ok() {
                    #[cfg(not(target_os = "android"))]
                    {
                        let target = path.to_string_lossy().to_string();
                        std::thread::spawn(move || {
                            #[cfg(target_os = "linux")]
                            let _ = std::process::Command::new("xdg-open").arg(target).spawn();
                            #[cfg(target_os = "windows")]
                            let _ = std::process::Command::new("explorer").arg(target).spawn();
                            #[cfg(target_os = "macos")]
                            let _ = std::process::Command::new("open").arg(target).spawn();
                        });
                    }
                    state.message = rust_i18n::t!("action_open_locations").to_string();
                } else {
                    state.message = rust_i18n::t!("error_write_gpx").to_string();
                }
            }
        }
        ToggleDetails => {
            let mut items = Vec::new();
            if let Some(p_uid) = &task.parent_uid {
                let name = state
                    .store
                    .get_summary(p_uid)
                    .unwrap_or_else(|| "Unknown task".to_string());
                items.push((
                    p_uid.clone(),
                    format!("↑ [Parent] {}", name),
                    "parent".to_string(),
                ));
            }
            for dep_uid in &task.dependencies {
                let name = state
                    .store
                    .get_summary(dep_uid)
                    .unwrap_or_else(|| "Unknown task".to_string());
                let is_done = state.store.is_task_done(dep_uid).unwrap_or(false);
                let check = if is_done { "[x]" } else { "[ ]" };
                items.push((
                    dep_uid.clone(),
                    format!("⬆ [Blocked by] {} {}", check, name),
                    "dependency".to_string(),
                ));
            }
            for related_uid in &task.related_to {
                let name = state
                    .store
                    .get_summary(related_uid)
                    .unwrap_or_else(|| "Unknown task".to_string());
                items.push((
                    related_uid.clone(),
                    format!("→ [Related to] {}", name),
                    "related_to".to_string(),
                ));
            }
            let incoming_related = state.store.get_tasks_related_to(&task.uid);
            for (related_uid, related_name) in incoming_related {
                items.push((
                    related_uid.clone(),
                    format!("← [Related from] {}", related_name),
                    "related_from".to_string(),
                ));
            }
            let blocking_tasks = state.store.get_tasks_blocking(&task.uid);
            for (blocking_uid, blocking_name) in blocking_tasks {
                items.push((
                    blocking_uid.clone(),
                    format!("⬇ [Blocking] {}", blocking_name),
                    "blocking".to_string(),
                ));
            }
            if !items.is_empty() {
                state.relationship_items = items;
                state.relationship_selection_state.select(Some(0));
                state.mode = InputMode::RelationshipBrowsing;
                state.message =
                    format!("{} (Del/x: Remove)", rust_i18n::t!("tui_select_task_jump"));
            } else {
                state.message = rust_i18n::t!("error_no_related_tasks").to_string();
            }
        }
        CompleteAndShift => {
            intent = Some(AppIntent::ToggleTaskShift { uid });
        }
        ToggleTimer => {
            if task.status == crate::model::TaskStatus::InProcess {
                intent = Some(AppIntent::PauseTask { uid });
            } else {
                intent = Some(AppIntent::StartTask { uid });
            }
        }
        StopTimer => {
            intent = Some(AppIntent::StopTask { uid });
        }
        AddSession => {
            state.mode = InputMode::AddingSession;
            state.reset_input();
            state.message = format!(
                "{} ({} 30m, yesterday 1h):",
                rust_i18n::t!("tui_log_time_prompt", name = task.summary.clone()),
                rust_i18n::t!("eg")
            );
        }
        IncreasePriority => {
            intent = Some(AppIntent::ChangePriority { uid, delta: 1 });
        }
        DecreasePriority => {
            intent = Some(AppIntent::ChangePriority { uid, delta: -1 });
        }
        Edit => {
            let smart_string = task.to_smart_string();
            state.input_buffer = smart_string;
            state.cursor_position = state.input_buffer.chars().count();
            state.editing_uid = Some(uid);
            state.mode = InputMode::Editing;
        }
        Yank => {
            let summary = task.summary.clone();
            let text = if task.description.is_empty() {
                task.to_smart_string()
            } else {
                format!("{}\n\n{}", task.to_smart_string(), task.description)
            };
            state.yanked_uid = Some(uid);
            use base64::Engine;
            let b64 = base64::engine::general_purpose::STANDARD.encode(text);
            print!("\x1b]52;c;{}\x07", b64);
            use std::io::Write;
            let _ = std::io::stdout().flush();
            state.message = rust_i18n::t!("yanked_and_copied", summary = summary).to_string();
        }
        TogglePin => {
            intent = Some(AppIntent::TogglePin { uid });
        }
        ExtractSubtasks => {
            if let Some((parent, _)) = state.store.get_task_mut(&uid) {
                let desc_text = parent.description.clone();
                let (clean_desc, extracted) =
                    crate::model::extractor::extract_markdown_tasks(&desc_text);

                if !extracted.is_empty() {
                    parent.description = clean_desc;
                    parent.sequence += 1;
                    let parent_copy = parent.clone();
                    let target_href = parent_copy.calendar_href.clone();

                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let def_time =
                        chrono::NaiveTime::parse_from_str(&config.default_reminder_time, "%H:%M")
                            .ok();

                    let mut actions = vec![crate::journal::Action::Update(parent_copy)];

                    for ext in extracted {
                        let mut sub = Task::new(&ext.raw_text, &state.tag_aliases, def_time);
                        sub.uid = ext.uid;
                        if !ext.description.is_empty() {
                            if sub.description.is_empty() {
                                sub.description = ext.description;
                            } else {
                                sub.description
                                    .push_str(&format!("\n\n{}", ext.description));
                            }
                        }
                        if ext.is_completed {
                            sub.status = crate::model::TaskStatus::Completed;
                            sub.set_completion_date(Some(chrono::Utc::now()));
                        }
                        sub.parent_uid = Some(ext.parent_uid.unwrap_or(uid.clone()));
                        sub.dependencies = ext.dependencies;
                        sub.calendar_href = target_href.clone();

                        state.store.add_task(sub.clone());
                        actions.push(crate::journal::Action::Create(sub));
                    }

                    state.refresh_filtered_view();
                    tokio::spawn({
                        let tx = action_tx.clone();
                        async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        }
                    });
                }
            }
        }
        CreateSubtask => {
            let mut initial_input = String::new();
            for cat in &task.categories {
                initial_input.push_str(&format!("#{} ", crate::model::parser::quote_value(cat)));
            }
            if let Some(loc) = &task.location {
                initial_input.push_str(&format!("@@{} ", crate::model::parser::quote_value(loc)));
            }
            state.input_buffer = initial_input;
            state.cursor_position = state.input_buffer.chars().count();
            state.mode = InputMode::Creating;
            state.creating_with_desc = false;
            state.new_task_title.clear();
            state.creating_child_of = Some(uid);
            state.message = rust_i18n::t!("new_child_of", name = task.summary.clone()).to_string();
        }
        DuplicateTree => {
            intent = Some(AppIntent::DuplicateTaskTree { uid });
        }
        Promote => {
            intent = Some(AppIntent::RemoveParent { uid });
        }
        Move => {
            let current_href = task.calendar_href.clone();
            state.move_targets = state
                .calendars
                .iter()
                .filter(|c| {
                    c.href != current_href
                        && !state.disabled_calendars.contains(&c.href)
                        && c.href != crate::storage::LOCAL_TRASH_HREF
                        && c.href != "local://recovery"
                })
                .cloned()
                .collect();
            if !state.move_targets.is_empty() {
                state.move_selection_state.select(Some(0));
                state.mode = InputMode::Moving;
                state.message = rust_i18n::t!("tui_select_calendar_prompt").to_string();
            }
        }
        Cancel => {
            intent = Some(AppIntent::CancelTask { uid });
        }
        Delete => {
            intent = Some(AppIntent::DeleteTask { uid });
        }
        DeleteTree => {
            intent = Some(AppIntent::DeleteTaskTree { uid });
        }
    }

    if let Some(i) = intent {
        let actions = state.store.apply_task_intent(&i, &config);
        state.refresh_filtered_view();
        if !actions.is_empty() {
            let tx = action_tx.clone();
            tokio::spawn(async move {
                let _ = tx.send(Action::PersistBatch(actions)).await;
            });
        }
    }
}

fn run_external_editor(
    initial_content: &str,
    ctx: &dyn crate::context::AppContext,
) -> Result<Option<String>, String> {
    let config = Config::load(ctx).unwrap_or_default();
    let mut editor_cmd = None;
    if config.description_editor == "builtin" {
        return Ok(None); // Fallback to builtin
    } else if !config.description_editor.is_empty() {
        editor_cmd = Some(config.description_editor.clone());
    } else {
        if let Ok(v) = std::env::var("VISUAL")
            && !v.is_empty()
        {
            editor_cmd = Some(v);
        }
        if editor_cmd.is_none()
            && let Ok(e) = std::env::var("EDITOR")
            && !e.is_empty()
        {
            editor_cmd = Some(e);
        }
    }

    if let Some(cmd) = editor_cmd {
        let uuid = uuid::Uuid::new_v4();
        let path = std::env::temp_dir().join(format!("cfait_desc_{}.md", uuid));
        if std::fs::write(&path, initial_content.as_bytes()).is_ok() {
            let _ =
                crossterm::execute!(std::io::stdout(), crossterm::terminal::LeaveAlternateScreen);
            let _ = crossterm::terminal::disable_raw_mode();

            #[cfg(target_os = "windows")]
            let status = std::process::Command::new("cmd")
                .arg("/C")
                .arg(format!("{} \"{}\"", cmd, path.display()))
                .status();

            #[cfg(not(target_os = "windows"))]
            let status = std::process::Command::new("sh")
                .arg("-c")
                .arg(format!("{} \"{}\"", cmd, path.display()))
                .status();

            let _ = crossterm::terminal::enable_raw_mode();
            let _ = crossterm::execute!(
                std::io::stdout(),
                crossterm::terminal::EnterAlternateScreen,
                crossterm::event::EnableMouseCapture,
                crossterm::terminal::Clear(crossterm::terminal::ClearType::All)
            );

            let success = status.map(|s| s.success()).unwrap_or(false);

            if success {
                if let Ok(new_desc) = std::fs::read_to_string(&path) {
                    let _ = std::fs::remove_file(&path);
                    return Ok(Some(new_desc.trim_end().to_string()));
                }
            } else {
                let _ = std::fs::remove_file(&path);
                return Err(format!(
                    "Failed to run editor '{}', falling back to builtin.",
                    cmd
                ));
            }
        } else {
            return Err("Failed to create temp file for external editor.".to_string());
        }
    }

    Ok(None)
}

fn save_description(state: &mut AppState, action_tx: &Sender<Action>) {
    if state.creating_with_desc {
        let desc_text = state.input_buffer.clone();
        let (clean_desc, extracted) = crate::model::extractor::extract_markdown_tasks(&desc_text);

        let (clean_input, new_aliases) =
            crate::model::parser::extract_inline_aliases(&state.new_task_title);

        if !new_aliases.is_empty() {
            for (k, v) in &new_aliases {
                state.tag_aliases.insert(k.clone(), v.clone());
            }
        }

        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
        let def_time =
            chrono::NaiveTime::parse_from_str(&config.default_reminder_time, "%H:%M").ok();

        let mut parent = Task::new(&clean_input, &state.tag_aliases, def_time);
        if !clean_desc.is_empty() {
            if parent.description.is_empty() {
                parent.description = clean_desc;
            } else {
                parent.description.push_str(&format!("\n\n{}", clean_desc));
            }
        }
        parent.parent_uid = state.creating_child_of.clone();

        let Some(target_href) = state
            .active_cal_href
            .clone()
            .filter(|href| state.local_mode_enabled || !href.starts_with("local://"))
            .or_else(|| {
                state
                    .get_filtered_calendars()
                    .first()
                    .map(|c| c.href.clone())
            })
        else {
            state.message = if state.local_mode_enabled {
                rust_i18n::t!("error_no_calendar_available").to_string()
            } else {
                rust_i18n::t!("error_no_remote_calendar").to_string()
            };
            state.creating_with_desc = false;
            state.new_task_title.clear();
            state.mode = InputMode::Normal;
            state.reset_input();
            state.creating_child_of = None;
            return;
        };
        parent.calendar_href = target_href.clone();

        let parent_uid = parent.uid.clone();
        state.store.add_task(parent.clone());

        tokio::spawn({
            let tx = action_tx.clone();
            async move {
                let _ = tx
                    .send(Action::PersistBatch(vec![crate::journal::Action::Create(
                        parent,
                    )]))
                    .await;
            }
        });

        for ext in extracted {
            let mut sub = Task::new(&ext.raw_text, &state.tag_aliases, def_time);
            sub.uid = ext.uid;
            if !ext.description.is_empty() {
                if sub.description.is_empty() {
                    sub.description = ext.description;
                } else {
                    sub.description
                        .push_str(&format!("\n\n{}", ext.description));
                }
            }
            if ext.is_completed {
                sub.status = crate::model::TaskStatus::Completed;
                sub.set_completion_date(Some(chrono::Utc::now()));
            }
            sub.parent_uid = Some(ext.parent_uid.unwrap_or(parent_uid.clone()));
            sub.dependencies = ext.dependencies;
            sub.calendar_href = target_href.clone();

            state.store.add_task(sub.clone());
            tokio::spawn({
                let tx = action_tx.clone();
                async move {
                    let _ = tx
                        .send(Action::PersistBatch(vec![crate::journal::Action::Create(
                            sub,
                        )]))
                        .await;
                }
            });
        }

        state.refresh_filtered_view();
        update_alarms(state);
        if let Some(idx) = state.find_task_index_by_uid(&parent_uid) {
            state.list_state.select(Some(idx));
        }

        state.creating_with_desc = false;
        state.new_task_title.clear();
        state.mode = InputMode::Normal;
        state.reset_input();
        state.creating_child_of = None;
    } else {
        // Standard Edit
        let target_uid: Option<String> = state.editing_uid.clone();

        if let Some(uid) = target_uid
            && let Some((t, _)) = state.store.get_task_mut(&uid)
        {
            t.description = state.input_buffer.clone();
            t.sequence += 1;
            let clone = t.clone();
            state.refresh_filtered_view();
            state.mode = InputMode::Normal;
            state.reset_input();
            let _ = action_tx.try_send(Action::PersistBatch(vec![crate::journal::Action::Update(
                clone,
            )]));
        }
        state.mode = InputMode::Normal;
        state.reset_input();
    }
}

pub fn handle_app_event(state: &mut AppState, event: AppEvent, default_cal: &Option<String>) {
    // Ensure the [UNSYNCED] badge reflects the current journal state on every event
    state.unsynced_changes = !crate::journal::Journal::load(state.ctx.as_ref()).is_empty();
    match event {
        AppEvent::Status { key: _, human } => state.message = human,
        AppEvent::Error(s) => {
            state.message = format!("Error: {}", s);
            state.loading = false;
        }
        AppEvent::CalendarsLoaded(mut cals) => {
            if !state.local_mode_enabled {
                cals.retain(|c| !c.href.starts_with("local://"));
            }
            cals.sort_by_key(|c| {
                if c.href == "local://recovery" {
                    1
                } else if c.href == crate::storage::LOCAL_TRASH_HREF {
                    2
                } else {
                    0
                }
            });
            state.calendars = cals;

            if let Some(def) = default_cal
                && let Some(found) = state
                    .calendars
                    .iter()
                    .find(|c| c.name == *def || c.href == *def)
            {
                if state.hidden_calendars.contains(&found.href) {
                    state.hidden_calendars.remove(&found.href);
                }
                state.active_cal_href = Some(found.href.clone());
            }

            if state.active_cal_href.is_none() {
                state.active_cal_href = state
                    .calendars
                    .iter()
                    .find(|c| !state.disabled_calendars.contains(&c.href))
                    .map(|c| c.href.clone())
                    .or_else(|| {
                        if state.local_mode_enabled {
                            Some(LOCAL_CALENDAR_HREF.to_string())
                        } else {
                            None
                        }
                    });
            }
            state.refresh_filtered_view();
        }
        AppEvent::TasksLoaded(results) => {
            for (href, tasks) in results {
                if !state.local_mode_enabled && href.starts_with("local://") {
                    continue;
                }
                state.store.insert(href, tasks);
            }
            state.refresh_filtered_view();
            state.loading = false;
        }
        AppEvent::TaskSynced {
            uid,
            href,
            etag,
            sequence,
        } => {
            if let Some((existing, _)) = state.store.get_task_mut(&uid) {
                existing.href = href;
                existing.etag = etag;
                if sequence > existing.sequence {
                    existing.sequence = sequence;
                }
            }
        }
        AppEvent::ConfigUpdated(cfg) => {
            state.tag_aliases = cfg.tag_aliases.clone();
            state.hide_completed = cfg.hide_completed;
            state.hide_fully_completed_tags = cfg.hide_fully_completed_tags;
            state.hide_aliases_in_sidebar = cfg.hide_aliases_in_sidebar;
            state.sort_cutoff_months = cfg.sort_cutoff_months;
            state.sort_standard_by_priority = cfg.sort_standard_by_priority;
            state.urgent_days = cfg.urgent_days_horizon;
            state.urgent_prio = cfg.urgent_priority_threshold;
            state.default_priority = cfg.default_priority;
            state.start_grace_period_days = cfg.start_grace_period_days;
            state.snooze_short_mins = cfg.snooze_short_mins;
            state.snooze_long_mins = cfg.snooze_long_mins;
            state.show_priority_numbers = cfg.show_priority_numbers;
            state.quick_filter_term = cfg.quick_filter_term.clone();
            state.quick_filter_icon = cfg.quick_filter_icon.clone();
            state.show_quick_filter = cfg.show_quick_filter;
            state.theme = cfg.theme;
            state.refresh_filtered_view();
        }
    }

    // Load priority rendering toggle directly from config when events arrive
    if let Ok(cfg) = Config::load(state.ctx.as_ref()) {
        state.show_priority_numbers = cfg.show_priority_numbers;
        state.quick_filter_term = cfg.quick_filter_term.clone();
        state.quick_filter_icon = cfg.quick_filter_icon.clone();
        state.show_quick_filter = cfg.show_quick_filter;
    }
}

// Helper to notify the alarm system of local changes immediately
fn update_alarms(state: &AppState) {
    if let Some(tx) = &state.alarm_actor_tx {
        let all = state
            .store
            .calendars
            .values()
            .flat_map(|m| m.values())
            .cloned()
            .collect();
        let _ = tx.try_send(SystemEvent::UpdateTasks(all));
    }
}

pub async fn handle_key_event(
    key: KeyEvent,
    state: &mut AppState,
    action_tx: &Sender<Action>,
) -> Option<Action> {
    // --- ALARM INTERCEPTION ---
    if let Some((task, alarm_uid)) = state.active_alarm.clone() {
        // If in Snoozing mode, let the input handler deal with it
        if state.mode == InputMode::Snoozing {
            // Fall through to normal input handling below
        } else {
            match key.code {
                KeyCode::Delete | KeyCode::Char('D') | KeyCode::Char('d') => {
                    if let Some((t, _)) = state.store.get_task_mut(&task.uid)
                        && t.handle_dismiss(&alarm_uid)
                    {
                        let uid = t.uid.clone();
                        // Update UI
                        state.active_alarm = None;
                        state.refresh_filtered_view();
                        // Push update via centralized logic
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::ToggleTask { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        // Push update to alarm actor
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
                KeyCode::Char('1') => {
                    // Snooze short preset
                    if let Some((t, _)) = state.store.get_task_mut(&task.uid)
                        && t.handle_snooze(&alarm_uid, state.snooze_short_mins)
                    {
                        let uid = t.uid.clone();
                        state.active_alarm = None;
                        state.refresh_filtered_view();
                        // Push update via centralized logic
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::ToggleTask { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
                KeyCode::Char('2') => {
                    // Snooze long preset
                    if let Some((t, _)) = state.store.get_task_mut(&task.uid)
                        && t.handle_snooze(&alarm_uid, state.snooze_long_mins)
                    {
                        let uid = t.uid.clone();
                        state.active_alarm = None;
                        state.refresh_filtered_view();
                        // Push update via centralized logic
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::ToggleTask { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
                KeyCode::Char('c') => {
                    /* Complete */
                    if let Some((t, _)) = state.store.get_task_mut(&task.uid) {
                        // Dismiss alarm first (optional but clean)
                        t.dismiss_alarm(&alarm_uid);
                        let uid = t.uid.clone();
                        // Close popup
                        state.active_alarm = None;
                        // Compute changes via centralized logic
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::ToggleTask { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        state.refresh_filtered_view();
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
                KeyCode::Char('x') => {
                    /* Cancel */
                    if let Some((t, _)) = state.store.get_task_mut(&task.uid) {
                        t.dismiss_alarm(&alarm_uid);
                        let uid = t.uid.clone();
                        state.active_alarm = None;

                        // Apply cancel logic via centralized logic
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::CancelTask { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        state.refresh_filtered_view();
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
                KeyCode::Char('S') | KeyCode::Char('s') => {
                    // Enter custom snooze mode
                    state.mode = InputMode::Snoozing;
                    state.reset_input();
                    return None;
                }
                _ => return None, // Block other input while alarm is ringing
            }
        }
    }
    // --------------------------
    // --- SANITY CHECK ---
    // Prevent out-of-bounds panics if cursor drift happened
    let char_count = state.input_buffer.chars().count();
    if state.cursor_position > char_count {
        state.cursor_position = char_count;
    }

    match state.mode {
        InputMode::Creating => match key.code {
            // NEW: Enter description mode during creation (Ctrl+E)
            KeyCode::Char('e') | KeyCode::Char('E')
                if key.modifiers.contains(KeyModifiers::CONTROL) =>
            {
                state.new_task_title = state.input_buffer.clone();
                state.input_buffer.clear();
                state.cursor_position = 0;
                state.mode = InputMode::EditingDescription;
                state.creating_with_desc = true;
                state.message = rust_i18n::t!("edit_description_instructions").to_string();
                return None;
            }
            KeyCode::Enter if !state.input_buffer.is_empty() => {
                if state.creating_with_desc {
                    state.new_task_title = state.input_buffer.clone();
                    state.input_buffer.clear();
                    state.cursor_position = 0;

                    match run_external_editor("", state.ctx.as_ref()) {
                        Ok(Some(new_desc)) => {
                            state.input_buffer = new_desc;
                            save_description(state, action_tx);
                            state.needs_redraw = true;
                            return None;
                        }
                        Ok(None) => {
                            state.mode = InputMode::EditingDescription;
                            state.message =
                                rust_i18n::t!("edit_description_instructions").to_string();
                            return None;
                        }
                        Err(e) => {
                            state.message = e;
                            state.mode = InputMode::EditingDescription;
                            state.needs_redraw = true;
                            return None;
                        }
                    }
                }

                let (clean_input, new_aliases): (String, HashMap<String, Vec<String>>) =
                    extract_inline_aliases(&state.input_buffer);

                if !new_aliases.is_empty() {
                    for (key, tags) in new_aliases {
                        if let Err(e) = validate_alias_integrity(&key, &tags, &state.tag_aliases) {
                            state.message =
                                rust_i18n::t!("error_general", error = e.to_string()).to_string();
                            return None;
                        }

                        state.tag_aliases.insert(key.clone(), tags.clone());
                        let modified = state.store.apply_alias_retroactively(&key, &tags);

                        for t in modified {
                            let _ = action_tx.try_send(Action::PersistBatch(vec![
                                crate::journal::Action::Update(t),
                            ]));
                        }
                    }
                    if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                        let old = cfg.clone();
                        cfg.tag_aliases = state.tag_aliases.clone();
                        cfg.update_sync_timestamp_if_changed(&old);
                        let _ = cfg.save(state.ctx.as_ref());
                    }
                    let trimmed = clean_input.trim();
                    let is_alias_only = trimmed.is_empty()
                        || (!trimmed.contains(' ')
                            && (trimmed.starts_with('#')
                                || trimmed.starts_with("@@")
                                || trimmed.to_lowercase().starts_with("loc:")));

                    if is_alias_only {
                        state.mode = InputMode::Normal;
                        state.reset_input();
                        state.message = rust_i18n::t!("alias_updated").to_string();
                        return None;
                    }
                }

                let target_href = state
                    .active_cal_href
                    .clone()
                    .filter(|href| state.local_mode_enabled || !href.starts_with("local://"))
                    .or_else(|| {
                        state
                            .get_filtered_calendars()
                            .first()
                            .map(|c| c.href.clone())
                    });

                if let Some(href) = target_href {
                    // Load config to get time
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let def_time =
                        NaiveTime::parse_from_str(&config.default_reminder_time, "%H:%M").ok();

                    let mut task = Task::new(&clean_input, &state.tag_aliases, def_time);
                    task.calendar_href = href.clone();
                    task.parent_uid = state.creating_child_of.clone();

                    let new_uid = task.uid.clone();
                    state.store.add_task(task.clone());
                    state.refresh_filtered_view();
                    update_alarms(state);

                    if let Some(idx) = state.find_task_index_by_uid(&new_uid) {
                        state.list_state.select(Some(idx));
                    }

                    state.mode = InputMode::Normal;
                    state.reset_input();
                    state.creating_child_of = None;
                    return Some(Action::PersistBatch(vec![crate::journal::Action::Create(
                        task,
                    )]));
                }
                state.message = if state.local_mode_enabled {
                    rust_i18n::t!("error_no_calendar_available").to_string()
                } else {
                    rust_i18n::t!("error_no_remote_calendar").to_string()
                };
                state.mode = InputMode::Normal;
                state.reset_input();
            }
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.reset_input();
                state.creating_with_desc = false;
                state.new_task_title.clear();
                state.message = rust_i18n::t!("editing_cancelled").to_string();
            }
            KeyCode::Char(c) => state.enter_char(c),
            KeyCode::Backspace => state.delete_char(),
            KeyCode::Left => state.move_cursor_left(),
            KeyCode::Right => state.move_cursor_right(),
            _ => {}
        },
        InputMode::Editing => match key.code {
            KeyCode::Enter => {
                let (clean_input, new_aliases): (String, HashMap<String, Vec<String>>) =
                    extract_inline_aliases(&state.input_buffer);
                if !new_aliases.is_empty() {
                    for (k, v) in new_aliases {
                        if let Err(e) = validate_alias_integrity(&k, &v, &state.tag_aliases) {
                            state.message =
                                rust_i18n::t!("error_general", error = e.to_string()).to_string();
                            return None;
                        }

                        state.tag_aliases.insert(k.clone(), v.clone());
                        let modified = state.store.apply_alias_retroactively(&k, &v);
                        for mod_t in modified {
                            let _ = action_tx.try_send(Action::PersistBatch(vec![
                                crate::journal::Action::Update(mod_t),
                            ]));
                        }
                    }
                    if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                        let old = cfg.clone();
                        cfg.tag_aliases = state.tag_aliases.clone();
                        cfg.update_sync_timestamp_if_changed(&old);
                        let _ = cfg.save(state.ctx.as_ref());
                    }
                }

                let target_uid: Option<String> = state.editing_uid.clone();

                if let Some(uid) = target_uid
                    && let Some((t, _)) = state.store.get_task_mut(&uid)
                {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let def_time =
                        NaiveTime::parse_from_str(&config.default_reminder_time, "%H:%M").ok();
                    t.apply_smart_input(&clean_input, &state.tag_aliases, def_time);
                    t.sequence += 1;
                    let clone = t.clone();
                    state.refresh_filtered_view();
                    update_alarms(state);
                    state.mode = InputMode::Normal;
                    state.reset_input();
                    let _ = action_tx.try_send(Action::PersistBatch(vec![
                        crate::journal::Action::Update(clone),
                    ]));
                }
                state.mode = InputMode::Normal;
            }
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.reset_input();
            }
            KeyCode::Char(c) => state.enter_char(c),
            KeyCode::Backspace => state.delete_char(),
            KeyCode::Left => state.move_cursor_left(),
            KeyCode::Right => state.move_cursor_right(),
            _ => {}
        },
        InputMode::EditingDescription => match key.code {
            // Enter inserts a newline
            KeyCode::Enter => {
                state.enter_char('\n');
            }
            // Save: Ctrl+S, Ctrl+D, F2
            KeyCode::Char('s') | KeyCode::Char('d')
                if key.modifiers.contains(KeyModifiers::CONTROL) =>
            {
                save_description(state, action_tx);
                return None;
            }
            KeyCode::F(2) => {
                save_description(state, action_tx);
                return None;
            }
            // Cancel: Esc
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.reset_input();

                // --- ADD THESE 2 LINES ---
                state.creating_with_desc = false;
                state.new_task_title.clear();
                // -------------------------

                state.message = rust_i18n::t!("editing_cancelled").to_string();
            }
            // Editing & Navigation
            KeyCode::Char(c) => {
                // FIX: Sanitize control characters and normalize tabs to spaces.
                // Convert Tab to 4 spaces to ensure cursor math matches rendering.
                if c == '\t' {
                    for _ in 0..4 {
                        state.enter_char(' ');
                    }
                } else if !c.is_control() || c == '\n' {
                    state.enter_char(c);
                }
            }
            KeyCode::Backspace => state.delete_char(),
            KeyCode::Left => state.move_cursor_left(),
            KeyCode::Right => state.move_cursor_right(),
            KeyCode::Up => {
                // Move cursor to same column on previous line (or start)
                let current_idx = state.cursor_position;
                let chars: Vec<char> = state.input_buffer.chars().collect();

                // 1. Find start of current line
                let mut line_start = current_idx;
                while line_start > 0 && chars[line_start - 1] != '\n' {
                    line_start -= 1;
                }

                // 2. Calculate column offset
                let col = current_idx - line_start;

                if line_start > 0 {
                    // 3. Find start of previous line
                    let mut prev_line_start = line_start - 1;
                    while prev_line_start > 0 && chars[prev_line_start - 1] != '\n' {
                        prev_line_start -= 1;
                    }

                    // 4. Determine length of previous line
                    // (line_start - 1) is the newline char itself
                    let prev_line_len = (line_start - 1) - prev_line_start;

                    // 5. Move to min(col, prev_len)
                    let new_col = col.min(prev_line_len);
                    state.cursor_position = prev_line_start + new_col;
                } else {
                    state.cursor_position = 0;
                }
            }
            KeyCode::Down => {
                let current_idx = state.cursor_position;
                let chars: Vec<char> = state.input_buffer.chars().collect();
                let total = chars.len();

                // 1. Find start of current line and column
                let mut line_start = current_idx;
                while line_start > 0 && chars[line_start - 1] != '\n' {
                    line_start -= 1;
                }
                let col = current_idx - line_start;

                // 2. Find end of current line (start of next)
                let mut next_line_start = current_idx;
                while next_line_start < total && chars[next_line_start] != '\n' {
                    next_line_start += 1;
                }

                if next_line_start < total {
                    next_line_start += 1; // Skip the newline

                    // 3. Find length of next line
                    let mut next_line_end = next_line_start;
                    while next_line_end < total && chars[next_line_end] != '\n' {
                        next_line_end += 1;
                    }
                    let next_line_len = next_line_end - next_line_start;

                    // 4. Move
                    let new_col = col.min(next_line_len);
                    state.cursor_position = next_line_start + new_col;
                } else {
                    state.cursor_position = total;
                }
            }
            _ => {}
        },
        InputMode::Snoozing => match key.code {
            KeyCode::Enter if !state.input_buffer.is_empty() => {
                // Parse custom snooze duration
                if let Some(mins) = crate::model::parser::parse_duration(&state.input_buffer) {
                    if let Some((task, alarm_uid)) = state.active_alarm.clone()
                        && let Some((t, _)) = state.store.get_task_mut(&task.uid)
                        && t.snooze_alarm(&alarm_uid, mins)
                    {
                        let uid = t.uid.clone();
                        state.active_alarm = None;
                        state.mode = InputMode::Normal;
                        state.reset_input();
                        state.refresh_filtered_view();
                        // Push update via centralized logic
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::ToggleTask { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                } else {
                    state.message =
                        rust_i18n::t!("error_invalid_duration", val = state.input_buffer.clone())
                            .to_string();
                }
                return None;
            }
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.reset_input();
                // Return to alarm display
                return None;
            }
            KeyCode::Char(c) => {
                state.enter_char(c);
            }
            KeyCode::Backspace => {
                state.delete_char();
            }
            KeyCode::Left => {
                state.move_cursor_left();
            }
            KeyCode::Right => {
                state.move_cursor_right();
            }
            _ => {}
        },
        InputMode::Searching => match key.code {
            KeyCode::Enter => {
                if state.input_buffer.starts_with('#') && !state.input_buffer.contains(' ') {
                    let tag = state.input_buffer.trim_start_matches('#').to_string();
                    state.sidebar_mode = SidebarMode::Categories;
                    state.selected_categories.clear();
                    state.selected_categories.insert(tag);
                    state.active_search_query.clear();
                } else if (state.input_buffer.starts_with("@@")
                    || state.input_buffer.starts_with("loc:"))
                    && !state.input_buffer.contains(' ')
                {
                    let raw = if state.input_buffer.starts_with("@@") {
                        state.input_buffer.trim_start_matches("@@")
                    } else {
                        state.input_buffer.trim_start_matches("loc:")
                    };
                    let loc = crate::model::parser::strip_quotes(raw);

                    state.sidebar_mode = SidebarMode::Locations;
                    state.selected_locations.clear();
                    state.selected_locations.insert(loc);
                    state.active_search_query.clear();
                } else {
                    state.active_search_query = state.input_buffer.clone();
                }

                state.mode = InputMode::Normal;
                state.reset_input();
                state.refresh_filtered_view();
            }
            KeyCode::Esc => {
                state.active_search_query.clear();
                state.mode = InputMode::Normal;
                state.reset_input();
                state.refresh_filtered_view();
            }
            KeyCode::Char(c) => {
                state.enter_char(c);
                state.refresh_filtered_view();
            }
            KeyCode::Backspace => {
                state.delete_char();
                state.refresh_filtered_view();
            }
            KeyCode::Left => state.move_cursor_left(),
            KeyCode::Right => state.move_cursor_right(),
            KeyCode::Down => state.next(),
            KeyCode::Up => state.previous(),
            KeyCode::PageDown => state.jump_forward(10),
            KeyCode::PageUp => state.jump_backward(10),
            _ => {}
        },
        InputMode::ActionMenu => match key.code {
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.action_filter.clear();
            }
            KeyCode::Down | KeyCode::Char('j') => {
                let len = state.action_menu_items.len();
                if len > 0 {
                    let current = state.action_selection_state.selected().unwrap_or(0);
                    state
                        .action_selection_state
                        .select(Some((current + 1) % len));
                }
            }
            KeyCode::Up | KeyCode::Char('k') => {
                let len = state.action_menu_items.len();
                if len > 0 {
                    let current = state.action_selection_state.selected().unwrap_or(0);
                    state
                        .action_selection_state
                        .select(Some((current + len - 1) % len));
                }
            }
            KeyCode::Backspace => {
                if !state.action_filter.is_empty() {
                    state.action_filter.pop();
                    update_action_menu_filter(state);
                } else {
                    state.mode = InputMode::Normal;
                }
            }
            KeyCode::Char(c) => {
                state.action_filter.push(c);
                update_action_menu_filter(state);
            }
            KeyCode::Enter => {
                if let Some(idx) = state.action_selection_state.selected()
                    && let Some(action) = state.action_menu_items.get(idx).copied()
                    && let Some(task) = state.get_selected_task().cloned()
                {
                    state.mode = InputMode::Normal;
                    state.action_filter.clear();
                    execute_task_action(state, action, &task, action_tx).await;
                }
            }
            _ => {}
        },
        InputMode::Help(current_tab) => match key.code {
            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => {
                state.mode = InputMode::Normal;
            }
            KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => {
                let next_tab = match current_tab {
                    crate::help::HelpTab::Syntax => crate::help::HelpTab::Shortcuts,
                    crate::help::HelpTab::Shortcuts => crate::help::HelpTab::About,
                    crate::help::HelpTab::About => crate::help::HelpTab::Syntax,
                };
                state.mode = InputMode::Help(next_tab);
                state.edit_scroll_offset = 0;
            }
            KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => {
                let prev_tab = match current_tab {
                    crate::help::HelpTab::Syntax => crate::help::HelpTab::About,
                    crate::help::HelpTab::Shortcuts => crate::help::HelpTab::Syntax,
                    crate::help::HelpTab::About => crate::help::HelpTab::Shortcuts,
                };
                state.mode = InputMode::Help(prev_tab);
                state.edit_scroll_offset = 0;
            }
            KeyCode::Down | KeyCode::Char('j') => {
                state.edit_scroll_offset = state.edit_scroll_offset.saturating_add(1);
            }
            KeyCode::Up | KeyCode::Char('k') => {
                state.edit_scroll_offset = state.edit_scroll_offset.saturating_sub(1);
            }
            KeyCode::PageDown => {
                state.edit_scroll_offset = state.edit_scroll_offset.saturating_add(10);
            }
            KeyCode::PageUp => {
                state.edit_scroll_offset = state.edit_scroll_offset.saturating_sub(10);
            }
            _ => {}
        },
        InputMode::Normal => match key.code {
            KeyCode::Char('e') | KeyCode::Char('E')
                if key.modifiers.contains(KeyModifiers::CONTROL) =>
            {
                state.mode = InputMode::Creating;
                state.creating_with_desc = true;
                state.reset_input();
                state.new_task_title.clear();
                state.message = rust_i18n::t!("task_title_prompt").to_string();
            }
            KeyCode::Esc => {
                let mut needs_refresh = false;
                if state.yanked_uid.is_some() {
                    state.yanked_uid = None;
                    state.yank_lock_active = false;
                    state.message = rust_i18n::t!("yank_cleared").to_string();
                } else if !state.active_search_query.is_empty() {
                    state.active_search_query.clear();
                    needs_refresh = true;
                } else if !state.selected_categories.is_empty() {
                    state.selected_categories.clear();
                    needs_refresh = true;
                }
                if needs_refresh {
                    state.refresh_filtered_view();
                }
            }
            KeyCode::Char('?') => {
                state.mode = InputMode::Help(crate::help::HelpTab::Shortcuts);
                state.edit_scroll_offset = 0;
            }
            KeyCode::Char('w') => {
                if state.active_search_query.contains(&state.quick_filter_term) {
                    state.active_search_query = state
                        .active_search_query
                        .replace(&state.quick_filter_term, "")
                        .trim()
                        .to_string();
                } else {
                    if state.active_search_query.is_empty() {
                        state.active_search_query = state.quick_filter_term.clone();
                    } else {
                        state.active_search_query =
                            format!("{} {}", state.quick_filter_term, state.active_search_query);
                    }
                }
                state.refresh_filtered_view();
            }
            KeyCode::Char('q') => return Some(Action::Quit),
            KeyCode::Char('r') => return Some(Action::Refresh),
            KeyCode::Char('R') => {
                // Weighted-random jump to a task (uppercase R)
                let real_tasks: Vec<Task> = state
                    .tasks
                    .iter()
                    .filter_map(|item| {
                        if let TaskListItem::Task(task) = item {
                            Some((**task).clone())
                        } else {
                            None
                        }
                    })
                    .collect();
                if let Some(idx) = select_weighted_random_index(&real_tasks, state.default_priority)
                {
                    state.list_state.select(Some(idx));
                    state.message = rust_i18n::t!("jumped_to_task").to_string();
                }
            }

            // Quick log: start a session input for the selected task
            KeyCode::Char('t') => {
                if let Some(summary) = state.get_selected_task().map(|t| t.summary.clone()) {
                    state.mode = InputMode::AddingSession;
                    state.reset_input();
                    state.message = format!(
                        "{} ({} 30m, yesterday 1h):",
                        t!("tui_log_time_prompt", name = summary),
                        t!("eg")
                    );
                }
            }

            // Manage sessions: open session list popup for selected task
            KeyCode::Char('T') => {
                if let Some(sessions) = state.get_selected_task().map(|t| t.sessions.clone()) {
                    let mut items = Vec::new();
                    for (i, session) in sessions.iter().enumerate() {
                        let s_dt = chrono::DateTime::from_timestamp(session.start, 0)
                            .unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap())
                            .with_timezone(&chrono::Local);
                        let e_dt = chrono::DateTime::from_timestamp(session.end, 0)
                            .unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap())
                            .with_timezone(&chrono::Local);
                        let dur = (session.end - session.start) / 60;
                        let display = format!(
                            "{} {}-{} ({}m)",
                            s_dt.format("%Y-%m-%d"),
                            s_dt.format("%H:%M"),
                            e_dt.format("%H:%M"),
                            dur
                        );
                        items.push((i, display));
                    }
                    if !items.is_empty() {
                        state.session_items = items;
                        state.session_selection_state.select(Some(0));
                        state.mode = InputMode::ManagingSessions;
                        state.message = t!("tui_manage_sessions_prompt").to_string();
                    } else {
                        state.message = t!("no_sessions_recorded").to_string();
                    }
                }
            }

            KeyCode::Char(' ') => {
                let is_shift = key.modifiers.contains(KeyModifiers::SHIFT);
                if state.active_focus == Focus::Main {
                    if let Some(view_task) = state.get_selected_task() {
                        if view_task.etag == "pending_refresh" {
                            return None;
                        }
                        let uid = view_task.uid.clone();
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();

                        let intent = if is_shift {
                            AppIntent::ToggleTaskShift { uid: uid.clone() }
                        } else {
                            AppIntent::ToggleTask { uid: uid.clone() }
                        };

                        let actions = state.store.apply_task_intent(&intent, &config);
                        state.refresh_filtered_view();
                        update_alarms(state);

                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                } else if state.active_focus == Focus::Sidebar
                    && state.sidebar_mode == SidebarMode::Calendars
                {
                    let target_href = if let Some(idx) = state.cal_state.selected() {
                        let filtered = state.get_filtered_calendars();
                        filtered.get(idx).map(|c| c.href.clone())
                    } else {
                        None
                    };

                    if let Some(href) = target_href
                        && state.active_cal_href.as_ref() != Some(&href)
                    {
                        if state.hidden_calendars.contains(&href) {
                            state.hidden_calendars.remove(&href);
                            let _ = action_tx.send(Action::ToggleCalendarVisibility(href)).await;
                        } else {
                            state.hidden_calendars.insert(href);
                        }
                        state.refresh_filtered_view();
                    }
                }
            }
            KeyCode::Char('s') => {
                if let Some(task) = state.get_selected_task() {
                    let uid = task.uid.clone();
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = if task.status == TaskStatus::InProcess {
                        AppIntent::PauseTask { uid: uid.clone() }
                    } else {
                        AppIntent::StartTask { uid: uid.clone() }
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    if !actions.is_empty() {
                        state.refresh_filtered_view();
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
            }
            KeyCode::Char('S') => {
                if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::StopTask { uid: uid.clone() };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    if !actions.is_empty() {
                        state.refresh_filtered_view();
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                    return None;
                }
            }
            KeyCode::Char('x') => {
                if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::CancelTask { uid: uid.clone() };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    update_alarms(state);

                    let tx = action_tx.clone();
                    tokio::spawn(async move {
                        let _ = tx.send(Action::PersistBatch(actions)).await;
                    });
                }
            }
            KeyCode::Char('+') => {
                if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::ChangePriority {
                        uid: uid.clone(),
                        delta: 1,
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
            }
            KeyCode::Char('-') => {
                if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::ChangePriority {
                        uid: uid.clone(),
                        delta: -1,
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
            }
            KeyCode::Delete => {
                if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = if key.modifiers.contains(KeyModifiers::CONTROL) {
                        AppIntent::DeleteTaskTree { uid: uid.clone() }
                    } else {
                        AppIntent::DeleteTask { uid: uid.clone() }
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    update_alarms(state);

                    let tx = action_tx.clone();
                    tokio::spawn(async move {
                        let _ = tx.send(Action::PersistBatch(actions)).await;
                    });
                }
            }
            KeyCode::Char('c') => {
                let data = if let Some(parent_uid) = &state.yanked_uid {
                    state
                        .get_selected_task()
                        .map(|view_task| (view_task.uid.clone(), parent_uid.clone()))
                } else {
                    None
                };

                if let Some((child_uid, parent_uid)) = data {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::MakeChild {
                        uid: child_uid.clone(),
                        parent_uid: parent_uid.clone(),
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    if !state.yank_lock_active {
                        state.yanked_uid = None;
                    }
                    state.refresh_filtered_view();
                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
            }
            KeyCode::Char('C') => {
                if state.active_focus == Focus::Main
                    && let Some(task) = state.get_selected_task()
                {
                    // Fix: Define these inside the block to resolve scope errors
                    let uid = task.uid.clone();
                    let summary = task.summary.clone();

                    let mut initial_input = String::new();
                    for cat in &task.categories {
                        // Parity: Use quote_value to handle spaces correctly
                        initial_input
                            .push_str(&format!("#{} ", crate::model::parser::quote_value(cat)));
                    }
                    // Parity: Add Location inheritance
                    if let Some(loc) = &task.location {
                        initial_input
                            .push_str(&format!("@@{} ", crate::model::parser::quote_value(loc)));
                    }

                    state.input_buffer = initial_input;
                    state.cursor_position = state.input_buffer.chars().count();

                    state.mode = InputMode::Creating;
                    state.creating_with_desc = false;
                    state.new_task_title.clear();
                    state.creating_child_of = Some(uid);
                    state.message = rust_i18n::t!("new_child_of", name = summary).to_string();
                }
            }
            KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::DuplicateTaskTree { uid: uid.clone() };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
            }
            KeyCode::Char('Y') => {
                state.yank_lock_active = !state.yank_lock_active;
                state.needs_redraw = true;
            }
            KeyCode::Char('y') => {
                if let Some(t) = state.get_selected_task() {
                    let uid = t.uid.clone();
                    let summary = t.summary.clone();

                    // Compute the text to copy while only holding the immutable borrow,
                    // then mutate state (to avoid borrow-checker error).
                    let text = if t.description.is_empty() {
                        t.to_smart_string()
                    } else {
                        format!("{}\n\n{}", t.to_smart_string(), t.description)
                    };

                    // Now it's safe to update yank state
                    state.yanked_uid = Some(uid);

                    // Copy to clipboard using OSC 52 (base64 payload)
                    use base64::Engine;
                    let b64 = base64::engine::general_purpose::STANDARD.encode(text);
                    print!("\x1b]52;c;{}\x07", b64);
                    use std::io::Write;
                    let _ = std::io::stdout().flush();

                    state.message =
                        rust_i18n::t!("yanked_and_copied", summary = summary).to_string();
                }
            }
            KeyCode::Char('g') => {
                if let Some(task) = state.get_selected_task() {
                    let uid = task.uid.clone();
                    let count = state.store.count_tree_locations(&uid);

                    if count > 1 {
                        let waypoints = state.store.get_tree_waypoints(&uid);
                        let mut gpx_string = String::from(
                            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpx version=\"1.1\" creator=\"Cfait\" xmlns=\"http://www.topografix.com/GPX/1/1\">\n",
                        );
                        for (name, geo) in waypoints {
                            let parts: Vec<&str> = geo.split(',').collect();
                            if parts.len() >= 2 {
                                let escaped_name = name
                                    .replace('&', "&amp;")
                                    .replace('<', "&lt;")
                                    .replace('>', "&gt;");
                                gpx_string.push_str(&format!(
                                    "  <wpt lat=\"{}\" lon=\"{}\"><name>{}</name></wpt>\n",
                                    parts[0].trim(),
                                    parts[1].trim(),
                                    escaped_name
                                ));
                            }
                        }
                        gpx_string.push_str("</gpx>");

                        if let Ok(cache_dir) = state.ctx.get_cache_dir() {
                            let path =
                                cache_dir.join(format!("locations_{}.gpx", uuid::Uuid::new_v4()));
                            if std::fs::write(&path, gpx_string).is_ok() {
                                #[cfg(not(target_os = "android"))]
                                {
                                    let target = path.to_string_lossy().to_string();
                                    std::thread::spawn(move || {
                                        #[cfg(target_os = "linux")]
                                        let _ = std::process::Command::new("xdg-open")
                                            .arg(target)
                                            .spawn();
                                        #[cfg(target_os = "windows")]
                                        let _ = std::process::Command::new("explorer")
                                            .arg(target)
                                            .spawn();
                                        #[cfg(target_os = "macos")]
                                        let _ =
                                            std::process::Command::new("open").arg(target).spawn();
                                    });
                                }
                                state.message = rust_i18n::t!("action_open_locations").to_string();
                            } else {
                                state.message = rust_i18n::t!("error_write_gpx").to_string();
                            }
                        }
                    } else if let Some(_geo) = &task.geo {
                        #[cfg(not(target_os = "android"))]
                        {
                            let target_url = format!("geo:{}", _geo);
                            std::thread::spawn(move || {
                                #[cfg(target_os = "linux")]
                                let _ = std::process::Command::new("xdg-open")
                                    .arg(target_url)
                                    .spawn();
                                #[cfg(target_os = "windows")]
                                let _ = std::process::Command::new("explorer")
                                    .arg(target_url)
                                    .spawn();
                                #[cfg(target_os = "macos")]
                                let _ = std::process::Command::new("open").arg(target_url).spawn();
                            });
                        }
                        state.message = rust_i18n::t!("open_coordinates").to_string();
                    } else {
                        state.message = rust_i18n::t!("error_no_location").to_string();
                    }
                }
            }
            KeyCode::Char('o') => {
                if let Some(task) = state.get_selected_task() {
                    if let Some(_url) = &task.url {
                        #[cfg(not(target_os = "android"))]
                        {
                            let target_url = _url.clone();
                            std::thread::spawn(move || {
                                #[cfg(target_os = "linux")]
                                let _ = std::process::Command::new("xdg-open")
                                    .arg(target_url)
                                    .spawn();
                                #[cfg(target_os = "windows")]
                                let _ = std::process::Command::new("explorer")
                                    .arg(target_url)
                                    .spawn();
                                #[cfg(target_os = "macos")]
                                let _ = std::process::Command::new("open").arg(target_url).spawn();
                            });
                        }
                        state.message = rust_i18n::t!("open_url").to_string();
                    } else {
                        state.message = rust_i18n::t!("error_no_url").to_string();
                    }
                }
            }
            KeyCode::Char('b') => {
                let data = if let Some(yanked) = &state.yanked_uid {
                    state
                        .get_selected_task()
                        .map(|current| (current.uid.clone(), yanked.clone()))
                } else {
                    None
                };

                if let Some((curr_uid, yanked_uid)) = data {
                    if curr_uid == yanked_uid {
                        state.message = rust_i18n::t!("error_cannot_depend_on_self").to_string();
                    } else {
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::AddDependency {
                            uid: curr_uid.clone(),
                            blocker_uid: yanked_uid.clone(),
                        };

                        let actions = state.store.apply_task_intent(&intent, &config);
                        if !state.yank_lock_active {
                            state.yanked_uid = None;
                        }
                        state.refresh_filtered_view();
                        if !actions.is_empty() {
                            let tx = action_tx.clone();
                            tokio::spawn(async move {
                                let _ = tx.send(Action::PersistBatch(actions)).await;
                            });
                        }
                    }
                }
            }
            KeyCode::Char('l') => {
                let data = if let Some(yanked) = &state.yanked_uid {
                    state
                        .get_selected_task()
                        .map(|current| (current.uid.clone(), yanked.clone()))
                } else {
                    None
                };

                if let Some((curr_uid, yanked_uid)) = data {
                    if curr_uid == yanked_uid {
                        state.message = rust_i18n::t!("error_cannot_relate_to_self").to_string();
                    } else {
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::AddRelatedTo {
                            uid: curr_uid.clone(),
                            related_uid: yanked_uid.clone(),
                        };

                        let actions = state.store.apply_task_intent(&intent, &config);
                        if !state.yank_lock_active {
                            state.yanked_uid = None;
                        }
                        state.refresh_filtered_view();
                        if !actions.is_empty() {
                            let tx = action_tx.clone();
                            tokio::spawn(async move {
                                let _ = tx.send(Action::PersistBatch(actions)).await;
                            });
                        }
                    }
                }
            }
            KeyCode::Char('.') | KeyCode::Char('>') => {
                if state.active_focus == Focus::Main
                    && let Some(idx) = state.list_state.selected()
                    && idx > 0
                    && idx < state.tasks.len()
                    && let (Some(parent_task), Some(current_task)) = (
                        state.get_task_at_index(idx - 1),
                        state.get_task_at_index(idx),
                    )
                {
                    let parent_uid = parent_task.uid.clone();
                    let current_uid = current_task.uid.clone();
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::MakeChild {
                        uid: current_uid.clone(),
                        parent_uid: parent_uid.clone(),
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
            }
            KeyCode::Char(',') | KeyCode::Char('<') => {
                if state.active_focus == Focus::Main
                    && let Some(view_task) = state.get_selected_task()
                    && view_task.parent_uid.is_some()
                {
                    let uid = view_task.uid.clone();
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::RemoveParent { uid: uid.clone() };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
            }
            KeyCode::Char('X') => {
                // Step 1: Select source local calendar
                state.export_source_calendars = state
                    .calendars
                    .iter()
                    .filter(|c| {
                        c.href.starts_with("local://")
                            && !state.disabled_calendars.contains(&c.href)
                            && c.href != crate::storage::LOCAL_TRASH_HREF
                    })
                    .cloned()
                    .collect();
                if !state.export_source_calendars.is_empty() {
                    state.export_source_selection_state.select(Some(0));
                    state.mode = InputMode::SelectingExportSource;
                    state.message = rust_i18n::t!("tui_export_select_source").to_string();
                }
            }
            KeyCode::Char('M') => {
                if let Some(task) = state.get_selected_task() {
                    let current_href = task.calendar_href.clone();
                    state.move_targets = state
                        .calendars
                        .iter()
                        .filter(|c| {
                            c.href != current_href
                                && !state.disabled_calendars.contains(&c.href)
                                && c.href != crate::storage::LOCAL_TRASH_HREF
                                && c.href != "local://recovery"
                        })
                        .cloned()
                        .collect();
                    if !state.move_targets.is_empty() {
                        state.move_selection_state.select(Some(0));
                        state.mode = InputMode::Moving;
                        state.message = rust_i18n::t!("tui_select_calendar_prompt").to_string();
                    }
                }
            }
            KeyCode::Down | KeyCode::Char('j') => state.next(),
            KeyCode::Up | KeyCode::Char('k') => state.previous(),
            KeyCode::PageDown => state.jump_forward(10),
            KeyCode::PageUp => state.jump_backward(10),
            KeyCode::Tab => state.toggle_focus(),
            KeyCode::Char('1') => {
                state.sidebar_mode = SidebarMode::Calendars;
                state.refresh_filtered_view();
            }
            KeyCode::Char('2') => {
                state.sidebar_mode = SidebarMode::Categories;
                state.refresh_filtered_view();
            }
            KeyCode::Char('z') => {
                if state.active_focus == Focus::Main {
                    if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone()) {
                        let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                        let intent = AppIntent::ToggleTreeCollapse { uid: uid.clone() };
                        let actions = state.store.apply_task_intent(&intent, &config);
                        state.refresh_filtered_view();
                        if !actions.is_empty() {
                            let tx = action_tx.clone();
                            tokio::spawn(async move {
                                let _ = tx.send(Action::PersistBatch(actions)).await;
                            });
                        }
                    }
                } else if state.active_focus == Focus::Sidebar {
                    match state.sidebar_mode {
                        SidebarMode::Categories => {
                            if let Some(idx) = state.cal_state.selected()
                                && let Some(item) = state.cached_categories.get(idx)
                                && item.has_children
                            {
                                let key = item.full_key.clone();
                                if !state.expanded_tags.remove(&key) {
                                    state.expanded_tags.insert(key);
                                }
                                state.refresh_filtered_view();
                                if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                    cfg.expanded_tags =
                                        state.expanded_tags.iter().cloned().collect();
                                    let _ = cfg.save(state.ctx.as_ref());
                                }
                            }
                        }
                        SidebarMode::Locations => {
                            if let Some(idx) = state.cal_state.selected()
                                && let Some(item) = state.cached_locations.get(idx)
                                && item.has_children
                            {
                                let key = item.full_key.clone();
                                if !state.expanded_locations.remove(&key) {
                                    state.expanded_locations.insert(key);
                                }
                                state.refresh_filtered_view();
                                if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                    cfg.expanded_locations =
                                        state.expanded_locations.iter().cloned().collect();
                                    let _ = cfg.save(state.ctx.as_ref());
                                }
                            }
                        }
                        _ => {}
                    }
                }
            }
            KeyCode::Char('3') => {
                state.sidebar_mode = SidebarMode::Locations;
                state.refresh_filtered_view();
            }
            KeyCode::Char('m') => {
                state.match_all_categories = !state.match_all_categories;
                state.refresh_filtered_view();
            }
            KeyCode::Char('H') => {
                state.hide_completed = !state.hide_completed;
                state.refresh_filtered_view();
            }
            KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                state.sort_standard_by_priority = !state.sort_standard_by_priority;
                state.refresh_filtered_view();
                state.message = if state.sort_standard_by_priority {
                    rust_i18n::t!("sort_standard_by_priority").to_string()
                } else {
                    rust_i18n::t!("sort_standard_by_date").to_string()
                };
            }
            KeyCode::Char('L') => {
                // Enter relationship browsing mode to navigate to linked tasks
                if let Some(task) = state.get_selected_task() {
                    let mut items = Vec::new();

                    // Add parent
                    if let Some(p_uid) = &task.parent_uid {
                        let name = state
                            .store
                            .get_summary(p_uid)
                            .unwrap_or_else(|| "Unknown task".to_string());
                        items.push((
                            p_uid.clone(),
                            format!("↑ [Parent] {}", name),
                            "parent".to_string(),
                        ));
                    }

                    // Add blocked-by dependencies
                    for dep_uid in &task.dependencies {
                        let name = state
                            .store
                            .get_summary(dep_uid)
                            .unwrap_or_else(|| "Unknown task".to_string());
                        let is_done = state.store.is_task_done(dep_uid).unwrap_or(false);
                        let check = if is_done { "[x]" } else { "[ ]" };
                        items.push((
                            dep_uid.clone(),
                            format!("⬆ [Blocked by] {} {}", check, name),
                            "dependency".to_string(),
                        ));
                    }

                    // Add outgoing relations
                    for related_uid in &task.related_to {
                        let name = state
                            .store
                            .get_summary(related_uid)
                            .unwrap_or_else(|| "Unknown task".to_string());
                        items.push((
                            related_uid.clone(),
                            format!("→ [Related to] {}", name),
                            "related_to".to_string(),
                        ));
                    }

                    // Add incoming relations
                    let incoming_related = state.store.get_tasks_related_to(&task.uid);
                    for (related_uid, related_name) in incoming_related {
                        items.push((
                            related_uid.clone(),
                            format!("← [Related from] {}", related_name),
                            "related_from".to_string(),
                        ));
                    }

                    // Add blocking (successors)
                    let blocking_tasks = state.store.get_tasks_blocking(&task.uid);
                    for (blocking_uid, blocking_name) in blocking_tasks {
                        items.push((
                            blocking_uid.clone(),
                            format!("⬇ [Blocking] {}", blocking_name),
                            "blocking".to_string(),
                        ));
                    }

                    if !items.is_empty() {
                        state.relationship_items = items;
                        state.relationship_selection_state.select(Some(0));
                        state.mode = InputMode::RelationshipBrowsing;
                        state.message =
                            format!("{} (Del/x: Remove)", rust_i18n::t!("tui_select_task_jump"));
                    } else {
                        state.message = rust_i18n::t!("error_no_related_tasks").to_string();
                    }
                }
            }
            KeyCode::Char('*') if state.active_focus == Focus::Sidebar => {
                match state.sidebar_mode {
                    SidebarMode::Calendars => {
                        let are_all_visible = state
                            .get_filtered_calendars()
                            .iter()
                            .filter(|c| {
                                c.href != crate::storage::LOCAL_TRASH_HREF
                                    && c.href != "local://recovery"
                            })
                            .all(|c| !state.hidden_calendars.contains(&c.href));

                        if are_all_visible {
                            for cal in &state.calendars {
                                if state.active_cal_href.as_ref() != Some(&cal.href) {
                                    state.hidden_calendars.insert(cal.href.clone());
                                }
                            }
                        } else {
                            state.hidden_calendars.clear();
                            // Re-hide trash if not active
                            if state.active_cal_href.as_deref() != Some("local://trash") {
                                state.hidden_calendars.insert("local://trash".to_string());
                            }
                            let _ = action_tx.send(Action::Refresh).await;
                        }
                    }
                    SidebarMode::Categories => {
                        state.selected_categories.clear();
                    }
                    SidebarMode::Locations => {
                        state.selected_locations.clear();
                    }
                }
            }
            KeyCode::Right => {
                if state.active_focus == Focus::Sidebar {
                    match state.sidebar_mode {
                        SidebarMode::Calendars => {
                            let target_href = if let Some(idx) = state.cal_state.selected() {
                                let filtered = state.get_filtered_calendars();
                                filtered.get(idx).map(|c| c.href.clone())
                            } else {
                                None
                            };

                            if let Some(href) = target_href {
                                state.active_cal_href = Some(href.clone());
                                state.hidden_calendars.clear();
                                for c in &state.calendars {
                                    if c.href != href {
                                        state.hidden_calendars.insert(c.href.clone());
                                    }
                                }
                                state.refresh_filtered_view();
                                if href != LOCAL_CALENDAR_HREF {
                                    return Some(Action::IsolateCalendar(href));
                                }
                            }
                        }
                        SidebarMode::Categories => {
                            // Use cached categories derived from the last `filter()` call
                            let cats = &state.cached_categories;
                            if let Some(idx) = state.cal_state.selected()
                                && let Some(c) = cats.get(idx)
                            {
                                let c_clone = c.full_key.clone();
                                state.selected_categories.clear();
                                state.selected_categories.insert(c_clone.clone());

                                if !state.expanded_tags.contains(&c_clone) {
                                    state.expanded_tags.insert(c_clone);
                                    if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                        cfg.expanded_tags =
                                            state.expanded_tags.iter().cloned().collect();
                                        let _ = cfg.save(state.ctx.as_ref());
                                    }
                                }

                                state.refresh_filtered_view();
                            }
                        }
                        SidebarMode::Locations => {
                            // Use cached locations derived from the last `filter()` call
                            let locs = &state.cached_locations;
                            if let Some(idx) = state.cal_state.selected()
                                && let Some(l) = locs.get(idx)
                            {
                                let l_clone = l.full_key.clone();
                                state.selected_locations.clear();
                                state.selected_locations.insert(l_clone.clone());

                                if !state.expanded_locations.contains(&l_clone) {
                                    state.expanded_locations.insert(l_clone);
                                    if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                        cfg.expanded_locations =
                                            state.expanded_locations.iter().cloned().collect();
                                        let _ = cfg.save(state.ctx.as_ref());
                                    }
                                }

                                state.refresh_filtered_view();
                            }
                        }
                    }
                } else if state.mode == InputMode::Editing {
                    state.move_cursor_right();
                }
            }
            KeyCode::Enter => {
                // If the main list has focus, handle expand/collapse control items first.
                if state.active_focus == Focus::Main {
                    if let Some(idx) = state.list_state.selected()
                        && let Some(task_item) = state.tasks.get(idx)
                    {
                        match task_item {
                            TaskListItem::ExpandGroup(key, _) => {
                                state.expanded_done_groups.insert(key.clone());
                                state.refresh_filtered_view();
                                if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                    cfg.expanded_done_groups =
                                        state.expanded_done_groups.iter().cloned().collect();
                                    let _ = cfg.save(state.ctx.as_ref());
                                }
                                return None;
                            }
                            TaskListItem::CollapseGroup(key, _) => {
                                state.expanded_done_groups.remove(key);
                                state.refresh_filtered_view();
                                if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                    cfg.expanded_done_groups =
                                        state.expanded_done_groups.iter().cloned().collect();
                                    let _ = cfg.save(state.ctx.as_ref());
                                }
                                return None;
                            }
                            TaskListItem::Task(_) => {
                                open_action_menu(state);
                                return None;
                            }
                        }
                    }
                } else if state.active_focus == Focus::Sidebar {
                    match state.sidebar_mode {
                        SidebarMode::Calendars => {
                            let target_href = if let Some(idx) = state.cal_state.selected() {
                                let filtered = state.get_filtered_calendars();
                                filtered.get(idx).map(|c| c.href.clone())
                            } else {
                                None
                            };

                            if let Some(href) = target_href {
                                state.active_cal_href = Some(href.clone());
                                state.hidden_calendars.remove(&href);
                                state.refresh_filtered_view();
                                if href != LOCAL_CALENDAR_HREF {
                                    return Some(Action::SwitchCalendar(href));
                                }
                            }
                        }
                        SidebarMode::Categories => {
                            // Use cached categories derived from the last `filter()` call
                            let cats = &state.cached_categories;
                            if let Some(idx) = state.cal_state.selected()
                                && let Some(c) = cats.get(idx)
                            {
                                let c_clone = c.full_key.clone();
                                if state.selected_categories.contains(&c_clone) {
                                    state.selected_categories.remove(&c_clone);
                                } else {
                                    state.selected_categories.insert(c_clone.clone());
                                    // Auto-expand when selecting
                                    if !state.expanded_tags.contains(&c_clone) {
                                        state.expanded_tags.insert(c_clone);
                                        if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                            cfg.expanded_tags =
                                                state.expanded_tags.iter().cloned().collect();
                                            let _ = cfg.save(state.ctx.as_ref());
                                        }
                                    }
                                }
                                state.refresh_filtered_view();
                            }
                        }
                        SidebarMode::Locations => {
                            // Use cached locations derived from the last `filter()` call
                            let locs = &state.cached_locations;
                            if let Some(idx) = state.cal_state.selected()
                                && let Some(l) = locs.get(idx)
                            {
                                let l_clone = l.full_key.clone();
                                if state.selected_locations.contains(&l_clone) {
                                    state.selected_locations.remove(&l_clone);
                                } else {
                                    state.selected_locations.insert(l_clone.clone());
                                    // Auto-expand when selecting
                                    if !state.expanded_locations.contains(&l_clone) {
                                        state.expanded_locations.insert(l_clone);
                                        if let Ok(mut cfg) = Config::load(state.ctx.as_ref()) {
                                            cfg.expanded_locations =
                                                state.expanded_locations.iter().cloned().collect();
                                            let _ = cfg.save(state.ctx.as_ref());
                                        }
                                    }
                                }
                                state.refresh_filtered_view();
                            }
                        }
                    }
                }
            }
            KeyCode::Char('/') => {
                state.mode = InputMode::Searching;
                state.reset_input();
            }
            KeyCode::Char('a') => {
                state.mode = InputMode::Creating;
                state.reset_input();
                state.creating_with_desc = false;
                state.new_task_title.clear();
                state.message = rust_i18n::t!("new_task_prompt").to_string();
            }
            KeyCode::Char('e') => {
                if let Some(t) = state.get_selected_task() {
                    let smart_string = t.to_smart_string();
                    let uid = t.uid.clone();
                    state.input_buffer = smart_string;
                    state.cursor_position = state.input_buffer.chars().count();
                    state.editing_uid = Some(uid);
                    state.mode = InputMode::Editing;
                }
            }
            KeyCode::Char('E') => {
                if state.active_focus == Focus::Main
                    && let Some(t) = state.get_selected_task()
                {
                    let desc = t.description.clone();
                    let uid = t.uid.clone();
                    match run_external_editor(&desc, state.ctx.as_ref()) {
                        Ok(Some(new_desc)) => {
                            if new_desc != desc
                                && let Some((t_mut, _)) = state.store.get_task_mut(&uid)
                            {
                                t_mut.description = new_desc;
                                t_mut.sequence += 1;
                                let clone = t_mut.clone();
                                state.refresh_filtered_view();
                                let _ =
                                    action_tx.try_send(crate::tui::action::Action::PersistBatch(
                                        vec![crate::journal::Action::Update(clone)],
                                    ));
                            }
                            state.needs_redraw = true;
                            return None;
                        }
                        Ok(None) => {
                            // Fallback to built-in editor
                            state.input_buffer = desc.clone();
                            state.cursor_position = state.input_buffer.chars().count();
                            state.edit_scroll_offset = 0;
                            state.edit_scroll_x = 0;
                            state.editing_uid = Some(uid.clone());
                            state.mode = InputMode::EditingDescription;
                        }
                        Err(e) => {
                            state.message = e;
                            // Fallback to built-in editor
                            state.input_buffer = desc.clone();
                            state.cursor_position = state.input_buffer.chars().count();
                            state.edit_scroll_offset = 0;
                            state.edit_scroll_x = 0;
                            state.editing_uid = Some(uid.clone());
                            state.mode = InputMode::EditingDescription;
                            state.needs_redraw = true;
                        }
                    }
                }
            }
            _ => {}
        },
        InputMode::AddingSession => match key.code {
            KeyCode::Enter => {
                let input = state.input_buffer.clone();
                if let Some(session) = crate::model::parser::parse_session_input(&input) {
                    if let Some(uid) = state.get_selected_task().map(|t| t.uid.clone())
                        && let Some((t_mut, _)) = state.store.get_task_mut(&uid)
                    {
                        t_mut.add_session(session);
                        t_mut.sequence += 1;
                        let cloned = t_mut.clone();
                        state.refresh_filtered_view();
                        state.mode = InputMode::Normal;
                        state.reset_input();
                        let _ = action_tx.try_send(Action::PersistBatch(vec![
                            crate::journal::Action::Update(cloned),
                        ]));
                    }
                } else {
                    state.message = rust_i18n::t!("error_failed_to_parse_time").to_string();
                }
            }
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.reset_input();
            }
            KeyCode::Char(c) => state.enter_char(c),
            KeyCode::Backspace => state.delete_char(),
            KeyCode::Left => state.move_cursor_left(),
            KeyCode::Right => state.move_cursor_right(),
            _ => {}
        },

        InputMode::ManagingSessions => match key.code {
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.message = String::new();
            }
            KeyCode::Down | KeyCode::Char('j') => {
                let len = state.session_items.len();
                if len > 0 {
                    let current = state.session_selection_state.selected().unwrap_or(0);
                    let next = if current >= len - 1 { 0 } else { current + 1 };
                    state.session_selection_state.select(Some(next));
                }
            }
            KeyCode::Up | KeyCode::Char('k') => {
                let len = state.session_items.len();
                if len > 0 {
                    let current = state.session_selection_state.selected().unwrap_or(0);
                    let prev = if current == 0 { len - 1 } else { current - 1 };
                    state.session_selection_state.select(Some(prev));
                }
            }
            KeyCode::Delete | KeyCode::Char('x') => {
                if let Some(idx) = state.session_selection_state.selected()
                    && let Some(&(real_idx, _)) = state.session_items.get(idx)
                    && let Some(uid) = state.get_selected_task().map(|t| t.uid.clone())
                    && let Some((t_mut, _)) = state.store.get_task_mut(&uid)
                {
                    t_mut.remove_session(real_idx);
                    t_mut.sequence += 1;
                    let cloned = t_mut.clone();
                    state.mode = InputMode::Normal;
                    state.refresh_filtered_view();
                    // Send via PersistBatch
                    let tx = action_tx.clone();
                    tokio::spawn(async move {
                        let _ = tx
                            .send(Action::PersistBatch(vec![crate::journal::Action::Update(
                                cloned,
                            )]))
                            .await;
                    });
                }
            }
            _ => {}
        },

        InputMode::Moving => match key.code {
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.message = String::new();
            }
            KeyCode::Down | KeyCode::Char('j') => state.next_move_target(),
            KeyCode::Up | KeyCode::Char('k') => state.previous_move_target(),
            KeyCode::Enter => {
                let data = if let Some(task) = state.get_selected_task() {
                    if let Some(idx) = state.move_selection_state.selected() {
                        state
                            .move_targets
                            .get(idx)
                            .map(|target_cal| (task.uid.clone(), target_cal.href.clone()))
                    } else {
                        None
                    }
                } else {
                    None
                };

                if let Some((uid, target_href)) = data {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let intent = AppIntent::MoveTask {
                        uid: uid.clone(),
                        target_href: target_href.clone(),
                    };

                    let actions = state.store.apply_task_intent(&intent, &config);
                    state.refresh_filtered_view();
                    // Update alarms immediately if needed (task moved, though move doesn't clear completion)
                    // Moving a task keeps its alarms but might change visibility.
                    // For safety, re-sync alarms.
                    update_alarms(state);

                    state.message = rust_i18n::t!("moving_task").to_string();
                    state.mode = InputMode::Normal;

                    if !actions.is_empty() {
                        let tx = action_tx.clone();
                        tokio::spawn(async move {
                            let _ = tx.send(Action::PersistBatch(actions)).await;
                        });
                    }
                }
                state.mode = InputMode::Normal;
            }
            _ => {}
        },
        InputMode::SelectingExportSource => match key.code {
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.message = String::new();
            }
            KeyCode::Down | KeyCode::Char('j') => state.next_export_source(),
            KeyCode::Up | KeyCode::Char('k') => state.previous_export_source(),
            KeyCode::Enter => {
                if let Some(idx) = state.export_source_selection_state.selected()
                    && let Some(source) = state.export_source_calendars.get(idx)
                {
                    // Step 2: Now select destination remote calendar
                    state.export_targets = state
                        .calendars
                        .iter()
                        .filter(|c| {
                            !c.href.starts_with("local://")
                                && !state.disabled_calendars.contains(&c.href)
                        })
                        .cloned()
                        .collect();
                    if !state.export_targets.is_empty() {
                        state.export_selection_state.select(Some(0));
                        state.mode = InputMode::Exporting;
                        state.message = rust_i18n::t!(
                            "exporting_select_destination",
                            name = source.name.clone()
                        )
                        .to_string();
                    } else {
                        state.mode = InputMode::Normal;
                        state.message =
                            rust_i18n::t!("error_no_remote_calendars_export").to_string();
                    }
                }
            }
            _ => {}
        },
        InputMode::Exporting => match key.code {
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.message = String::new();
            }
            KeyCode::Down | KeyCode::Char('j') => state.next_export_target(),
            KeyCode::Up | KeyCode::Char('k') => state.previous_export_target(),
            KeyCode::Enter => {
                if let Some(source_idx) = state.export_source_selection_state.selected()
                    && let Some(source) = state.export_source_calendars.get(source_idx)
                    && let Some(target_idx) = state.export_selection_state.selected()
                    && let Some(target) = state.export_targets.get(target_idx)
                {
                    let source_href = source.href.clone();
                    let target_href = target.href.clone();
                    state.mode = InputMode::Normal;
                    state.message = String::new();
                    return Some(Action::MigrateLocal(source_href, target_href));
                }
            }
            _ => {}
        },
        InputMode::RelationshipBrowsing => match key.code {
            KeyCode::Esc => {
                state.mode = InputMode::Normal;
                state.message = String::new();
            }
            KeyCode::Down | KeyCode::Char('j') => {
                let len = state.relationship_items.len();
                if len > 0 {
                    let current = state.relationship_selection_state.selected().unwrap_or(0);
                    let next = if current >= len - 1 { 0 } else { current + 1 };
                    state.relationship_selection_state.select(Some(next));
                }
            }
            KeyCode::Up | KeyCode::Char('k') => {
                let len = state.relationship_items.len();
                if len > 0 {
                    let current = state.relationship_selection_state.selected().unwrap_or(0);
                    let prev = if current == 0 { len - 1 } else { current - 1 };
                    state.relationship_selection_state.select(Some(prev));
                }
            }
            KeyCode::Delete | KeyCode::Char('x') => {
                if let Some(idx) = state.relationship_selection_state.selected()
                    && let Some((target_uid, _, rel_type)) = state.relationship_items.get(idx)
                    && let Some(curr_uid) = state.get_selected_task().map(|t| t.uid.clone())
                {
                    let config = Config::load(state.ctx.as_ref()).unwrap_or_default();
                    let mut intent = None;

                    if rel_type == "dependency" {
                        intent = Some(AppIntent::RemoveDependency {
                            uid: curr_uid.clone(),
                            blocker_uid: target_uid.clone(),
                        });
                    } else if rel_type == "related_to" {
                        intent = Some(AppIntent::RemoveRelatedTo {
                            uid: curr_uid.clone(),
                            related_uid: target_uid.clone(),
                        });
                    } else if rel_type == "related_from" {
                        intent = Some(AppIntent::RemoveRelatedTo {
                            uid: target_uid.clone(),
                            related_uid: curr_uid.clone(),
                        });
                    } else if rel_type == "blocking" {
                        intent = Some(AppIntent::RemoveDependency {
                            uid: target_uid.clone(),
                            blocker_uid: curr_uid.clone(),
                        });
                    } else if rel_type == "parent" {
                        intent = Some(AppIntent::RemoveParent {
                            uid: curr_uid.clone(),
                        });
                    }

                    if let Some(i) = intent {
                        let actions = state.store.apply_task_intent(&i, &config);
                        state.refresh_filtered_view();
                        if !actions.is_empty() {
                            let tx = action_tx.clone();
                            tokio::spawn(async move {
                                let _ = tx.send(Action::PersistBatch(actions)).await;
                            });
                        }
                    }
                    state.mode = InputMode::Normal;
                    state.message = String::new();
                }
            }
            KeyCode::Enter => {
                if let Some(idx) = state.relationship_selection_state.selected()
                    && let Some((target_uid, _, _)) = state.relationship_items.get(idx)
                {
                    // Jump to the target task
                    let target_uid = target_uid.clone();

                    // Find which calendar this task belongs to
                    if let Some(href) = state.store.index.get(&target_uid).cloned() {
                        // Clear filters that might hide the task
                        state.active_search_query.clear();
                        state.selected_categories.clear();
                        state.selected_locations.clear();

                        // Switch calendar if needed and unhide
                        if state.active_cal_href.as_ref() != Some(&href) {
                            state.active_cal_href = Some(href.clone());
                            state.hidden_calendars.remove(&href);
                        }

                        state.refresh_filtered_view();

                        // Find and select the target task in the list
                        if let Some(task_idx) = state.find_task_index_by_uid(&target_uid) {
                            state.list_state.select(Some(task_idx));
                        }

                        state.mode = InputMode::Normal;
                        state.message = rust_i18n::t!("jumped_to_task").to_string();
                    } else {
                        state.message = rust_i18n::t!("error_task_not_found").to_string();
                        state.mode = InputMode::Normal;
                    }
                }
            }
            _ => {}
        },
    }
    None
}
