//! Mouse input handling for [`AgentView`]: the `handle_mouse` event handler
//! (click-to-focus, hit-testing of cached click rects, per-pane click
//! dispatch) and the scrollbar click helper.
//!
//! Extracted from `agent_view.rs` as a sibling `impl AgentView` block (same
//! pattern as `queue_edit.rs`).
//!
//! Hit-tests here assume the cached rects come from the last rendered frame.
use super::actions::Action;
use super::agent_view::{
    AgentPane, AgentView, CONTEXT_CLICK_DEBOUNCE_MS, MULTI_CLICK_TIMEOUT_MS, PromptInputMode,
    TextClickState, app_should_open_link_on_click, has_native_link_hover, is_link_modifier_held,
    is_text_selection_on_double_click,
};
use super::app_view::InputOutcome;
use crate::scrollback::block::BlockContent;
use crate::views::btw_overlay::BTW_OVERLAY_ENTRY_IDX;
use crate::views::prompt_widget::PromptEvent;
use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
use std::time::Instant;
impl AgentView {
    /// Handle mouse events: click-to-focus, forward to prompt textarea.
    ///
    /// Scroll events are handled at app level (not here).
    pub(super) fn handle_mouse(&mut self, mouse: &MouseEvent) -> InputOutcome {
        match mouse.kind {
            MouseEventKind::Down(MouseButton::Left) => {
                self.left_mouse_down = true;
                if self.hit_todo_close.contains(mouse.column, mouse.row) {
                    self.todo.overlay.escape();
                    self.todo.on_state_change();
                    if self.active_pane == AgentPane::Todo {
                        self.set_active_pane(AgentPane::Scrollback, false);
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_queue_close.contains(mouse.column, mouse.row) {
                    self.queue.overlay.escape();
                    self.queue.on_state_change();
                    if self.active_pane == AgentPane::Queue {
                        self.set_active_pane(AgentPane::Scrollback, false);
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_queue_badge.contains(mouse.column, mouse.row) {
                    self.toggle_queue_pane();
                    return InputOutcome::Changed;
                }
                if self.hit_bg_status.contains(mouse.column, mouse.row) {
                    self.tasks.overlay.toggle();
                    self.tasks.on_state_change();
                    if self.tasks.overlay.focused {
                        self.set_active_pane(AgentPane::Tasks, false);
                    } else if self.active_pane == AgentPane::Tasks {
                        self.set_active_pane(AgentPane::Scrollback, false);
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_goal_status.contains(mouse.column, mouse.row) {
                    if self.goal_state.is_some() {
                        self.show_goal_detail = !self.show_goal_detail;
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_context.contains(mouse.column, mouse.row) {
                    let now = Instant::now();
                    let too_soon = self.last_context_click_at.is_some_and(|t| {
                        now.duration_since(t).as_millis() < CONTEXT_CLICK_DEBOUNCE_MS
                    });
                    if too_soon {
                        return InputOutcome::Unchanged;
                    }
                    self.last_context_click_at = Some(now);
                    return InputOutcome::Action(Action::ShowContextInfo);
                }
                if self.hit_plan_button.contains(mouse.column, mouse.row) {
                    if self.plan_approval_view.is_some() {
                        self.reopen_plan_approval();
                    } else {
                        self.show_plan_preview();
                    }
                    return InputOutcome::Changed;
                }
                if self
                    .hit_plan_approval_status
                    .contains(mouse.column, mouse.row)
                {
                    if self.plan_approval_view.is_some() {
                        self.reopen_plan_approval();
                    } else {
                        self.show_plan_preview();
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_catalog_close.contains(mouse.column, mouse.row) {
                    self.catalog.overlay.escape();
                    self.catalog.on_state_change();
                    if self.active_pane == AgentPane::Catalog {
                        self.set_active_pane(AgentPane::Scrollback, false);
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_bg_close.contains(mouse.column, mouse.row) {
                    self.tasks.overlay.escape();
                    self.tasks.on_state_change();
                    if self.active_pane == AgentPane::Tasks {
                        self.set_active_pane(AgentPane::Scrollback, false);
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_bg_button.contains(mouse.column, mouse.row)
                    && !self.pos_occluded(mouse.column, mouse.row)
                {
                    return InputOutcome::Action(Action::DemoteToBackground);
                }
                if self.hit_cancel_button.contains(mouse.column, mouse.row)
                    && !self.pos_occluded(mouse.column, mouse.row)
                {
                    self.cancel_trigger_hint = Some(crate::app::actions::CancelTrigger::Mouse);
                    return InputOutcome::Action(Action::CancelTurn);
                }
                if let Some(idx) = self.follow_up_chip_at(mouse.column, mouse.row)
                    && let Some(text) = self
                        .follow_ups
                        .as_ref()
                        .and_then(|f| f.suggestions.get(idx))
                        .cloned()
                {
                    return InputOutcome::Action(Action::SubmitFollowUp(text));
                }
                if self.hit_cwd.contains(mouse.column, mouse.row) {
                    let path = self.session.cwd.display().to_string();
                    self.copy_to_clipboard(&path);
                    return InputOutcome::Changed;
                }
                if self.hit_badge.contains(mouse.column, mouse.row) {
                    self.todo.overlay.toggle();
                    self.todo.on_state_change();
                    if self.todo.overlay.focused {
                        self.set_active_pane(AgentPane::Todo, false);
                    } else if self.active_pane == AgentPane::Todo {
                        self.set_active_pane(AgentPane::Scrollback, false);
                    }
                    return InputOutcome::Changed;
                }
                if self.hit_follow_indicator.contains(mouse.column, mouse.row) {
                    self.scrollback.goto_bottom();
                    return InputOutcome::Changed;
                }
                if let Some(hd_area) = self.history_dropdown_area
                    && hd_area.contains((mouse.column, mouse.row).into())
                    && self.prompt.history_search.is_active()
                {
                    let vis_row = (mouse.row - hd_area.y) as usize;
                    let sel = self.prompt.history_search.selected;
                    let visible_rows = hd_area.height as usize;
                    let scroll_offset = if sel >= visible_rows {
                        sel - (visible_rows - 1)
                    } else {
                        0
                    };
                    let row_idx = scroll_offset + vis_row;
                    self.prompt.history_search.set_hovered(Some(row_idx));
                    if self.prompt.history_search.select_hovered()
                        && let Some(text) = self
                            .prompt
                            .history_search
                            .selected_text()
                            .map(str::to_owned)
                    {
                        self.prompt.history_search.deactivate();
                        if self.prompt_input_mode != PromptInputMode::Remember
                            && let Some(cmd) = text.strip_prefix("! ")
                        {
                            self.prompt_input_mode = PromptInputMode::Bash;
                            self.prompt.set_text(cmd);
                        } else if self.prompt_input_mode == PromptInputMode::Bash {
                            self.prompt_input_mode = PromptInputMode::Normal;
                            self.prompt.set_text(&text);
                        } else {
                            self.prompt.set_text(&text);
                        }
                        let len = self.prompt.textarea.text().len();
                        self.prompt.textarea.set_cursor(len);
                        self.prompt.file_search.clear_context();
                    }
                    self.set_active_pane(AgentPane::Prompt, false);
                    return InputOutcome::Changed;
                }
                if let Some(dd_area) = self.dropdown_items_area
                    && dd_area.contains((mouse.column, mouse.row).into())
                {
                    let has_scrollbar =
                        self.prompt.file_search.result_count() > dd_area.height as usize;
                    let on_scrollbar = has_scrollbar
                        && mouse.column >= dd_area.x + dd_area.width.saturating_sub(2);
                    if on_scrollbar {
                        let click_frac =
                            (mouse.row - dd_area.y) as f64 / dd_area.height.max(1) as f64;
                        let target =
                            (click_frac * self.prompt.file_search.result_count() as f64) as usize;
                        let max = self.prompt.file_search.result_count().saturating_sub(1);
                        self.prompt.file_search.move_selection(
                            target.min(max) as isize - self.prompt.file_search.selected() as isize,
                        );
                    } else {
                        let row_idx = (mouse.row - dd_area.y) as usize
                            + self.prompt.file_search.scroll_offset();
                        self.prompt.file_search.set_hovered(Some(row_idx));
                        if self.prompt.file_search.select_hovered() {
                            self.prompt.accept_file_search_result();
                        }
                    }
                    self.set_active_pane(AgentPane::Prompt, false);
                    return InputOutcome::Changed;
                }
                if let Some(dd_area) = self.slash_dropdown_items_area
                    && dd_area.contains((mouse.column, mouse.row).into())
                {
                    let snap = self.prompt.slash_snapshot();
                    let has_scrollbar = self.slash_dropdown_hit.has_scrollbar;
                    let on_scrollbar = has_scrollbar
                        && mouse.column >= dd_area.x + dd_area.width.saturating_sub(2);
                    if on_scrollbar {
                        let click_frac =
                            (mouse.row - dd_area.y) as f64 / dd_area.height.max(1) as f64;
                        let target = (click_frac * snap.matches.len() as f64) as usize;
                        let max = snap.matches.len().saturating_sub(1);
                        let delta = target.min(max) as isize - snap.selected as isize;
                        self.prompt.slash_move_selection(delta);
                        self.prompt.slash_preview_current_selection();
                    } else if let Some(&item_idx) = self
                        .slash_dropdown_hit
                        .row_items
                        .get((mouse.row - dd_area.y) as usize)
                    {
                        self.prompt.set_slash_hovered(Some(item_idx));
                        if self.prompt.select_slash_hovered() {
                            self.prompt.slash_commit_preview();
                            self.prompt.accept_slash_completion(&self.session.models);
                        }
                    }
                    self.set_active_pane(AgentPane::Prompt, false);
                    return InputOutcome::Changed;
                }
                if let Some(dd_area) = self.completion_dropdown_items_area
                    && dd_area.contains((mouse.column, mouse.row).into())
                {
                    let dd = &self.prompt.suggestions.dropdown;
                    let has_scrollbar = dd.items.len() > dd_area.height as usize;
                    let on_scrollbar = has_scrollbar
                        && mouse.column >= dd_area.x + dd_area.width.saturating_sub(2);
                    if on_scrollbar {
                        let click_frac =
                            (mouse.row - dd_area.y) as f64 / dd_area.height.max(1) as f64;
                        let total = self.prompt.suggestions.dropdown.items.len();
                        let target = (click_frac * total as f64) as usize;
                        let max = total.saturating_sub(1);
                        let delta = target.min(max) as isize
                            - self.prompt.suggestions.dropdown.selected as isize;
                        self.prompt.completion_dropdown_move(delta);
                    } else {
                        let offset = crate::views::completion_dropdown::scroll_offset(
                            &self.prompt.suggestions.dropdown,
                        );
                        let row_idx = (mouse.row - dd_area.y) as usize + offset;
                        self.prompt.set_completion_hovered(Some(row_idx));
                        if self.prompt.select_completion_hovered() {
                            self.accept_completion_dropdown_item();
                        }
                    }
                    self.set_active_pane(AgentPane::Prompt, false);
                    return InputOutcome::Changed;
                }
                if self.hit_scrollbar.contains(mouse.column, mouse.row) {
                    self.set_active_pane(AgentPane::Scrollback, false);
                    self.scrollbar_dragging = true;
                    self.apply_scrollbar_click(mouse.row);
                    return InputOutcome::Changed;
                }
                if let Some(rail) = self.timeline_rail.as_ref()
                    && rail.rect.contains((mouse.column, mouse.row).into())
                {
                    let target = rail
                        .hit(mouse.column, mouse.row)
                        .and_then(|hit| crate::views::timeline::chevron_target(rail, hit));
                    if let Some(turn_idx) = target {
                        self.set_active_pane(AgentPane::Scrollback, false);
                        self.scrollback.jump_to_turn(turn_idx);
                        return InputOutcome::Changed;
                    }
                    return InputOutcome::Unchanged;
                }
                if self.hit_sb_copy.contains(mouse.column, mouse.row) {
                    return InputOutcome::Action(Action::CopyBlockContent);
                }
                if self.hit_sb_view.contains(mouse.column, mouse.row) {
                    return InputOutcome::Action(Action::OpenBlockViewer);
                }
                if self.last_btw_area.area() > 0
                    && self
                        .last_btw_area
                        .contains((mouse.column, mouse.row).into())
                    && !self.hit_btw_close.contains(mouse.column, mouse.row)
                {
                    self.set_active_pane(AgentPane::Prompt, false);
                    self.btw_focused = true;
                    if !has_native_link_hover()
                        && is_link_modifier_held(mouse.modifiers)
                        && !self.pos_occluded(mouse.column, mouse.row)
                        && let Some(link) = self.visible_link_map.link_at(mouse.column, mouse.row)
                    {
                        self.pending_link_click = app_should_open_link_on_click(link)
                            .then(|| (mouse.column, mouse.row, link.url.to_string()));
                        self.pending_scrollback_click = None;
                        return InputOutcome::Changed;
                    }
                    self.pending_link_click = None;
                    if self.begin_pending_btw_text_drag(mouse) {
                        return InputOutcome::Changed;
                    }
                    return InputOutcome::Unchanged;
                }
                match self.pane_areas.hit_test(mouse.column, mouse.row) {
                    Some(AgentPane::Todo) => {
                        self.set_active_pane(AgentPane::Todo, false);
                        self.todo.handle_mouse(
                            mouse.kind,
                            mouse.column,
                            mouse.row,
                            self.pane_areas.todo,
                        );
                        InputOutcome::Changed
                    }
                    Some(AgentPane::Queue) => {
                        if let Some(id) = self.queue.delete_click(mouse.column, mouse.row) {
                            let row = self.queue.row_ref(id);
                            let is_server = matches!(
                                row.as_ref().map(|r| r.origin),
                                Some(crate::views::queue_pane::QueueRowOrigin::Server)
                            );
                            if is_server {
                                if let (Some(_sid), Some(row)) =
                                    (self.session.session_id.as_ref(), row)
                                    && let Some(server_id) = row.server_id
                                {
                                    self.shared_queue.retain(|e| e.id != server_id);
                                    if self.visible_queue_is_empty() {
                                        self.hide_queue_pane();
                                    }
                                    self.maybe_push_parked_marker();
                                    return InputOutcome::Action(Action::QueueRemoveShared {
                                        id: server_id,
                                        expected_version: row.version,
                                    });
                                }
                                return InputOutcome::Changed;
                            }
                            let was_drain_blocked = self.drain_blocked();
                            self.remove_local_queue_row(id);
                            self.maybe_push_parked_marker();
                            if was_drain_blocked {
                                return InputOutcome::Action(Action::DrainQueue);
                            }
                            return InputOutcome::Changed;
                        }
                        if let Some(id) = self.queue.send_now_click(mouse.column, mouse.row)
                            && self.session.state.is_turn_running()
                            && let InputOutcome::Action(action) = self.force_interject_queue_row(id)
                        {
                            return InputOutcome::Action(action);
                        }
                        self.set_active_pane(AgentPane::Queue, false);
                        self.queue.handle_mouse(
                            mouse.kind,
                            mouse.column,
                            mouse.row,
                            self.pane_areas.queue,
                        );
                        InputOutcome::Changed
                    }
                    Some(AgentPane::Prompt) => {
                        let was_collapsed = self.active_pane != AgentPane::Prompt
                            && self.scrollback.appearance().prompt.collapse_unfocused;
                        self.set_active_pane(AgentPane::Prompt, false);
                        self.btw_focused = false;
                        if !was_collapsed {
                            if matches!(self.prompt.handle_mouse(mouse), PromptEvent::Edited) {
                                self.prompt.refresh_slash(&self.session.models);
                                if let Some(eff) = self.notify_suggestion_text_changed() {
                                    self.pending_effects.push(eff);
                                }
                            }
                            let now = std::time::Instant::now();
                            if let Some(last) = self.last_prompt_click_ms
                                && now.duration_since(last).as_millis() < MULTI_CLICK_TIMEOUT_MS
                            {
                                if self.prompt.file_ref_near_cursor()
                                    && let Some((path, initial_range)) =
                                        self.prompt.file_ref_element_at_cursor()
                                {
                                    self.prompt.textarea.begin_undo_group();
                                    self.open_line_viewer(
                                        &std::path::PathBuf::from(path),
                                        initial_range,
                                    );
                                } else if self.prompt.expand_paste_element_at_cursor() {
                                    self.prompt.refresh_slash(&self.session.models);
                                }
                            }
                            self.last_prompt_click_ms = Some(now);
                        }
                        InputOutcome::Changed
                    }
                    Some(AgentPane::Tasks) => {
                        use crate::views::tasks_pane::TaskEntryId;
                        self.set_active_pane(AgentPane::Tasks, false);
                        for (entry_id, rect) in &self.tasks.kill_button_rects {
                            if rect.contains((mouse.column, mouse.row).into()) {
                                match entry_id {
                                    TaskEntryId::BgTask(tid) => {
                                        return InputOutcome::Action(Action::KillBgTask(
                                            tid.clone(),
                                        ));
                                    }
                                    TaskEntryId::Agent(sid) => {
                                        return InputOutcome::Action(Action::KillSubagent(
                                            sid.clone(),
                                        ));
                                    }
                                    TaskEntryId::Scheduled(tid) => {
                                        return InputOutcome::Action(Action::CancelScheduledTask(
                                            tid.clone(),
                                        ));
                                    }
                                }
                            }
                        }
                        for (entry_id, rect) in &self.tasks.view_button_rects {
                            if rect.contains((mouse.column, mouse.row).into()) {
                                match entry_id {
                                    TaskEntryId::BgTask(tid) => {
                                        let already_open = self
                                            .block_viewer
                                            .as_ref()
                                            .and_then(|v| v.bg_task_id.as_deref())
                                            == Some(tid);
                                        if already_open {
                                            self.block_viewer = None;
                                            return InputOutcome::Changed;
                                        }
                                        if let Some(task) = self.session.bg_tasks.get(tid) {
                                            let entry_id =
                                                task.scrollback_entry_id.unwrap_or_else(|| {
                                                    crate::scrollback::entry::EntryId::new(0)
                                                });
                                            let is_running = task.status
                                                == crate::app::agent::BgTaskStatus::Running;
                                            self.block_viewer = Some(
                                                crate::views::block_viewer::BlockViewerPane::for_bg_task(
                                                    entry_id,
                                                    tid,
                                                    &task.stdout,
                                                    is_running,
                                                ),
                                            );
                                            self.set_active_pane(AgentPane::Scrollback, true);
                                            return InputOutcome::Changed;
                                        }
                                    }
                                    TaskEntryId::Agent(sid) => {
                                        if let Some(child_sid) = self
                                            .subagent_sessions
                                            .iter()
                                            .find(|(_, info)| {
                                                info.subagent_id.as_ref() == sid.as_str()
                                            })
                                            .map(|(k, _)| k.clone())
                                            && self.subagent_views.contains_key(&child_sid)
                                        {
                                            self.open_subagent_fullscreen(child_sid);
                                            return InputOutcome::Changed;
                                        }
                                    }
                                    TaskEntryId::Scheduled(_) => {}
                                }
                            }
                        }
                        self.tasks.handle_mouse(
                            mouse.kind,
                            mouse.column,
                            mouse.row,
                            self.pane_areas.tasks,
                        );
                        if let Some(group) = self.tasks.selected_header_group() {
                            self.tasks.toggle_group(group);
                            return InputOutcome::Changed;
                        }
                        let now = Instant::now();
                        if let Some(last) = self.last_bg_click
                            && now.duration_since(last).as_millis() < MULTI_CLICK_TIMEOUT_MS
                        {
                            if let Some(task_id) =
                                self.tasks.selected_task_id().map(|s| s.to_string())
                                && let Some(task) = self.session.bg_tasks.get(&task_id)
                            {
                                let entry_id = task
                                    .scrollback_entry_id
                                    .unwrap_or_else(|| crate::scrollback::entry::EntryId::new(0));
                                let is_running =
                                    task.status == crate::app::agent::BgTaskStatus::Running;
                                self.block_viewer =
                                    Some(crate::views::block_viewer::BlockViewerPane::for_bg_task(
                                        entry_id,
                                        &task_id,
                                        &task.stdout,
                                        is_running,
                                    ));
                                self.set_active_pane(AgentPane::Scrollback, true);
                                self.last_bg_click = None;
                                return InputOutcome::Changed;
                            }
                            if let Some(child_sid) = self.tasks.selected_child_session_id()
                                && self.subagent_views.contains_key(child_sid)
                            {
                                self.open_subagent_fullscreen(child_sid.to_string());
                                self.last_bg_click = None;
                                return InputOutcome::Changed;
                            }
                        }
                        self.last_bg_click = Some(now);
                        InputOutcome::Changed
                    }
                    Some(AgentPane::Catalog) => {
                        self.set_active_pane(AgentPane::Catalog, false);
                        self.catalog.handle_mouse(
                            mouse.kind,
                            mouse.column,
                            mouse.row,
                            self.pane_areas.catalog,
                        );
                        InputOutcome::Changed
                    }
                    Some(AgentPane::Scrollback) => {
                        self.set_active_pane(AgentPane::Scrollback, false);
                        if self.block_viewer.is_some() {
                            self.handle_block_viewer_mouse(mouse);
                            return InputOutcome::Changed;
                        }
                        self.persistent_text_selection = None;
                        self.table_selection_geometry = None;
                        self.selection_created_at = None;
                        if !has_native_link_hover()
                            && is_link_modifier_held(mouse.modifiers)
                            && !self.pos_occluded(mouse.column, mouse.row)
                            && let Some(link) =
                                self.visible_link_map.link_at(mouse.column, mouse.row)
                        {
                            self.pending_link_click = app_should_open_link_on_click(link)
                                .then(|| (mouse.column, mouse.row, link.url.to_string()));
                            self.pending_scrollback_click = None;
                            return InputOutcome::Changed;
                        }
                        self.pending_link_click = None;
                        self.last_question_click = None;
                        self.last_permission_click = None;
                        self.pending_scrollback_click = Some((mouse.column, mouse.row));
                        tracing::debug!(
                            event = "scrollback_mouse_down", col = mouse.column, row =
                            mouse.row, area = ? self.pane_areas.scrollback, content_area
                            = ? self.last_scrollback_selection_model.content_area, ranges
                            = self.last_scrollback_selection_model.ranges.len(), blocks =
                            self.last_scrollback_selection_model.visible_blocks.len(),
                            hovered_entry = ? self.hovered_entry, "scrollback mouse down"
                        );
                        if self.begin_pending_text_drag(mouse) {
                            return InputOutcome::Changed;
                        }
                        self.begin_pending_block_drag(mouse);
                        self.deferred_text_press = Some((mouse.column, mouse.row));
                        InputOutcome::Changed
                    }
                    None => {
                        let scrollback = self.pane_areas.scrollback;
                        let prompt = self.pane_areas.prompt;
                        let above_prompt_strip = self.block_viewer.is_none()
                            && scrollback.area() > 0
                            && prompt.area() > 0
                            && mouse.row >= scrollback.y.saturating_add(scrollback.height)
                            && mouse.row < prompt.y;
                        if above_prompt_strip {
                            self.persistent_text_selection = None;
                            self.table_selection_geometry = None;
                            self.selection_created_at = None;
                            self.deferred_text_press = Some((mouse.column, mouse.row));
                            InputOutcome::Changed
                        } else {
                            InputOutcome::Unchanged
                        }
                    }
                }
            }
            MouseEventKind::Drag(MouseButton::Left) => {
                self.pending_link_click = None;
                tracing::debug!(
                    event = "scrollback_mouse_drag", col = mouse.column, row = mouse.row,
                    pending = ? self.pending_text_drag, active = ? self.drag_selection,
                    "scrollback mouse drag"
                );
                self.handle_scrollback_drag_motion(mouse)
            }
            MouseEventKind::Up(MouseButton::Left) => {
                self.left_mouse_down = false;
                tracing::debug!(
                    event = "scrollback_mouse_up", col = mouse.column, row = mouse.row,
                    pending = ? self.pending_text_drag, active = ? self.drag_selection,
                    "scrollback mouse up"
                );
                if self.scrollbar_dragging {
                    self.scrollbar_dragging = false;
                }
                self.deferred_text_press = None;
                if self.drag_selection.is_some() {
                    return if self.finish_text_drag() {
                        InputOutcome::Changed
                    } else {
                        InputOutcome::Unchanged
                    };
                }
                if self.block_drag_selection.is_some() {
                    return if self.finish_block_drag() {
                        InputOutcome::Changed
                    } else {
                        InputOutcome::Unchanged
                    };
                }
                let had_pending_text_drag = self.pending_text_drag.take().is_some();
                let _had_pending_block_drag = self.pending_block_drag.take().is_some();
                if let Some((lc, lr, url)) = self.pending_link_click.take()
                    && mouse.column == lc
                    && mouse.row == lr
                {
                    return InputOutcome::Action(Action::OpenUrl(url));
                }
                if self.active_pane == AgentPane::Scrollback {
                    if let Some((click_col, click_row)) = self.pending_scrollback_click.take() {
                        let now = Instant::now();
                        if is_text_selection_on_double_click() {
                            let exact_text_hit = self
                                .last_scrollback_selection_model
                                .hit_test_text_exact(click_col, click_row);
                            if let Some(hit) = exact_text_hit {
                                let text_click_count = self.count_text_click(now, &hit);
                                let handled = match text_click_count {
                                    1 => {
                                        self.persistent_text_selection = None;
                                        self.table_selection_geometry = None;
                                        self.selection_created_at = None;
                                        if hit.entry_idx != BTW_OVERLAY_ENTRY_IDX {
                                            self.scrollback.set_selected(Some(hit.entry_idx));
                                        }
                                        true
                                    }
                                    2 => {
                                        self.select_word_at(&hit);
                                        true
                                    }
                                    3 => {
                                        if !self.select_cell_at(&hit) {
                                            self.select_line_at(&hit);
                                        }
                                        true
                                    }
                                    _ => false,
                                };
                                if handled {
                                    self.last_text_click = Some(TextClickState {
                                        time: now,
                                        entry_idx: hit.entry_idx,
                                        range_id: hit.range_id,
                                        block_line_idx: hit.block_line_idx,
                                        col_within_range: hit.col_within_range,
                                        click_count: text_click_count,
                                    });
                                    self.last_click = None;
                                    return InputOutcome::Changed;
                                }
                                self.last_text_click = None;
                            }
                        }
                        self.last_text_click = None;
                        if let Some(outcome) = self.handle_inline_media_click(click_col, click_row)
                        {
                            return outcome;
                        }
                        let hit_idx = self
                            .scrollback
                            .entry_index_at_screen_row(click_row, self.pane_areas.scrollback);
                        if let Some(idx) = hit_idx {
                            let selectable = self
                                .scrollback
                                .get(idx)
                                .is_some_and(|e| e.block.is_selectable());
                            if selectable {
                                let header_row_click = self
                                    .scrollback
                                    .get_cached_entry_layouts()
                                    .and_then(|l| l.get(idx))
                                    .is_some_and(|i| {
                                        i.verb_group_header && i.group_collapse_header
                                    })
                                    && self
                                        .scrollback
                                        .entry_screen_area(idx, self.pane_areas.scrollback)
                                        .is_some_and(|(a, _, _)| click_row == a.y);
                                let (last_click, show_word_select_tip) =
                                    self.handle_scrollback_click(now, idx, header_row_click);
                                self.last_click = last_click;
                                if show_word_select_tip {
                                    return InputOutcome::Action(Action::ShowWordSelectTip);
                                }
                                return InputOutcome::Changed;
                            }
                        } else if let Some((last_time, last_idx, last_count)) = self.last_click
                            && now.duration_since(last_time).as_millis() < MULTI_CLICK_TIMEOUT_MS
                            && last_count >= 2
                        {
                            let (last_click, show_word_select_tip) =
                                self.handle_scrollback_click(now, last_idx, false);
                            self.last_click = last_click;
                            if show_word_select_tip {
                                return InputOutcome::Action(Action::ShowWordSelectTip);
                            }
                            return InputOutcome::Changed;
                        }
                        if had_pending_text_drag {
                            tracing::debug!(
                                event = "pending_text_drag_released_without_click_target",
                                col = click_col,
                                row = click_row,
                                "scrollback pending text drag released without valid click target"
                            );
                        }
                    }
                } else {
                    self.pending_scrollback_click = None;
                }
                if self.active_pane == AgentPane::Todo {
                    self.todo.handle_mouse(
                        mouse.kind,
                        mouse.column,
                        mouse.row,
                        self.pane_areas.todo,
                    );
                }
                if self.active_pane == AgentPane::Prompt {
                    let event = self.prompt.handle_mouse(mouse);
                    if matches!(event, PromptEvent::Edited)
                        && let Some(eff) = self.notify_suggestion_text_changed()
                    {
                        self.pending_effects.push(eff);
                    }
                    InputOutcome::Changed
                } else {
                    InputOutcome::Unchanged
                }
            }
            MouseEventKind::Moved => {
                tracing::debug!(
                    event = "scrollback_mouse_moved", col = mouse.column, row = mouse
                    .row, pending = ? self.pending_text_drag, active = ? self
                    .drag_selection, left_mouse_down = self.left_mouse_down,
                    "scrollback mouse moved"
                );
                if self.left_mouse_down
                    && (self.pending_text_drag.is_some()
                        || self.drag_selection.is_some()
                        || self.pending_block_drag.is_some()
                        || self.block_drag_selection.is_some()
                        || self.deferred_text_press.is_some())
                {
                    self.pending_link_click = None;
                    let outcome = self.handle_scrollback_drag_motion(mouse);
                    if !matches!(outcome, InputOutcome::Unchanged) {
                        return outcome;
                    }
                }
                let suppress_scrollback_hover = self.pending_text_drag.is_some()
                    || self.drag_selection.is_some()
                    || self.pending_block_drag.is_some()
                    || self.block_drag_selection.is_some()
                    || self.deferred_text_press.is_some();
                let hit = self.pane_areas.hit_test(mouse.column, mouse.row);
                let on_rail = self
                    .timeline_rail
                    .as_ref()
                    .is_some_and(|r| r.rect.contains((mouse.column, mouse.row).into()));
                let new_timeline_hover = self
                    .timeline_rail
                    .as_ref()
                    .and_then(|r| r.hit(mouse.column, mouse.row));
                let new_hover = if on_rail || suppress_scrollback_hover {
                    None
                } else {
                    hit.and_then(|pane| match pane {
                        AgentPane::Scrollback => self
                            .scrollback
                            .entry_index_at_screen_row(mouse.row, self.pane_areas.scrollback)
                            .filter(|&idx| {
                                self.scrollback
                                    .get(idx)
                                    .is_some_and(|e| e.block.is_selectable())
                            }),
                        AgentPane::Todo
                        | AgentPane::Queue
                        | AgentPane::Prompt
                        | AgentPane::Tasks
                        | AgentPane::Catalog => None,
                    })
                };
                let new_prompt_hover = hit == Some(AgentPane::Prompt)
                    && self.scrollback.appearance().prompt.mouse_hover;
                let old_mouse_pos = self.last_mouse_pos;
                self.last_mouse_pos = (mouse.column, mouse.row);
                if old_mouse_pos != self.last_mouse_pos {
                    self.last_mouse_moved_at = Some(Instant::now());
                }
                let mut changed =
                    new_hover != self.hovered_entry || new_prompt_hover != self.hovered_prompt;
                if new_hover.is_some()
                    && old_mouse_pos != self.last_mouse_pos
                    && let Some(idx) = new_hover
                    && let Some(entry) = self.scrollback.get(idx)
                    && (entry.block.is_user_prompt()
                        || matches!(
                            entry.block,
                            crate::scrollback::block::RenderBlock::AgentMessage(_)
                                | crate::scrollback::block::RenderBlock::Btw(_)
                        )
                        || entry.hook_data.as_ref().is_some_and(|hd| hd.has_content()))
                {
                    changed = true;
                }
                self.hovered_entry = new_hover;
                self.hovered_prompt = new_prompt_hover;
                if new_timeline_hover != self.timeline_hover {
                    self.timeline_hover = new_timeline_hover;
                    self.sync_timeline_hover_preview();
                    changed = true;
                }
                changed |= self
                    .set_hovered_follow_up_chip(self.follow_up_chip_at(mouse.column, mouse.row));
                changed |= self.hit_badge.update_hover(mouse.column, mouse.row);
                changed |= self.hit_context.update_hover(mouse.column, mouse.row);
                changed |= self.hit_todo_close.update_hover(mouse.column, mouse.row);
                changed |= self.hit_queue_close.update_hover(mouse.column, mouse.row);
                changed |= self.hit_queue_badge.update_hover(mouse.column, mouse.row);
                if matches!(
                    self.pane_areas.hit_test(mouse.column, mouse.row),
                    Some(AgentPane::Queue)
                ) {
                    changed |= self.queue.update_delete_hover(mouse.column, mouse.row);
                    changed |= self.queue.update_send_now_hover(mouse.column, mouse.row);
                    changed |= self.queue.update_row_hover(mouse.column, mouse.row);
                } else {
                    if self.queue.hovered_delete_id.is_some() {
                        self.queue.clear_delete_hover();
                        changed = true;
                    }
                    changed |= self.queue.clear_send_now_hover();
                    changed |= self.queue.clear_row_hover();
                }
                changed |= self.hit_plan_button.update_hover(mouse.column, mouse.row);
                changed |= self
                    .hit_plan_approval_status
                    .update_hover(mouse.column, mouse.row);
                changed |= self
                    .hit_follow_indicator
                    .update_hover(mouse.column, mouse.row);
                changed |= self.hit_cancel_button.update_hover(mouse.column, mouse.row);
                changed |= self.hit_bg_button.update_hover(mouse.column, mouse.row);
                changed |= self.hit_bg_status.update_hover(mouse.column, mouse.row);
                changed |= self.hit_goal_status.update_hover(mouse.column, mouse.row);
                changed |= self.hit_bg_close.update_hover(mouse.column, mouse.row);
                changed |= self.hit_catalog_close.update_hover(mouse.column, mouse.row);
                changed |= self.hit_cwd.update_hover(mouse.column, mouse.row);
                {
                    let new_kill = self
                        .tasks
                        .kill_button_rects
                        .iter()
                        .find(|(_, rect)| rect.contains((mouse.column, mouse.row).into()))
                        .map(|(tid, _)| tid.clone());
                    if new_kill != self.tasks.hovered_kill {
                        self.tasks.hovered_kill = new_kill;
                        changed = true;
                    }
                    let new_view = self
                        .tasks
                        .view_button_rects
                        .iter()
                        .find(|(_, rect)| rect.contains((mouse.column, mouse.row).into()))
                        .map(|(tid, _)| tid.clone());
                    if new_view != self.tasks.hovered_view {
                        self.tasks.hovered_view = new_view;
                        changed = true;
                    }
                }
                changed |= self.hit_sb_copy.update_hover(mouse.column, mouse.row);
                changed |= self.hit_sb_view.update_hover(mouse.column, mouse.row);
                if let Some(hd_area) = self.history_dropdown_area {
                    let hs_count = self.prompt.history_search.result_count();
                    let has_sb = hs_count > hd_area.height as usize;
                    let on_sb =
                        has_sb && mouse.column >= hd_area.x + hd_area.width.saturating_sub(2);
                    let new_hover = if hd_area.contains((mouse.column, mouse.row).into()) && !on_sb
                    {
                        let vis_row = (mouse.row - hd_area.y) as usize;
                        let sel = self.prompt.history_search.selected;
                        let visible_rows = hd_area.height as usize;
                        let scroll_offset = if sel >= visible_rows {
                            sel - (visible_rows - 1)
                        } else {
                            0
                        };
                        Some(scroll_offset + vis_row)
                    } else {
                        None
                    };
                    changed |= self.prompt.history_search.set_hovered(new_hover);
                } else {
                    changed |= self.prompt.history_search.set_hovered(None);
                }
                if let Some(dd_area) = self.dropdown_items_area {
                    let has_scrollbar =
                        self.prompt.file_search.result_count() > dd_area.height as usize;
                    let on_scrollbar = has_scrollbar
                        && mouse.column >= dd_area.x + dd_area.width.saturating_sub(2);
                    let new_dd_hover =
                        if dd_area.contains((mouse.column, mouse.row).into()) && !on_scrollbar {
                            Some(
                                (mouse.row - dd_area.y) as usize
                                    + self.prompt.file_search.scroll_offset(),
                            )
                        } else {
                            None
                        };
                    changed |= self.prompt.file_search.set_hovered(new_dd_hover);
                } else {
                    changed |= self.prompt.file_search.set_hovered(None);
                }
                if let Some(dd_area) = self.slash_dropdown_items_area {
                    let has_scrollbar = self.slash_dropdown_hit.has_scrollbar;
                    let on_scrollbar = has_scrollbar
                        && mouse.column >= dd_area.x + dd_area.width.saturating_sub(2);
                    let new_hover =
                        if dd_area.contains((mouse.column, mouse.row).into()) && !on_scrollbar {
                            self.slash_dropdown_hit
                                .row_items
                                .get((mouse.row - dd_area.y) as usize)
                                .copied()
                        } else {
                            None
                        };
                    changed |= self.prompt.set_slash_hovered(new_hover);
                } else {
                    changed |= self.prompt.set_slash_hovered(None);
                }
                if let Some(dd_area) = self.completion_dropdown_items_area {
                    let dd = &self.prompt.suggestions.dropdown;
                    let has_scrollbar = dd.items.len() > dd_area.height as usize;
                    let on_scrollbar = has_scrollbar
                        && mouse.column >= dd_area.x + dd_area.width.saturating_sub(2);
                    let new_hover =
                        if dd_area.contains((mouse.column, mouse.row).into()) && !on_scrollbar {
                            let offset = crate::views::completion_dropdown::scroll_offset(dd);
                            Some((mouse.row - dd_area.y) as usize + offset)
                        } else {
                            None
                        };
                    changed |= self.prompt.set_completion_hovered(new_hover);
                } else {
                    changed |= self.prompt.set_completion_hovered(None);
                }
                changed |= self.update_hovered_link(is_link_modifier_held(mouse.modifiers));
                if changed {
                    InputOutcome::Changed
                } else {
                    InputOutcome::Unchanged
                }
            }
            _ => InputOutcome::Unchanged,
        }
    }
    /// Apply a scrollbar click/drag at the given screen row.
    ///
    /// Uses [`scrollbar_click_to_offset`] (same math as the thumb renderer)
    /// so the resulting scroll position is the exact inverse of where the
    /// thumb would be drawn for that offset.
    pub(super) fn apply_scrollbar_click(&mut self, screen_y: u16) -> bool {
        use crate::render::scrollbar::{ScrollbarClickResult, scrollbar_click_to_offset};
        let Some(sb) = self.hit_scrollbar.rect else {
            return false;
        };
        if sb.height == 0 {
            return false;
        }
        let cell_index = screen_y.saturating_sub(sb.y);
        let (_, viewport_height, total_height) = self.scrollback.scroll_info();
        let scale = if total_height > u16::MAX as usize {
            (total_height / u16::MAX as usize) + 1
        } else {
            1
        };
        let scaled_total = (total_height / scale) as u16;
        let result =
            scrollbar_click_to_offset(cell_index, sb.height, scaled_total, viewport_height);
        match result {
            ScrollbarClickResult::Top => {
                self.scrollback.goto_top();
            }
            ScrollbarClickResult::Bottom => {
                self.scrollback.goto_bottom();
            }
            ScrollbarClickResult::Offset(offset) => {
                self.scrollback
                    .set_scroll_offset(offset.saturating_mul(scale));
            }
        }
        true
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::app::agent::AgentState;
    use crate::app::agent_view::PromptMode;
    use crate::app::agent_view::test_fixtures::{
        make_running_agent, running_agent_local_only, test_pasted_image,
    };
    use crossterm::event::KeyModifiers;
    use ratatui::buffer::Buffer;
    use ratatui::layout::Rect;
    /// Render the queue, locate the action-button cell where `hit` resolves
    /// to `selected_id`, and dispatch a left-click on it; returns the outcome.
    fn click_queue_button(
        agent: &mut AgentView,
        selected_id: u64,
        hit: impl Fn(&AgentView, u16, u16) -> Option<u64>,
    ) -> InputOutcome {
        agent.queue.list_state.select_by_id(selected_id);
        let area = Rect::new(0, 0, 80, 6);
        let mut buf = Buffer::empty(area);
        let layout_cfg = crate::appearance::LayoutConfig::default();
        let running = agent.session.state.is_turn_running();
        agent
            .queue
            .render(area, &mut buf, true, &layout_cfg, None, running);
        agent.pane_areas.queue = area;
        let mut found = None;
        'find: for row in area.y..area.y + area.height {
            for col in area.x..area.x + area.width {
                if hit(agent, col, row) == Some(selected_id) {
                    found = Some((col, row));
                    break 'find;
                }
            }
        }
        let (col, row) = found.expect("queue button must render for the selected row");
        agent.handle_mouse(&MouseEvent {
            kind: MouseEventKind::Down(MouseButton::Left),
            column: col,
            row,
            modifiers: KeyModifiers::empty(),
        })
    }
    /// Left-click the row's `[Interject]` (send-now) button.
    fn click_send_now(agent: &mut AgentView, selected_id: u64) -> InputOutcome {
        click_queue_button(agent, selected_id, |a, c, r| a.queue.send_now_click(c, r))
    }
    /// Left-click the row's `[cancel]` (delete) button.
    fn click_delete(agent: &mut AgentView, selected_id: u64) -> InputOutcome {
        click_queue_button(agent, selected_id, |a, c, r| a.queue.delete_click(c, r))
    }
    /// Mouse "Send now" (interject) on the last local row keeps the pane open
    /// when a server row remains — the third sibling site of the same fix.
    #[test]
    fn mouse_send_now_last_local_row_keeps_pane_open_when_server_remains() {
        let mut agent = make_running_agent();
        agent.active_pane = AgentPane::Queue;
        agent.session.pending_prompts[0]
            .images
            .push(test_pasted_image());
        let ids = agent.queue.entry_ids();
        let outcome = click_send_now(&mut agent, ids[1]);
        match outcome {
            InputOutcome::Action(Action::SendPromptNow { text, images }) => {
                assert_eq!(text, "local one");
                assert_eq!(images.len(), 1, "row image must ride the send-now");
            }
            other => panic!("expected SendPromptNow action, got {other:?}"),
        }
        assert!(agent.session.pending_prompts.is_empty());
        assert_eq!(agent.shared_queue.len(), 1);
        assert!(agent.queue.overlay.visible);
        assert!(agent.queue.overlay.focused);
        assert_eq!(agent.active_pane, AgentPane::Queue);
    }
    /// Hide via the mouse "Send now" path (site 3): with no server rows left,
    /// interjecting the last local row empties the merged view → hide.
    #[test]
    fn mouse_send_now_last_local_row_hides_pane_when_shared_queue_empty() {
        let mut agent = running_agent_local_only();
        let ids = agent.queue.entry_ids();
        assert_eq!(ids.len(), 1);
        let outcome = click_send_now(&mut agent, ids[0]);
        match outcome {
            InputOutcome::Action(Action::SendPromptNow { text, .. }) => {
                assert_eq!(text, "local one")
            }
            other => panic!("expected SendPromptNow action, got {other:?}"),
        }
        assert!(agent.session.pending_prompts.is_empty());
        assert!(!agent.queue.overlay.visible);
        assert!(!agent.queue.overlay.focused);
        assert_eq!(agent.active_pane, AgentPane::Scrollback);
    }
    /// Send-now `[Interject]` on the lone local row while it is being
    /// DIRTY-edited: the removal must discard the edit via the canonical
    /// helper — the old inline removal stranded `EditingQueued` and armed
    /// the invisible modal.
    #[test]
    fn mouse_send_now_edited_lone_local_row_discards_edit_without_orphaned_modal() {
        let mut agent = running_agent_local_only();
        let ids = agent.queue.entry_ids();
        agent.stashed_prompt = Some(crate::views::prompt_widget::StashedPrompt {
            text: "draft".into(),
            cursor: 0,
            images: Vec::new(),
            chip_elements: Vec::new(),
            image_counter: 0,
            image_undo_stash: Vec::new(),
        });
        agent.prompt_mode = PromptMode::EditingQueued {
            id: ids[0],
            original: "local one".into(),
            server_id: None,
            kind: crate::app::agent::QueueEntryKind::Prompt,
        };
        agent.prompt.set_text("local one EDITED");
        let outcome = click_send_now(&mut agent, ids[0]);
        match outcome {
            InputOutcome::Action(Action::SendPromptNow { text, .. }) => {
                assert_eq!(text, "local one")
            }
            other => panic!("expected SendPromptNow action, got {other:?}"),
        }
        assert!(agent.session.pending_prompts.is_empty());
        assert!(matches!(agent.prompt_mode, PromptMode::Normal));
        assert!(
            agent.active_modal.is_none(),
            "orphaned EditConfirm would brick input"
        );
        assert_eq!(agent.prompt.text(), "draft");
        assert!(!agent.queue.overlay.visible);
    }
    /// Mouse `[cancel]` of the FRONT local row being edited while idle:
    /// discarding the edit releases the drain block, so the click must kick
    /// `DrainQueue` like the modal Delete arm (the row behind must not sit
    /// stuck until an unrelated trigger).
    #[test]
    fn mouse_delete_edited_front_row_while_idle_kicks_drain() {
        let mut agent = running_agent_local_only();
        agent.session.state = AgentState::Idle;
        agent.session.enqueue_prompt("local two".to_string());
        agent.queue.sync_from_merged(
            &agent.session.pending_prompts,
            &agent.shared_queue,
            None,
            None,
            &agent.send_now_painted_blocks,
        );
        let ids = agent.queue.entry_ids();
        agent.stashed_prompt = Some(crate::views::prompt_widget::StashedPrompt {
            text: "draft".into(),
            cursor: 0,
            images: Vec::new(),
            chip_elements: Vec::new(),
            image_counter: 0,
            image_undo_stash: Vec::new(),
        });
        agent.prompt_mode = PromptMode::EditingQueued {
            id: ids[0],
            original: "local one".into(),
            server_id: None,
            kind: crate::app::agent::QueueEntryKind::Prompt,
        };
        agent.prompt.set_text("local one EDITED");
        let outcome = click_delete(&mut agent, ids[0]);
        assert!(
            matches!(outcome, InputOutcome::Action(Action::DrainQueue)),
            "deleting the edited front row must kick the drain, got {outcome:?}"
        );
        assert_eq!(agent.session.pending_prompts.len(), 1);
        assert_eq!(agent.session.pending_prompts[0].text, "local two");
        assert!(matches!(agent.prompt_mode, PromptMode::Normal));
        assert!(agent.active_modal.is_none());
        assert_eq!(agent.prompt.text(), "draft");
    }
    /// A synthetic left-click on a rendered follow-up chip yields the LITERAL
    /// `SubmitFollowUp` action (never a slash-command path).
    #[test]
    fn follow_up_chip_click_yields_literal_submit_action() {
        use crate::app::agent_view::test_fixtures::make_agent;
        let mut agent = make_agent();
        agent.apply_follow_ups("resp-1".into(), vec!["/always-approve".into()]);
        let area = Rect::new(0, 0, 40, 1);
        let mut buf = Buffer::empty(area);
        let theme = crate::theme::Theme::current();
        let suggestions = agent.follow_ups.as_ref().unwrap().suggestions.clone();
        agent.follow_up_chips =
            crate::views::agent::render_follow_ups(area, &mut buf, &theme, &suggestions, None);
        let r = agent.follow_up_chips[0];
        let outcome = agent.handle_mouse(&MouseEvent {
            kind: MouseEventKind::Down(MouseButton::Left),
            column: r.x + 1,
            row: r.y,
            modifiers: KeyModifiers::empty(),
        });
        match outcome {
            InputOutcome::Action(Action::SubmitFollowUp(text)) => {
                assert_eq!(text, "/always-approve", "even a /-prefixed chip is literal");
            }
            other => panic!("expected SubmitFollowUp, got {other:?}"),
        }
    }
    /// Double-click on a `[Pasted: N lines]` chip expands it into plain
    /// editable text; the first click only places the cursor on the chip.
    #[test]
    fn double_click_on_paste_chip_expands_it() {
        use crate::app::agent_view::test_fixtures::make_agent;
        use crate::views::prompt_widget::PromptStyle;
        let mut agent = make_agent();
        agent.active_pane = AgentPane::Prompt;
        let text = "line1\nline2\nline3\nline4";
        agent.prompt.handle_paste(text);
        assert_eq!(agent.prompt.textarea.elements().len(), 1);
        let area = Rect::new(0, 0, 40, 4);
        agent.pane_areas.prompt = area;
        let mut buf = Buffer::empty(area);
        let style = PromptStyle {
            chrome: false,
            vpad_top: 0,
            ..Default::default()
        };
        agent.prompt.draw(&mut buf, area, None, &style, None);
        let ta = agent.prompt.textarea_area();
        let click = MouseEvent {
            kind: MouseEventKind::Down(MouseButton::Left),
            column: ta.x + 1,
            row: ta.y,
            modifiers: KeyModifiers::empty(),
        };
        agent.handle_mouse(&click);
        assert_eq!(
            agent.prompt.textarea.elements().len(),
            1,
            "single click must not expand the chip"
        );
        agent.handle_mouse(&click);
        assert!(
            agent.prompt.textarea.elements().is_empty(),
            "double-click must expand the chip"
        );
        assert_eq!(agent.prompt.textarea.text(), text);
    }
}
